From 0a676297c9f61d26849f6c75ffd295ad8d2c7eb8 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Fri, 23 Oct 2015 20:02:23 +0530 Subject: [PATCH 1/8] Updated to version 2.47.0 --- CHANGES.md | 241 + LICENSE | 202 + NOTICE | 2 + README.md | 132 +- _base.js | 135 +- builder.js | 495 +- chrome.js | 498 +- docs/Changes.html | 317 + docs/class_bot_Error.html | 21 +- docs/class_goog_Disposable.html | 37 + docs/class_goog_Uri.html | 47 +- docs/class_goog_Uri_QueryData.html | 26 +- docs/class_goog_asserts_AssertionError.html | 6 +- docs/class_goog_async_run_WorkItem_.html | 1 + docs/class_goog_debug_Error.html | 2 +- docs/class_goog_debug_LogBuffer.html | 2 + docs/class_goog_debug_LogRecord.html | 12 + docs/class_goog_debug_Logger.html | 59 + docs/class_goog_debug_Logger_Level.html | 32 + docs/class_goog_dom_DomHelper.html | 104 + docs/class_goog_events_BrowserEvent.html | 25 + docs/class_goog_events_Event.html | 16 + docs/class_goog_events_EventId.html | 10 + docs/class_goog_events_EventTarget.html | 68 + docs/class_goog_events_Listener.html | 6 + docs/class_goog_events_ListenerMap.html | 23 + docs/class_goog_iter_GroupByIterator_.html | 12 + docs/class_goog_iter_Iterator.html | 8 +- docs/class_goog_json_Serializer.html | 4 +- .../class_goog_labs_testing_AllOfMatcher.html | 4 +- .../class_goog_labs_testing_AnyOfMatcher.html | 2 +- ...lass_goog_labs_testing_CloseToMatcher.html | 2 +- ...og_labs_testing_ContainsStringMatcher.html | 2 +- ...ass_goog_labs_testing_EndsWithMatcher.html | 2 +- ...ting_EqualToIgnoringWhitespaceMatcher.html | 2 +- ...lass_goog_labs_testing_EqualToMatcher.html | 2 +- ...class_goog_labs_testing_EqualsMatcher.html | 2 +- ...abs_testing_GreaterThanEqualToMatcher.html | 2 +- ..._goog_labs_testing_GreaterThanMatcher.html | 2 +- ..._goog_labs_testing_HasPropertyMatcher.html | 2 +- ...s_goog_labs_testing_InstanceOfMatcher.html | 2 +- .../class_goog_labs_testing_IsNotMatcher.html | 2 +- ...class_goog_labs_testing_IsNullMatcher.html | 2 +- ...labs_testing_IsNullOrUndefinedMatcher.html | 2 +- ..._goog_labs_testing_IsUndefinedMatcher.html | 2 +- ...g_labs_testing_LessThanEqualToMatcher.html | 2 +- ...ass_goog_labs_testing_LessThanMatcher.html | 2 +- .../class_goog_labs_testing_MatcherError.html | 4 +- ...goog_labs_testing_ObjectEqualsMatcher.html | 2 +- .../class_goog_labs_testing_RegexMatcher.html | 2 +- ...s_goog_labs_testing_StartsWithMatcher.html | 2 +- ..._testing_StringContainsInOrderMatcher.html | 2 +- docs/class_goog_math_Box.html | 23 + docs/class_goog_math_Coordinate.html | 22 + docs/class_goog_math_Rect.html | 34 + docs/class_goog_math_Size.html | 10 + .../class_goog_net_DefaultXmlHttpFactory.html | 6 +- .../class_goog_net_WrapperXmlHttpFactory.html | 9 +- docs/class_goog_net_XmlHttpFactory.html | 6 +- docs/class_goog_structs_Map.html | 26 +- docs/class_goog_structs_Set.html | 28 + docs/class_goog_testing_AsyncTestCase.html | 81 + ...syncTestCase_ControlBreakingException.html | 1 + docs/class_goog_testing_FunctionCall.html | 2 + docs/class_goog_testing_JsUnitException.html | 2 + ...og_testing_LooseExpectationCollection.html | 4 + docs/class_goog_testing_LooseMock.html | 25 + docs/class_goog_testing_Mock.html | 29 + docs/class_goog_testing_MockClock.html | 66 + docs/class_goog_testing_MockControl.html | 22 + docs/class_goog_testing_MockExpectation.html | 3 + ...ass_goog_testing_ObjectPropertyString.html | 3 + docs/class_goog_testing_PropertyReplacer.html | 46 + docs/class_goog_testing_StrictMock.html | 23 + docs/class_goog_testing_TestCase.html | 85 + docs/class_goog_testing_TestCase_Error.html | 1 + docs/class_goog_testing_TestCase_Result.html | 5 + docs/class_goog_testing_TestCase_Test.html | 2 + ..._goog_testing_TestCase_protectedDate_.html | 3 + docs/class_goog_testing_TestRunner.html | 17 + docs/class_goog_testing_events_Event.html | 6 + ..._testing_mockmatchers_ArgumentMatcher.html | 9 + ...g_testing_mockmatchers_IgnoreArgument.html | 8 + ..._goog_testing_mockmatchers_InstanceOf.html | 6 + ...oog_testing_mockmatchers_ObjectEquals.html | 5 + ...goog_testing_mockmatchers_RegexpMatch.html | 6 + ...oog_testing_mockmatchers_SaveArgument.html | 8 + ...lass_goog_testing_mockmatchers_TypeOf.html | 6 + docs/class_goog_testing_stacktrace_Frame.html | 6 + docs/class_webdriver_AbstractBuilder.html | 8 +- docs/class_webdriver_ActionSequence.html | 202 +- docs/class_webdriver_Alert.html | 133 +- docs/class_webdriver_AlertPromise.html | 91 + docs/class_webdriver_Builder.html | 6 +- docs/class_webdriver_Capabilities.html | 69 +- docs/class_webdriver_Command.html | 16 +- docs/class_webdriver_EventEmitter.html | 42 +- docs/class_webdriver_FileDetector.html | 21 + docs/class_webdriver_FirefoxDomExecutor.html | 4 +- docs/class_webdriver_Locator.html | 14 +- docs/class_webdriver_Serializable.html | 9 + docs/class_webdriver_Session.html | 16 +- docs/class_webdriver_TouchSequence.html | 49 + docs/class_webdriver_UnhandledAlertError.html | 22 +- docs/class_webdriver_WebDriver.html | 562 +- docs/class_webdriver_WebDriver_Logs.html | 25 +- .../class_webdriver_WebDriver_Navigation.html | 21 +- docs/class_webdriver_WebDriver_Options.html | 57 +- ...ass_webdriver_WebDriver_TargetLocator.html | 65 +- docs/class_webdriver_WebDriver_Timeouts.html | 50 +- docs/class_webdriver_WebDriver_Window.html | 37 +- docs/class_webdriver_WebElement.html | 466 +- docs/class_webdriver_WebElementPromise.html | 266 + docs/class_webdriver_http_CorsClient.html | 2 +- docs/class_webdriver_http_Executor.html | 30 +- docs/class_webdriver_http_Request.html | 16 +- docs/class_webdriver_http_Response.html | 16 +- docs/class_webdriver_http_XhrClient.html | 2 +- docs/class_webdriver_logging_Entry.html | 19 +- docs/class_webdriver_logging_Level.html | 55 + docs/class_webdriver_logging_LogRecord.html | 45 + docs/class_webdriver_logging_Logger.html | 119 + docs/class_webdriver_logging_Preferences.html | 8 + ..._webdriver_promise_CanceledTaskError_.html | 4 +- ...s_webdriver_promise_CancellationError.html | 18 + docs/class_webdriver_promise_ControlFlow.html | 225 +- docs/class_webdriver_promise_Deferred.html | 188 +- docs/class_webdriver_promise_Frame_.html | 109 +- ...omise_MultipleUnhandledRejectionError.html | 14 + docs/class_webdriver_promise_Node_.html | 99 +- docs/class_webdriver_promise_Promise.html | 156 +- docs/class_webdriver_promise_Task_.html | 99 +- docs/class_webdriver_stacktrace_Frame.html | 28 +- docs/class_webdriver_stacktrace_Snapshot.html | 11 +- docs/class_webdriver_testing_Assertion.html | 131 +- ..._testing_Assertion_DelegatingMatcher_.html | 16 +- ...ass_webdriver_testing_ContainsMatcher.html | 12 +- ...ss_webdriver_testing_NegatedAssertion.html | 123 +- docs/class_webdriver_testing_TestCase.html | 76 + docs/class_webdriver_until_Condition.html | 7 + docs/dossier.css | 2 +- docs/dossier.js | 221 +- docs/enum_bot_ErrorCode.html | 5 +- docs/enum_bot_Error_State.html | 3 +- docs/enum_goog_Disposable_MonitoringMode.html | 6 + docs/enum_goog_dom_BrowserFeature.html | 8 + docs/enum_goog_dom_NodeType.html | 10 + docs/enum_goog_dom_TagName.html | 2 + ..._goog_events_BrowserEvent_MouseButton.html | 1 + docs/enum_goog_events_BrowserFeature.html | 4 + ...num_goog_events_CaptureSimulationMode.html | 3 + docs/enum_goog_events_EventType.html | 1 + docs/enum_goog_events_KeyCodes.html | 22 + docs/enum_goog_net_XmlHttp_OptionType.html | 4 +- docs/enum_goog_net_XmlHttp_ReadyState.html | 4 +- docs/enum_goog_string_Unicode.html | 2 +- docs/enum_goog_testing_TestCase_Order.html | 2 + docs/enum_goog_uri_utils_CharCode_.html | 2 +- docs/enum_goog_uri_utils_ComponentIndex.html | 2 +- ...num_goog_uri_utils_StandardQueryParam.html | 2 +- docs/enum_webdriver_Browser.html | 3 +- docs/enum_webdriver_Button.html | 3 +- docs/enum_webdriver_Capability.html | 47 +- docs/enum_webdriver_CommandName.html | 5 +- ...bdriver_FirefoxDomExecutor_Attribute_.html | 2 +- ...bdriver_FirefoxDomExecutor_EventType_.html | 2 +- docs/enum_webdriver_Key.html | 21 +- docs/enum_webdriver_logging_Level.html | 2 +- docs/enum_webdriver_logging_Type.html | 7 +- ...bdriver_promise_ControlFlow_EventType.html | 13 +- ...num_webdriver_promise_Deferred_State_.html | 2 +- docs/index.html | 161 +- ...nterface_goog_debug_EntryPointMonitor.html | 11 + ...interface_goog_disposable_IDisposable.html | 3 + docs/interface_goog_events_Listenable.html | 84 + docs/interface_goog_events_ListenableKey.html | 2 + docs/interface_goog_labs_testing_Matcher.html | 2 +- docs/interface_goog_net_XhrLike.html | 3 + docs/interface_goog_structs_Collection.html | 1 + .../interface_goog_testing_MockInterface.html | 3 + docs/interface_webdriver_CommandExecutor.html | 14 +- docs/interface_webdriver_http_Client.html | 16 +- .../interface_webdriver_promise_Thenable.html | 77 + docs/license.html | 2 +- docs/module_selenium-webdriver.html | 27 +- docs/module_selenium-webdriver__base.html | 37 +- ...elenium-webdriver__base_class_Context.html | 5 + docs/module_selenium-webdriver_builder.html | 3 +- ...enium-webdriver_builder_class_Builder.html | 151 +- docs/module_selenium-webdriver_chrome.html | 95 +- ...elenium-webdriver_chrome_class_Driver.html | 280 + ...lenium-webdriver_chrome_class_Options.html | 147 +- ...webdriver_chrome_class_ServiceBuilder.html | 59 +- ...lenium-webdriver_class_ActionSequence.html | 115 + ...dule_selenium-webdriver_class_Builder.html | 138 + ...selenium-webdriver_class_Capabilities.html | 60 + ...dule_selenium-webdriver_class_Command.html | 15 + ...selenium-webdriver_class_EventEmitter.html | 35 + ...selenium-webdriver_class_FileDetector.html | 21 + ...selenium-webdriver_class_Serializable.html | 9 + ...dule_selenium-webdriver_class_Session.html | 13 + ...le_selenium-webdriver_class_WebDriver.html | 313 + ...e_selenium-webdriver_class_WebElement.html | 216 + ...ium-webdriver_class_WebElementPromise.html | 266 + ...odule_selenium-webdriver_enum_Browser.html | 2 + ...module_selenium-webdriver_enum_Button.html | 2 + ...le_selenium-webdriver_enum_Capability.html | 33 + ...e_selenium-webdriver_enum_CommandName.html | 3 + docs/module_selenium-webdriver_enum_Key.html | 12 + docs/module_selenium-webdriver_error.html | 8 +- ..._selenium-webdriver_error_class_Error.html | 15 + ...lenium-webdriver_error_enum_ErrorCode.html | 3 + docs/module_selenium-webdriver_executors.html | 13 +- ...iver_executors_class_DeferredExecutor.html | 12 + docs/module_selenium-webdriver_firefox.html | 67 + ...ule_selenium-webdriver_firefox_binary.html | 4 + ...webdriver_firefox_binary_class_Binary.html | 21 + ...lenium-webdriver_firefox_class_Binary.html | 21 + ...lenium-webdriver_firefox_class_Driver.html | 276 + ...enium-webdriver_firefox_class_Options.html | 19 + ...enium-webdriver_firefox_class_Profile.html | 50 + ..._selenium-webdriver_firefox_extension.html | 8 + ...le_selenium-webdriver_firefox_profile.html | 13 + ...bdriver_firefox_profile_class_Profile.html | 50 + docs/module_selenium-webdriver_http.html | 13 +- ...elenium-webdriver_http_class_Executor.html | 22 + ...enium-webdriver_http_class_HttpClient.html | 18 +- ...selenium-webdriver_http_class_Request.html | 12 + ...elenium-webdriver_http_class_Response.html | 13 + docs/module_selenium-webdriver_http_util.html | 22 +- docs/module_selenium-webdriver_ie.html | 11 + ...le_selenium-webdriver_ie_class_Driver.html | 274 + ...e_selenium-webdriver_ie_class_Options.html | 89 + ...dule_selenium-webdriver_ie_enum_Level.html | 1 + docs/module_selenium-webdriver_io.html | 41 +- docs/module_selenium-webdriver_io_exec.html | 6 + ...odule_selenium-webdriver_namespace_By.html | 56 + ...le_selenium-webdriver_namespace_error.html | 4 + ..._selenium-webdriver_namespace_logging.html | 18 + ..._selenium-webdriver_namespace_promise.html | 162 + ...lenium-webdriver_namespace_stacktrace.html | 17 + ...le_selenium-webdriver_namespace_until.html | 82 + docs/module_selenium-webdriver_net.html | 8 +- ...ule_selenium-webdriver_net_portprober.html | 18 +- docs/module_selenium-webdriver_opera.html | 62 + ...selenium-webdriver_opera_class_Driver.html | 276 + ...elenium-webdriver_opera_class_Options.html | 44 + ...-webdriver_opera_class_ServiceBuilder.html | 35 + docs/module_selenium-webdriver_phantomjs.html | 3 +- ...nium-webdriver_phantomjs_class_Driver.html | 299 + docs/module_selenium-webdriver_proxy.html | 50 +- docs/module_selenium-webdriver_remote.html | 24 +- ...-webdriver_remote_class_DriverService.html | 51 +- ...m-webdriver_remote_class_FileDetector.html | 22 + ...webdriver_remote_class_SeleniumServer.html | 71 +- docs/module_selenium-webdriver_safari.html | 9 + ...elenium-webdriver_safari_class_Driver.html | 279 + ...lenium-webdriver_safari_class_Options.html | 23 + docs/module_selenium-webdriver_testing.html | 136 +- ...ule_selenium-webdriver_testing_assert.html | 28 +- ...mespace_PRIMITIVE_EQUALITY_PREDICATES.html | 1 + docs/namespace_bot.html | 8 +- docs/namespace_bot_json.html | 24 +- docs/namespace_bot_response.html | 23 +- docs/namespace_bot_userAgent.html | 54 +- docs/namespace_goog.html | 311 +- docs/namespace_goog_array.html | 188 +- docs/namespace_goog_asserts.html | 18 +- docs/namespace_goog_async.html | 14 + docs/namespace_goog_async_nextTick.html | 11 + docs/namespace_goog_async_run.html | 9 + docs/namespace_goog_debug.html | 28 +- docs/namespace_goog_debug_LogManager.html | 10 + ...mespace_goog_debug_entryPointRegistry.html | 17 + docs/namespace_goog_defineClass.html | 16 + docs/namespace_goog_disposable.html | 1 + docs/namespace_goog_dom.html | 232 + docs/namespace_goog_dom_vendor.html | 4 + docs/namespace_goog_events.html | 97 + docs/namespace_goog_functions.html | 30 + docs/namespace_goog_iter.html | 249 +- docs/namespace_goog_json.html | 12 +- docs/namespace_goog_labs.html | 2 +- docs/namespace_goog_labs_testing.html | 2 +- docs/namespace_goog_labs_userAgent.html | 1 + ...namespace_goog_labs_userAgent_browser.html | 13 + .../namespace_goog_labs_userAgent_engine.html | 4 + docs/namespace_goog_labs_userAgent_util.html | 9 + docs/namespace_goog_math.html | 61 + docs/namespace_goog_net.html | 2 +- docs/namespace_goog_net_XmlHttp.html | 6 +- docs/namespace_goog_net_XmlHttpDefines.html | 1 + docs/namespace_goog_object.html | 46 +- docs/namespace_goog_reflect.html | 7 + docs/namespace_goog_string.html | 107 +- docs/namespace_goog_structs.html | 19 +- docs/namespace_goog_style.html | 211 + docs/namespace_goog_testing.html | 28 + docs/namespace_goog_testing_MethodMock.html | 3 + docs/namespace_goog_testing_asserts.html | 15 + docs/namespace_goog_testing_events.html | 80 + docs/namespace_goog_testing_jsunit.html | 1 + docs/namespace_goog_testing_mockmatchers.html | 9 + docs/namespace_goog_testing_stacktrace.html | 29 + docs/namespace_goog_testing_watchers.html | 1 + docs/namespace_goog_uri.html | 2 +- docs/namespace_goog_uri_utils.html | 67 +- docs/namespace_goog_userAgent.html | 49 +- docs/namespace_goog_userAgent_product.html | 2 +- docs/namespace_webdriver.html | 34 +- docs/namespace_webdriver_By.html | 82 +- docs/namespace_webdriver_http.html | 10 +- docs/namespace_webdriver_logging.html | 74 +- docs/namespace_webdriver_process.html | 2 +- docs/namespace_webdriver_promise.html | 244 +- docs/namespace_webdriver_stacktrace.html | 56 +- docs/namespace_webdriver_testing.html | 8 +- docs/namespace_webdriver_testing_assert.html | 12 +- docs/namespace_webdriver_testing_asserts.html | 28 +- docs/namespace_webdriver_until.html | 82 + docs/source/_base.js.src.html | 2 +- docs/source/builder.js.src.html | 2 +- docs/source/chrome.js.src.html | 2 +- docs/source/error.js.src.html | 2 +- docs/source/executors.js.src.html | 2 +- docs/source/firefox/binary.js.src.html | 1 + docs/source/firefox/extension.js.src.html | 1 + docs/source/firefox/index.js.src.html | 1 + docs/source/firefox/profile.js.src.html | 1 + docs/source/http/index.js.src.html | 2 +- docs/source/http/util.js.src.html | 2 +- docs/source/ie.js.src.html | 1 + docs/source/index.js.src.html | 2 +- docs/source/io/exec.js.src.html | 1 + docs/source/io/index.js.src.html | 2 +- docs/source/lib/atoms/error.js.src.html | 2 +- docs/source/lib/atoms/json.js.src.html | 2 +- docs/source/lib/atoms/response.js.src.html | 2 +- docs/source/lib/atoms/userAgent.js.src.html | 2 +- docs/source/lib/goog/array/array.js.src.html | 2 +- .../lib/goog/asserts/asserts.js.src.html | 2 +- .../lib/goog/async/freelist.js.src.html | 1 + .../lib/goog/async/nexttick.js.src.html | 1 + docs/source/lib/goog/async/run.js.src.html | 1 + .../lib/goog/async/workqueue.js.src.html | 1 + docs/source/lib/goog/base.js.src.html | 2 +- docs/source/lib/goog/debug/debug.js.src.html | 1 + .../goog/debug/entrypointregistry.js.src.html | 1 + docs/source/lib/goog/debug/error.js.src.html | 2 +- .../lib/goog/debug/logbuffer.js.src.html | 1 + docs/source/lib/goog/debug/logger.js.src.html | 1 + .../lib/goog/debug/logrecord.js.src.html | 1 + docs/source/lib/goog/deps.js.src.html | 2 +- .../goog/disposable/disposable.js.src.html | 1 + .../goog/disposable/idisposable.js.src.html | 1 + .../lib/goog/dom/browserfeature.js.src.html | 1 + docs/source/lib/goog/dom/dom.js.src.html | 1 + docs/source/lib/goog/dom/nodetype.js.src.html | 1 + docs/source/lib/goog/dom/safe.js.src.html | 1 + docs/source/lib/goog/dom/tagname.js.src.html | 1 + docs/source/lib/goog/dom/tags.js.src.html | 1 + docs/source/lib/goog/dom/vendor.js.src.html | 1 + .../lib/goog/events/browserevent.js.src.html | 1 + .../goog/events/browserfeature.js.src.html | 1 + docs/source/lib/goog/events/event.js.src.html | 1 + .../lib/goog/events/eventid.js.src.html | 1 + .../source/lib/goog/events/events.js.src.html | 1 + .../lib/goog/events/eventtarget.js.src.html | 1 + .../lib/goog/events/eventtype.js.src.html | 1 + .../lib/goog/events/keycodes.js.src.html | 1 + .../lib/goog/events/listenable.js.src.html | 1 + .../lib/goog/events/listener.js.src.html | 1 + .../lib/goog/events/listenermap.js.src.html | 1 + docs/source/lib/goog/fs/url.js.src.html | 1 + .../lib/goog/functions/functions.js.src.html | 1 + .../source/lib/goog/html/safehtml.js.src.html | 1 + .../lib/goog/html/safescript.js.src.html | 1 + .../lib/goog/html/safestyle.js.src.html | 1 + .../lib/goog/html/safestylesheet.js.src.html | 1 + docs/source/lib/goog/html/safeurl.js.src.html | 1 + .../goog/html/trustedresourceurl.js.src.html | 1 + .../html/uncheckedconversions.js.src.html | 1 + docs/source/lib/goog/i18n/bidi.js.src.html | 1 + docs/source/lib/goog/iter/iter.js.src.html | 2 +- docs/source/lib/goog/json/json.js.src.html | 2 +- .../goog/labs/testing/assertthat.js.src.html | 2 +- .../labs/testing/logicmatcher.js.src.html | 2 +- .../lib/goog/labs/testing/matcher.js.src.html | 2 +- .../labs/testing/numbermatcher.js.src.html | 2 +- .../labs/testing/objectmatcher.js.src.html | 2 +- .../labs/testing/stringmatcher.js.src.html | 2 +- .../goog/labs/useragent/browser.js.src.html | 1 + .../goog/labs/useragent/engine.js.src.html | 1 + .../goog/labs/useragent/platform.js.src.html | 1 + .../lib/goog/labs/useragent/util.js.src.html | 1 + docs/source/lib/goog/log/log.js.src.html | 1 + docs/source/lib/goog/math/box.js.src.html | 1 + .../lib/goog/math/coordinate.js.src.html | 1 + docs/source/lib/goog/math/math.js.src.html | 1 + docs/source/lib/goog/math/rect.js.src.html | 1 + docs/source/lib/goog/math/size.js.src.html | 1 + .../net/wrapperxmlhttpfactory.js.src.html | 2 +- docs/source/lib/goog/net/xhrlike.js.src.html | 1 + docs/source/lib/goog/net/xmlhttp.js.src.html | 2 +- .../lib/goog/net/xmlhttpfactory.js.src.html | 2 +- .../source/lib/goog/object/object.js.src.html | 2 +- .../lib/goog/promise/promise.js.src.html | 1 + .../lib/goog/promise/resolver.js.src.html | 1 + .../lib/goog/promise/thenable.js.src.html | 1 + .../lib/goog/reflect/reflect.js.src.html | 1 + docs/source/lib/goog/string/const.js.src.html | 1 + .../source/lib/goog/string/string.js.src.html | 2 +- .../lib/goog/string/typedstring.js.src.html | 1 + .../lib/goog/structs/collection.js.src.html | 1 + docs/source/lib/goog/structs/map.js.src.html | 2 +- docs/source/lib/goog/structs/set.js.src.html | 1 + .../lib/goog/structs/structs.js.src.html | 2 +- docs/source/lib/goog/style/style.js.src.html | 1 + .../lib/goog/testing/asserts.js.src.html | 1 + .../goog/testing/asynctestcase.js.src.html | 1 + .../goog/testing/events/events.js.src.html | 1 + .../lib/goog/testing/functionmock.js.src.html | 1 + .../lib/goog/testing/jsunit.js.src.html | 1 + .../lib/goog/testing/loosemock.js.src.html | 1 + docs/source/lib/goog/testing/mock.js.src.html | 1 + .../lib/goog/testing/mockclock.js.src.html | 1 + .../lib/goog/testing/mockcontrol.js.src.html | 1 + .../goog/testing/mockinterface.js.src.html | 1 + .../lib/goog/testing/mockmatchers.js.src.html | 1 + .../testing/objectpropertystring.js.src.html | 1 + .../goog/testing/propertyreplacer.js.src.html | 1 + .../goog/testing/recordfunction.js.src.html | 1 + .../lib/goog/testing/stacktrace.js.src.html | 1 + .../lib/goog/testing/strictmock.js.src.html | 1 + .../lib/goog/testing/testcase.js.src.html | 1 + .../lib/goog/testing/testrunner.js.src.html | 1 + .../lib/goog/testing/watchers.js.src.html | 1 + docs/source/lib/goog/uri/uri.js.src.html | 2 +- docs/source/lib/goog/uri/utils.js.src.html | 2 +- .../lib/goog/useragent/product.js.src.html | 2 +- .../useragent/product_isversion.js.src.html | 2 +- .../lib/goog/useragent/useragent.js.src.html | 2 +- .../lib/webdriver/abstractbuilder.js.src.html | 2 +- .../lib/webdriver/actionsequence.js.src.html | 2 +- docs/source/lib/webdriver/builder.js.src.html | 2 +- docs/source/lib/webdriver/button.js.src.html | 2 +- .../lib/webdriver/capabilities.js.src.html | 2 +- docs/source/lib/webdriver/command.js.src.html | 2 +- docs/source/lib/webdriver/events.js.src.html | 2 +- .../webdriver/firefoxdomexecutor.js.src.html | 2 +- .../lib/webdriver/http/corsclient.js.src.html | 2 +- .../lib/webdriver/http/http.js.src.html | 2 +- .../lib/webdriver/http/xhrclient.js.src.html | 2 +- docs/source/lib/webdriver/key.js.src.html | 2 +- .../source/lib/webdriver/locators.js.src.html | 2 +- docs/source/lib/webdriver/logging.js.src.html | 2 +- docs/source/lib/webdriver/process.js.src.html | 2 +- docs/source/lib/webdriver/promise.js.src.html | 2 +- .../lib/webdriver/serializable.js.src.html | 1 + docs/source/lib/webdriver/session.js.src.html | 2 +- .../lib/webdriver/stacktrace.js.src.html | 2 +- .../lib/webdriver/testing/asserts.js.src.html | 2 +- .../webdriver/testing/testcase.js.src.html | 1 + .../lib/webdriver/touchsequence.js.src.html | 1 + docs/source/lib/webdriver/until.js.src.html | 1 + .../lib/webdriver/webdriver.js.src.html | 2 +- docs/source/net/index.js.src.html | 2 +- docs/source/net/portprober.js.src.html | 2 +- docs/source/opera.js.src.html | 1 + docs/source/phantomjs.js.src.html | 2 +- docs/source/proxy.js.src.html | 2 +- docs/source/remote/index.js.src.html | 2 +- docs/source/safari.js.src.html | 1 + docs/source/testing/assert.js.src.html | 2 +- docs/source/testing/index.js.src.html | 2 +- docs/types.js | 2 +- error.js | 26 +- example/chrome_android.js | 38 + example/chrome_mobile_emulation.js | 39 + example/google_search.js | 68 +- example/google_search_generator.js | 47 + example/google_search_test.js | 59 +- example/logging.js | 39 + example/parallel_flows.js | 51 + executors.js | 33 +- firefox/binary.js | 255 + firefox/extension.js | 186 + firefox/index.js | 316 + firefox/profile.js | 431 + http/index.js | 87 +- http/util.js | 26 +- ie.js | 465 ++ index.js | 44 +- io/exec.js | 140 + io/index.js | 199 +- lib/atoms/error.js | 61 +- lib/atoms/json.js | 34 +- lib/atoms/response.js | 31 +- lib/atoms/userAgent.js | 26 +- lib/firefox/amd64/libnoblur64.so | Bin 0 -> 41623 bytes lib/firefox/i386/libnoblur.so | Bin 0 -> 36970 bytes lib/firefox/webdriver.json | 73 + lib/firefox/webdriver.xpi | Bin 0 -> 687832 bytes lib/goog/array/array.js | 554 +- lib/goog/asserts/asserts.js | 106 +- lib/goog/async/freelist.js | 88 + lib/goog/async/nexttick.js | 241 + lib/goog/async/run.js | 139 + lib/goog/async/workqueue.js | 139 + lib/goog/base.js | 1511 +++- lib/goog/debug/debug.js | 653 ++ lib/goog/debug/entrypointregistry.js | 158 + lib/goog/debug/error.js | 16 +- lib/goog/debug/logbuffer.js | 148 + lib/goog/debug/logger.js | 873 ++ lib/goog/debug/logrecord.js | 242 + lib/goog/deps.js | 197 +- lib/goog/disposable/disposable.js | 307 + lib/goog/disposable/idisposable.js | 45 + lib/goog/dom/browserfeature.js | 72 + lib/goog/dom/dom.js | 2995 +++++++ lib/goog/dom/nodetype.js | 48 + lib/goog/dom/safe.js | 347 + lib/goog/dom/tagname.js | 160 + lib/goog/dom/tags.js | 42 + lib/goog/dom/vendor.js | 96 + lib/goog/events/browserevent.js | 386 + lib/goog/events/browserfeature.js | 85 + lib/goog/events/event.js | 143 + lib/goog/events/eventid.js | 47 + lib/goog/events/events.js | 984 +++ lib/goog/events/eventtarget.js | 394 + lib/goog/events/eventtype.js | 233 + lib/goog/events/keycodes.js | 420 + lib/goog/events/listenable.js | 335 + lib/goog/events/listener.js | 131 + lib/goog/events/listenermap.js | 308 + lib/goog/fs/url.js | 105 + lib/goog/functions/functions.js | 332 + lib/goog/html/safehtml.js | 756 ++ lib/goog/html/safescript.js | 234 + lib/goog/html/safestyle.js | 442 + lib/goog/html/safestylesheet.js | 276 + lib/goog/html/safeurl.js | 431 + lib/goog/html/trustedresourceurl.js | 224 + lib/goog/html/uncheckedconversions.js | 231 + lib/goog/i18n/bidi.js | 908 +++ lib/goog/iter/iter.js | 1037 ++- lib/goog/json/json.js | 160 +- lib/goog/labs/testing/assertthat.js | 5 +- lib/goog/labs/testing/logicmatcher.js | 14 +- lib/goog/labs/testing/matcher.js | 29 + lib/goog/labs/testing/numbermatcher.js | 12 + lib/goog/labs/testing/objectmatcher.js | 13 +- lib/goog/labs/testing/stringmatcher.js | 21 +- lib/goog/labs/useragent/browser.js | 327 + lib/goog/labs/useragent/engine.js | 160 + lib/goog/labs/useragent/platform.js | 160 + lib/goog/labs/useragent/util.js | 148 + lib/goog/log/log.js | 197 + lib/goog/math/box.js | 389 + lib/goog/math/coordinate.js | 268 + lib/goog/math/math.js | 451 ++ lib/goog/math/rect.js | 476 ++ lib/goog/math/size.js | 227 + lib/goog/net/wrapperxmlhttpfactory.js | 11 +- lib/goog/net/xhrlike.js | 124 + lib/goog/net/xmlhttp.js | 28 +- lib/goog/net/xmlhttpfactory.js | 5 +- lib/goog/object/object.js | 121 +- lib/goog/promise/promise.js | 1310 +++ lib/goog/promise/resolver.js | 48 + lib/goog/promise/thenable.js | 126 + lib/goog/reflect/reflect.js | 78 + lib/goog/string/const.js | 182 + lib/goog/string/string.js | 319 +- lib/goog/string/typedstring.js | 48 + lib/goog/structs/collection.js | 56 + lib/goog/structs/map.js | 89 +- lib/goog/structs/set.js | 279 + lib/goog/structs/structs.js | 25 +- lib/goog/style/style.js | 2054 +++++ lib/goog/testing/asserts.js | 1265 +++ lib/goog/testing/asynctestcase.js | 896 +++ lib/goog/testing/events/events.js | 727 ++ lib/goog/testing/functionmock.js | 176 + lib/goog/testing/jsunit.js | 162 + lib/goog/testing/loosemock.js | 242 + lib/goog/testing/mock.js | 645 ++ lib/goog/testing/mockclock.js | 617 ++ lib/goog/testing/mockcontrol.js | 220 + lib/goog/testing/mockinterface.js | 45 + lib/goog/testing/mockmatchers.js | 400 + lib/goog/testing/objectpropertystring.js | 68 + lib/goog/testing/propertyreplacer.js | 245 + lib/goog/testing/recordfunction.js | 215 + lib/goog/testing/stacktrace.js | 594 ++ lib/goog/testing/strictmock.js | 130 + lib/goog/testing/testcase.js | 1569 ++++ lib/goog/testing/testrunner.js | 440 + lib/goog/testing/watchers.js | 46 + lib/goog/uri/uri.js | 303 +- lib/goog/uri/utils.js | 145 +- lib/goog/useragent/product.js | 152 +- lib/goog/useragent/product_isversion.js | 11 +- lib/goog/useragent/useragent.js | 317 +- lib/safari/client.js | 7165 +++++++++++++++++ lib/test/build.js | 26 +- lib/test/data/click_tests/click_iframe.html | 6 + .../data/click_tests/click_in_iframe.html | 8 + .../click_tests/disappearing_element.html | 62 + .../click_tests/overlapping_elements.html | 59 + .../page_with_fixed_element.html | 2 +- lib/test/data/firefox/jetpack-sample.xpi | Bin 0 -> 7289 bytes lib/test/data/firefox/sample.xpi | Bin 0 -> 1551 bytes lib/test/data/formPage.html | 1 + lib/test/data/frameWithAnimals.html | 11 + .../js/skins/lightgray/content.inline.min.css | 1 + .../data/js/skins/lightgray/content.min.css | 1 + .../data/js/skins/lightgray/fonts/readme.md | 1 + .../lightgray/fonts/tinymce-small.dev.svg | 175 + .../skins/lightgray/fonts/tinymce-small.eot | Bin 0 -> 10316 bytes .../skins/lightgray/fonts/tinymce-small.svg | 62 + .../skins/lightgray/fonts/tinymce-small.ttf | Bin 0 -> 10128 bytes .../skins/lightgray/fonts/tinymce-small.woff | Bin 0 -> 7848 bytes .../js/skins/lightgray/fonts/tinymce.dev.svg | 153 + .../data/js/skins/lightgray/fonts/tinymce.eot | Bin 0 -> 10024 bytes .../data/js/skins/lightgray/fonts/tinymce.svg | 63 + .../data/js/skins/lightgray/fonts/tinymce.ttf | Bin 0 -> 9860 bytes .../js/skins/lightgray/fonts/tinymce.woff | Bin 0 -> 7664 bytes .../data/js/skins/lightgray/img/anchor.gif | Bin 0 -> 53 bytes .../data/js/skins/lightgray/img/loader.gif | Bin 0 -> 2608 bytes .../data/js/skins/lightgray/img/object.gif | Bin 0 -> 152 bytes .../data/js/skins/lightgray/img/trans.gif | Bin 0 -> 43 bytes .../data/js/skins/lightgray/skin.ie7.min.css | 1 + lib/test/data/js/skins/lightgray/skin.min.css | 1 + lib/test/data/js/themes/modern/theme.min.js | 1 + lib/test/data/js/tinymce.min.js | 10 + lib/test/data/rich_text.html | 3 +- lib/test/data/screen/screen_frame1.html | 2 +- lib/test/data/screen/screen_frame2.html | 2 +- lib/test/data/slowLoadingResourcePage.html | 4 +- lib/test/data/tinymce.html | 10 + lib/test/data/transparentUpload.html | 70 + lib/test/fileserver.js | 343 +- lib/test/httpserver.js | 37 +- lib/test/index.js | 253 +- lib/test/resources.js | 26 +- lib/test/seleniumserver.js | 8 +- lib/webdriver/abstractbuilder.js | 13 + lib/webdriver/actionsequence.js | 84 +- lib/webdriver/builder.js | 177 +- lib/webdriver/button.js | 26 +- lib/webdriver/capabilities.js | 143 +- lib/webdriver/command.js | 38 +- lib/webdriver/events.js | 25 +- lib/webdriver/firefoxdomexecutor.js | 36 +- lib/webdriver/http/corsclient.js | 37 +- lib/webdriver/http/http.js | 103 +- lib/webdriver/http/xhrclient.js | 29 +- lib/webdriver/key.js | 25 +- lib/webdriver/locators.js | 48 +- lib/webdriver/logging.js | 393 +- lib/webdriver/promise.js | 3027 ++++--- lib/webdriver/serializable.js | 41 + lib/webdriver/session.js | 25 +- lib/webdriver/stacktrace.js | 117 +- lib/webdriver/test/builder_test.js | 53 + lib/webdriver/test/capabilities_test.js | 72 + lib/webdriver/test/events_test.js | 203 + lib/webdriver/test/http/corsclient_test.js | 151 + lib/webdriver/test/http/http_test.js | 428 + lib/webdriver/test/http/xhrclient_test.js | 196 + lib/webdriver/test/locators_test.js | 59 + lib/webdriver/test/logging_test.js | 85 + lib/webdriver/test/process_test.js | 72 + lib/webdriver/test/promise_error_test.js | 719 ++ lib/webdriver/test/promise_flow_test.js | 1957 +++++ lib/webdriver/test/promise_generator_test.js | 251 + lib/webdriver/test/promise_test.js | 1981 +++++ lib/webdriver/test/stacktrace_test.js | 480 ++ lib/webdriver/test/test_bootstrap.js | 67 + lib/webdriver/test/testing/asserts_test.js | 117 + lib/webdriver/test/testing/client_test.js | 88 + lib/webdriver/test/testing/flowtester_test.js | 204 + lib/webdriver/test/testing/testcase_test.js | 229 + lib/webdriver/test/testutil.js | 209 + lib/webdriver/test/testutil_test.js | 104 + lib/webdriver/test/until_test.js | 411 + .../test/webdriver_generator_test.js | 92 + lib/webdriver/test/webdriver_test.js | 2369 ++++++ lib/webdriver/testing/asserts.js | 32 +- lib/webdriver/testing/client.js | 179 + lib/webdriver/testing/flowtester.js | 372 + lib/webdriver/testing/jsunit.js | 363 + lib/webdriver/testing/testcase.js | 168 + lib/webdriver/testing/window.js | 244 + lib/webdriver/touchsequence.js | 248 + lib/webdriver/until.js | 412 + lib/webdriver/webdriver.js | 1387 ++-- net/index.js | 29 +- net/portprober.js | 32 +- opera.js | 499 ++ package.json | 35 +- phantomjs.js | 159 +- proxy.js | 74 +- remote/index.js | 283 +- safari.js | 538 ++ test/_base_test.js | 129 + test/actions_test.js | 53 + test/chrome/options_test.js | 114 +- test/chrome/service_test.js | 45 + test/cookie_test.js | 42 +- test/element_finding_test.js | 60 +- test/execute_script_test.js | 322 + test/fingerprint_test.js | 57 + test/firefox/extension_test.js | 96 + test/firefox/firefox_test.js | 159 + test/firefox/profile_test.js | 187 + test/http/http_test.js | 132 + test/http/util_test.js | 35 +- test/io_test.js | 227 + test/logging_test.js | 167 + test/net/portprober_test.js | 26 +- test/page_loading_test.js | 78 +- test/phantomjs/execute_phantomjs_test.js | 73 + test/promise_aplus_test.js | 46 + test/proxy_test.js | 67 +- test/remote_test.js | 72 + test/stale_element_test.js | 45 +- test/tag_name_test.js | 35 +- test/testing/index_test.js | 163 +- test/upload_test.js | 85 + test/window_test.js | 67 +- testing/assert.js | 66 +- testing/index.js | 195 +- 736 files changed, 77233 insertions(+), 8030 deletions(-) create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 docs/Changes.html create mode 100644 docs/class_goog_Disposable.html create mode 100644 docs/class_goog_async_run_WorkItem_.html create mode 100644 docs/class_goog_debug_LogBuffer.html create mode 100644 docs/class_goog_debug_LogRecord.html create mode 100644 docs/class_goog_debug_Logger.html create mode 100644 docs/class_goog_debug_Logger_Level.html create mode 100644 docs/class_goog_dom_DomHelper.html create mode 100644 docs/class_goog_events_BrowserEvent.html create mode 100644 docs/class_goog_events_Event.html create mode 100644 docs/class_goog_events_EventId.html create mode 100644 docs/class_goog_events_EventTarget.html create mode 100644 docs/class_goog_events_Listener.html create mode 100644 docs/class_goog_events_ListenerMap.html create mode 100644 docs/class_goog_iter_GroupByIterator_.html create mode 100644 docs/class_goog_math_Box.html create mode 100644 docs/class_goog_math_Coordinate.html create mode 100644 docs/class_goog_math_Rect.html create mode 100644 docs/class_goog_math_Size.html create mode 100644 docs/class_goog_structs_Set.html create mode 100644 docs/class_goog_testing_AsyncTestCase.html create mode 100644 docs/class_goog_testing_AsyncTestCase_ControlBreakingException.html create mode 100644 docs/class_goog_testing_FunctionCall.html create mode 100644 docs/class_goog_testing_JsUnitException.html create mode 100644 docs/class_goog_testing_LooseExpectationCollection.html create mode 100644 docs/class_goog_testing_LooseMock.html create mode 100644 docs/class_goog_testing_Mock.html create mode 100644 docs/class_goog_testing_MockClock.html create mode 100644 docs/class_goog_testing_MockControl.html create mode 100644 docs/class_goog_testing_MockExpectation.html create mode 100644 docs/class_goog_testing_ObjectPropertyString.html create mode 100644 docs/class_goog_testing_PropertyReplacer.html create mode 100644 docs/class_goog_testing_StrictMock.html create mode 100644 docs/class_goog_testing_TestCase.html create mode 100644 docs/class_goog_testing_TestCase_Error.html create mode 100644 docs/class_goog_testing_TestCase_Result.html create mode 100644 docs/class_goog_testing_TestCase_Test.html create mode 100644 docs/class_goog_testing_TestCase_protectedDate_.html create mode 100644 docs/class_goog_testing_TestRunner.html create mode 100644 docs/class_goog_testing_events_Event.html create mode 100644 docs/class_goog_testing_mockmatchers_ArgumentMatcher.html create mode 100644 docs/class_goog_testing_mockmatchers_IgnoreArgument.html create mode 100644 docs/class_goog_testing_mockmatchers_InstanceOf.html create mode 100644 docs/class_goog_testing_mockmatchers_ObjectEquals.html create mode 100644 docs/class_goog_testing_mockmatchers_RegexpMatch.html create mode 100644 docs/class_goog_testing_mockmatchers_SaveArgument.html create mode 100644 docs/class_goog_testing_mockmatchers_TypeOf.html create mode 100644 docs/class_goog_testing_stacktrace_Frame.html create mode 100644 docs/class_webdriver_AlertPromise.html create mode 100644 docs/class_webdriver_FileDetector.html create mode 100644 docs/class_webdriver_Serializable.html create mode 100644 docs/class_webdriver_TouchSequence.html create mode 100644 docs/class_webdriver_WebElementPromise.html create mode 100644 docs/class_webdriver_logging_Level.html create mode 100644 docs/class_webdriver_logging_LogRecord.html create mode 100644 docs/class_webdriver_logging_Logger.html create mode 100644 docs/class_webdriver_logging_Preferences.html create mode 100644 docs/class_webdriver_promise_CancellationError.html create mode 100644 docs/class_webdriver_promise_MultipleUnhandledRejectionError.html create mode 100644 docs/class_webdriver_testing_TestCase.html create mode 100644 docs/class_webdriver_until_Condition.html create mode 100644 docs/enum_goog_Disposable_MonitoringMode.html create mode 100644 docs/enum_goog_dom_BrowserFeature.html create mode 100644 docs/enum_goog_dom_NodeType.html create mode 100644 docs/enum_goog_dom_TagName.html create mode 100644 docs/enum_goog_events_BrowserEvent_MouseButton.html create mode 100644 docs/enum_goog_events_BrowserFeature.html create mode 100644 docs/enum_goog_events_CaptureSimulationMode.html create mode 100644 docs/enum_goog_events_EventType.html create mode 100644 docs/enum_goog_events_KeyCodes.html create mode 100644 docs/enum_goog_testing_TestCase_Order.html create mode 100644 docs/interface_goog_debug_EntryPointMonitor.html create mode 100644 docs/interface_goog_disposable_IDisposable.html create mode 100644 docs/interface_goog_events_Listenable.html create mode 100644 docs/interface_goog_events_ListenableKey.html create mode 100644 docs/interface_goog_net_XhrLike.html create mode 100644 docs/interface_goog_structs_Collection.html create mode 100644 docs/interface_goog_testing_MockInterface.html create mode 100644 docs/interface_webdriver_promise_Thenable.html create mode 100644 docs/module_selenium-webdriver__base_class_Context.html create mode 100644 docs/module_selenium-webdriver_chrome_class_Driver.html create mode 100644 docs/module_selenium-webdriver_class_ActionSequence.html create mode 100644 docs/module_selenium-webdriver_class_Builder.html create mode 100644 docs/module_selenium-webdriver_class_Capabilities.html create mode 100644 docs/module_selenium-webdriver_class_Command.html create mode 100644 docs/module_selenium-webdriver_class_EventEmitter.html create mode 100644 docs/module_selenium-webdriver_class_FileDetector.html create mode 100644 docs/module_selenium-webdriver_class_Serializable.html create mode 100644 docs/module_selenium-webdriver_class_Session.html create mode 100644 docs/module_selenium-webdriver_class_WebDriver.html create mode 100644 docs/module_selenium-webdriver_class_WebElement.html create mode 100644 docs/module_selenium-webdriver_class_WebElementPromise.html create mode 100644 docs/module_selenium-webdriver_enum_Browser.html create mode 100644 docs/module_selenium-webdriver_enum_Button.html create mode 100644 docs/module_selenium-webdriver_enum_Capability.html create mode 100644 docs/module_selenium-webdriver_enum_CommandName.html create mode 100644 docs/module_selenium-webdriver_enum_Key.html create mode 100644 docs/module_selenium-webdriver_error_class_Error.html create mode 100644 docs/module_selenium-webdriver_error_enum_ErrorCode.html create mode 100644 docs/module_selenium-webdriver_executors_class_DeferredExecutor.html create mode 100644 docs/module_selenium-webdriver_firefox.html create mode 100644 docs/module_selenium-webdriver_firefox_binary.html create mode 100644 docs/module_selenium-webdriver_firefox_binary_class_Binary.html create mode 100644 docs/module_selenium-webdriver_firefox_class_Binary.html create mode 100644 docs/module_selenium-webdriver_firefox_class_Driver.html create mode 100644 docs/module_selenium-webdriver_firefox_class_Options.html create mode 100644 docs/module_selenium-webdriver_firefox_class_Profile.html create mode 100644 docs/module_selenium-webdriver_firefox_extension.html create mode 100644 docs/module_selenium-webdriver_firefox_profile.html create mode 100644 docs/module_selenium-webdriver_firefox_profile_class_Profile.html create mode 100644 docs/module_selenium-webdriver_http_class_Executor.html create mode 100644 docs/module_selenium-webdriver_http_class_Request.html create mode 100644 docs/module_selenium-webdriver_http_class_Response.html create mode 100644 docs/module_selenium-webdriver_ie.html create mode 100644 docs/module_selenium-webdriver_ie_class_Driver.html create mode 100644 docs/module_selenium-webdriver_ie_class_Options.html create mode 100644 docs/module_selenium-webdriver_ie_enum_Level.html create mode 100644 docs/module_selenium-webdriver_io_exec.html create mode 100644 docs/module_selenium-webdriver_namespace_By.html create mode 100644 docs/module_selenium-webdriver_namespace_error.html create mode 100644 docs/module_selenium-webdriver_namespace_logging.html create mode 100644 docs/module_selenium-webdriver_namespace_promise.html create mode 100644 docs/module_selenium-webdriver_namespace_stacktrace.html create mode 100644 docs/module_selenium-webdriver_namespace_until.html create mode 100644 docs/module_selenium-webdriver_opera.html create mode 100644 docs/module_selenium-webdriver_opera_class_Driver.html create mode 100644 docs/module_selenium-webdriver_opera_class_Options.html create mode 100644 docs/module_selenium-webdriver_opera_class_ServiceBuilder.html create mode 100644 docs/module_selenium-webdriver_phantomjs_class_Driver.html create mode 100644 docs/module_selenium-webdriver_remote_class_FileDetector.html create mode 100644 docs/module_selenium-webdriver_safari.html create mode 100644 docs/module_selenium-webdriver_safari_class_Driver.html create mode 100644 docs/module_selenium-webdriver_safari_class_Options.html create mode 100644 docs/namespace_PRIMITIVE_EQUALITY_PREDICATES.html create mode 100644 docs/namespace_goog_async.html create mode 100644 docs/namespace_goog_async_nextTick.html create mode 100644 docs/namespace_goog_async_run.html create mode 100644 docs/namespace_goog_debug_LogManager.html create mode 100644 docs/namespace_goog_debug_entryPointRegistry.html create mode 100644 docs/namespace_goog_defineClass.html create mode 100644 docs/namespace_goog_disposable.html create mode 100644 docs/namespace_goog_dom.html create mode 100644 docs/namespace_goog_dom_vendor.html create mode 100644 docs/namespace_goog_events.html create mode 100644 docs/namespace_goog_functions.html create mode 100644 docs/namespace_goog_labs_userAgent.html create mode 100644 docs/namespace_goog_labs_userAgent_browser.html create mode 100644 docs/namespace_goog_labs_userAgent_engine.html create mode 100644 docs/namespace_goog_labs_userAgent_util.html create mode 100644 docs/namespace_goog_math.html create mode 100644 docs/namespace_goog_net_XmlHttpDefines.html create mode 100644 docs/namespace_goog_reflect.html create mode 100644 docs/namespace_goog_style.html create mode 100644 docs/namespace_goog_testing.html create mode 100644 docs/namespace_goog_testing_MethodMock.html create mode 100644 docs/namespace_goog_testing_asserts.html create mode 100644 docs/namespace_goog_testing_events.html create mode 100644 docs/namespace_goog_testing_jsunit.html create mode 100644 docs/namespace_goog_testing_mockmatchers.html create mode 100644 docs/namespace_goog_testing_stacktrace.html create mode 100644 docs/namespace_goog_testing_watchers.html create mode 100644 docs/namespace_webdriver_until.html create mode 100644 docs/source/firefox/binary.js.src.html create mode 100644 docs/source/firefox/extension.js.src.html create mode 100644 docs/source/firefox/index.js.src.html create mode 100644 docs/source/firefox/profile.js.src.html create mode 100644 docs/source/ie.js.src.html create mode 100644 docs/source/io/exec.js.src.html create mode 100644 docs/source/lib/goog/async/freelist.js.src.html create mode 100644 docs/source/lib/goog/async/nexttick.js.src.html create mode 100644 docs/source/lib/goog/async/run.js.src.html create mode 100644 docs/source/lib/goog/async/workqueue.js.src.html create mode 100644 docs/source/lib/goog/debug/debug.js.src.html create mode 100644 docs/source/lib/goog/debug/entrypointregistry.js.src.html create mode 100644 docs/source/lib/goog/debug/logbuffer.js.src.html create mode 100644 docs/source/lib/goog/debug/logger.js.src.html create mode 100644 docs/source/lib/goog/debug/logrecord.js.src.html create mode 100644 docs/source/lib/goog/disposable/disposable.js.src.html create mode 100644 docs/source/lib/goog/disposable/idisposable.js.src.html create mode 100644 docs/source/lib/goog/dom/browserfeature.js.src.html create mode 100644 docs/source/lib/goog/dom/dom.js.src.html create mode 100644 docs/source/lib/goog/dom/nodetype.js.src.html create mode 100644 docs/source/lib/goog/dom/safe.js.src.html create mode 100644 docs/source/lib/goog/dom/tagname.js.src.html create mode 100644 docs/source/lib/goog/dom/tags.js.src.html create mode 100644 docs/source/lib/goog/dom/vendor.js.src.html create mode 100644 docs/source/lib/goog/events/browserevent.js.src.html create mode 100644 docs/source/lib/goog/events/browserfeature.js.src.html create mode 100644 docs/source/lib/goog/events/event.js.src.html create mode 100644 docs/source/lib/goog/events/eventid.js.src.html create mode 100644 docs/source/lib/goog/events/events.js.src.html create mode 100644 docs/source/lib/goog/events/eventtarget.js.src.html create mode 100644 docs/source/lib/goog/events/eventtype.js.src.html create mode 100644 docs/source/lib/goog/events/keycodes.js.src.html create mode 100644 docs/source/lib/goog/events/listenable.js.src.html create mode 100644 docs/source/lib/goog/events/listener.js.src.html create mode 100644 docs/source/lib/goog/events/listenermap.js.src.html create mode 100644 docs/source/lib/goog/fs/url.js.src.html create mode 100644 docs/source/lib/goog/functions/functions.js.src.html create mode 100644 docs/source/lib/goog/html/safehtml.js.src.html create mode 100644 docs/source/lib/goog/html/safescript.js.src.html create mode 100644 docs/source/lib/goog/html/safestyle.js.src.html create mode 100644 docs/source/lib/goog/html/safestylesheet.js.src.html create mode 100644 docs/source/lib/goog/html/safeurl.js.src.html create mode 100644 docs/source/lib/goog/html/trustedresourceurl.js.src.html create mode 100644 docs/source/lib/goog/html/uncheckedconversions.js.src.html create mode 100644 docs/source/lib/goog/i18n/bidi.js.src.html create mode 100644 docs/source/lib/goog/labs/useragent/browser.js.src.html create mode 100644 docs/source/lib/goog/labs/useragent/engine.js.src.html create mode 100644 docs/source/lib/goog/labs/useragent/platform.js.src.html create mode 100644 docs/source/lib/goog/labs/useragent/util.js.src.html create mode 100644 docs/source/lib/goog/log/log.js.src.html create mode 100644 docs/source/lib/goog/math/box.js.src.html create mode 100644 docs/source/lib/goog/math/coordinate.js.src.html create mode 100644 docs/source/lib/goog/math/math.js.src.html create mode 100644 docs/source/lib/goog/math/rect.js.src.html create mode 100644 docs/source/lib/goog/math/size.js.src.html create mode 100644 docs/source/lib/goog/net/xhrlike.js.src.html create mode 100644 docs/source/lib/goog/promise/promise.js.src.html create mode 100644 docs/source/lib/goog/promise/resolver.js.src.html create mode 100644 docs/source/lib/goog/promise/thenable.js.src.html create mode 100644 docs/source/lib/goog/reflect/reflect.js.src.html create mode 100644 docs/source/lib/goog/string/const.js.src.html create mode 100644 docs/source/lib/goog/string/typedstring.js.src.html create mode 100644 docs/source/lib/goog/structs/collection.js.src.html create mode 100644 docs/source/lib/goog/structs/set.js.src.html create mode 100644 docs/source/lib/goog/style/style.js.src.html create mode 100644 docs/source/lib/goog/testing/asserts.js.src.html create mode 100644 docs/source/lib/goog/testing/asynctestcase.js.src.html create mode 100644 docs/source/lib/goog/testing/events/events.js.src.html create mode 100644 docs/source/lib/goog/testing/functionmock.js.src.html create mode 100644 docs/source/lib/goog/testing/jsunit.js.src.html create mode 100644 docs/source/lib/goog/testing/loosemock.js.src.html create mode 100644 docs/source/lib/goog/testing/mock.js.src.html create mode 100644 docs/source/lib/goog/testing/mockclock.js.src.html create mode 100644 docs/source/lib/goog/testing/mockcontrol.js.src.html create mode 100644 docs/source/lib/goog/testing/mockinterface.js.src.html create mode 100644 docs/source/lib/goog/testing/mockmatchers.js.src.html create mode 100644 docs/source/lib/goog/testing/objectpropertystring.js.src.html create mode 100644 docs/source/lib/goog/testing/propertyreplacer.js.src.html create mode 100644 docs/source/lib/goog/testing/recordfunction.js.src.html create mode 100644 docs/source/lib/goog/testing/stacktrace.js.src.html create mode 100644 docs/source/lib/goog/testing/strictmock.js.src.html create mode 100644 docs/source/lib/goog/testing/testcase.js.src.html create mode 100644 docs/source/lib/goog/testing/testrunner.js.src.html create mode 100644 docs/source/lib/goog/testing/watchers.js.src.html create mode 100644 docs/source/lib/webdriver/serializable.js.src.html create mode 100644 docs/source/lib/webdriver/testing/testcase.js.src.html create mode 100644 docs/source/lib/webdriver/touchsequence.js.src.html create mode 100644 docs/source/lib/webdriver/until.js.src.html create mode 100644 docs/source/opera.js.src.html create mode 100644 docs/source/safari.js.src.html create mode 100644 example/chrome_android.js create mode 100644 example/chrome_mobile_emulation.js create mode 100644 example/google_search_generator.js create mode 100644 example/logging.js create mode 100644 example/parallel_flows.js create mode 100644 firefox/binary.js create mode 100644 firefox/extension.js create mode 100644 firefox/index.js create mode 100644 firefox/profile.js create mode 100644 ie.js create mode 100644 io/exec.js create mode 100644 lib/firefox/amd64/libnoblur64.so create mode 100644 lib/firefox/i386/libnoblur.so create mode 100644 lib/firefox/webdriver.json create mode 100644 lib/firefox/webdriver.xpi create mode 100644 lib/goog/async/freelist.js create mode 100644 lib/goog/async/nexttick.js create mode 100644 lib/goog/async/run.js create mode 100644 lib/goog/async/workqueue.js create mode 100644 lib/goog/debug/debug.js create mode 100644 lib/goog/debug/entrypointregistry.js create mode 100644 lib/goog/debug/logbuffer.js create mode 100644 lib/goog/debug/logger.js create mode 100644 lib/goog/debug/logrecord.js create mode 100644 lib/goog/disposable/disposable.js create mode 100644 lib/goog/disposable/idisposable.js create mode 100644 lib/goog/dom/browserfeature.js create mode 100644 lib/goog/dom/dom.js create mode 100644 lib/goog/dom/nodetype.js create mode 100644 lib/goog/dom/safe.js create mode 100644 lib/goog/dom/tagname.js create mode 100644 lib/goog/dom/tags.js create mode 100644 lib/goog/dom/vendor.js create mode 100644 lib/goog/events/browserevent.js create mode 100644 lib/goog/events/browserfeature.js create mode 100644 lib/goog/events/event.js create mode 100644 lib/goog/events/eventid.js create mode 100644 lib/goog/events/events.js create mode 100644 lib/goog/events/eventtarget.js create mode 100644 lib/goog/events/eventtype.js create mode 100644 lib/goog/events/keycodes.js create mode 100644 lib/goog/events/listenable.js create mode 100644 lib/goog/events/listener.js create mode 100644 lib/goog/events/listenermap.js create mode 100644 lib/goog/fs/url.js create mode 100644 lib/goog/functions/functions.js create mode 100644 lib/goog/html/safehtml.js create mode 100644 lib/goog/html/safescript.js create mode 100644 lib/goog/html/safestyle.js create mode 100644 lib/goog/html/safestylesheet.js create mode 100644 lib/goog/html/safeurl.js create mode 100644 lib/goog/html/trustedresourceurl.js create mode 100644 lib/goog/html/uncheckedconversions.js create mode 100644 lib/goog/i18n/bidi.js create mode 100644 lib/goog/labs/useragent/browser.js create mode 100644 lib/goog/labs/useragent/engine.js create mode 100644 lib/goog/labs/useragent/platform.js create mode 100644 lib/goog/labs/useragent/util.js create mode 100644 lib/goog/log/log.js create mode 100644 lib/goog/math/box.js create mode 100644 lib/goog/math/coordinate.js create mode 100644 lib/goog/math/math.js create mode 100644 lib/goog/math/rect.js create mode 100644 lib/goog/math/size.js create mode 100644 lib/goog/net/xhrlike.js create mode 100644 lib/goog/promise/promise.js create mode 100644 lib/goog/promise/resolver.js create mode 100644 lib/goog/promise/thenable.js create mode 100644 lib/goog/reflect/reflect.js create mode 100644 lib/goog/string/const.js create mode 100644 lib/goog/string/typedstring.js create mode 100644 lib/goog/structs/collection.js create mode 100644 lib/goog/structs/set.js create mode 100644 lib/goog/style/style.js create mode 100644 lib/goog/testing/asserts.js create mode 100644 lib/goog/testing/asynctestcase.js create mode 100644 lib/goog/testing/events/events.js create mode 100644 lib/goog/testing/functionmock.js create mode 100644 lib/goog/testing/jsunit.js create mode 100644 lib/goog/testing/loosemock.js create mode 100644 lib/goog/testing/mock.js create mode 100644 lib/goog/testing/mockclock.js create mode 100644 lib/goog/testing/mockcontrol.js create mode 100644 lib/goog/testing/mockinterface.js create mode 100644 lib/goog/testing/mockmatchers.js create mode 100644 lib/goog/testing/objectpropertystring.js create mode 100644 lib/goog/testing/propertyreplacer.js create mode 100644 lib/goog/testing/recordfunction.js create mode 100644 lib/goog/testing/stacktrace.js create mode 100644 lib/goog/testing/strictmock.js create mode 100644 lib/goog/testing/testcase.js create mode 100644 lib/goog/testing/testrunner.js create mode 100644 lib/goog/testing/watchers.js create mode 100644 lib/safari/client.js create mode 100644 lib/test/data/click_tests/click_iframe.html create mode 100644 lib/test/data/click_tests/click_in_iframe.html create mode 100644 lib/test/data/click_tests/disappearing_element.html create mode 100644 lib/test/data/click_tests/overlapping_elements.html create mode 100644 lib/test/data/firefox/jetpack-sample.xpi create mode 100644 lib/test/data/firefox/sample.xpi create mode 100644 lib/test/data/frameWithAnimals.html create mode 100644 lib/test/data/js/skins/lightgray/content.inline.min.css create mode 100644 lib/test/data/js/skins/lightgray/content.min.css create mode 100644 lib/test/data/js/skins/lightgray/fonts/readme.md create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce-small.dev.svg create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce-small.eot create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce-small.svg create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce-small.ttf create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce-small.woff create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce.dev.svg create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce.eot create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce.svg create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce.ttf create mode 100644 lib/test/data/js/skins/lightgray/fonts/tinymce.woff create mode 100644 lib/test/data/js/skins/lightgray/img/anchor.gif create mode 100644 lib/test/data/js/skins/lightgray/img/loader.gif create mode 100644 lib/test/data/js/skins/lightgray/img/object.gif create mode 100644 lib/test/data/js/skins/lightgray/img/trans.gif create mode 100644 lib/test/data/js/skins/lightgray/skin.ie7.min.css create mode 100644 lib/test/data/js/skins/lightgray/skin.min.css create mode 100644 lib/test/data/js/themes/modern/theme.min.js create mode 100644 lib/test/data/js/tinymce.min.js create mode 100644 lib/test/data/tinymce.html create mode 100644 lib/test/data/transparentUpload.html create mode 100644 lib/webdriver/serializable.js create mode 100644 lib/webdriver/test/builder_test.js create mode 100644 lib/webdriver/test/capabilities_test.js create mode 100644 lib/webdriver/test/events_test.js create mode 100644 lib/webdriver/test/http/corsclient_test.js create mode 100644 lib/webdriver/test/http/http_test.js create mode 100644 lib/webdriver/test/http/xhrclient_test.js create mode 100644 lib/webdriver/test/locators_test.js create mode 100644 lib/webdriver/test/logging_test.js create mode 100644 lib/webdriver/test/process_test.js create mode 100644 lib/webdriver/test/promise_error_test.js create mode 100644 lib/webdriver/test/promise_flow_test.js create mode 100644 lib/webdriver/test/promise_generator_test.js create mode 100644 lib/webdriver/test/promise_test.js create mode 100644 lib/webdriver/test/stacktrace_test.js create mode 100644 lib/webdriver/test/test_bootstrap.js create mode 100644 lib/webdriver/test/testing/asserts_test.js create mode 100644 lib/webdriver/test/testing/client_test.js create mode 100644 lib/webdriver/test/testing/flowtester_test.js create mode 100644 lib/webdriver/test/testing/testcase_test.js create mode 100644 lib/webdriver/test/testutil.js create mode 100644 lib/webdriver/test/testutil_test.js create mode 100644 lib/webdriver/test/until_test.js create mode 100644 lib/webdriver/test/webdriver_generator_test.js create mode 100644 lib/webdriver/test/webdriver_test.js create mode 100644 lib/webdriver/testing/client.js create mode 100644 lib/webdriver/testing/flowtester.js create mode 100644 lib/webdriver/testing/jsunit.js create mode 100644 lib/webdriver/testing/testcase.js create mode 100644 lib/webdriver/testing/window.js create mode 100644 lib/webdriver/touchsequence.js create mode 100644 lib/webdriver/until.js create mode 100644 opera.js create mode 100644 safari.js create mode 100644 test/_base_test.js create mode 100644 test/actions_test.js create mode 100644 test/chrome/service_test.js create mode 100644 test/execute_script_test.js create mode 100644 test/fingerprint_test.js create mode 100644 test/firefox/extension_test.js create mode 100644 test/firefox/firefox_test.js create mode 100644 test/firefox/profile_test.js create mode 100644 test/http/http_test.js create mode 100644 test/io_test.js create mode 100644 test/logging_test.js create mode 100644 test/phantomjs/execute_phantomjs_test.js create mode 100644 test/promise_aplus_test.js create mode 100644 test/remote_test.js create mode 100644 test/upload_test.js diff --git a/CHANGES.md b/CHANGES.md index 874f0e5..908aa67 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,244 @@ +## v2.47.0 + +### Notice + +This is the last release for `selenium-webdriver` that will support ES5. +Subsequent releases will depend on ES6 features that are enabled by +[default](https://nodejs.org/en/docs/es6/) in Node v4.0.0. Node v0.12.x will +continue to be supported, but will require setting the `--harmony` flag. + +### Change Summary + +* Add support for [Node v4.0.0](https://nodejs.org/en/blog/release/v4.0.0/) + * Updated `ws` dependency from `0.7.1` to `0.8.0` +* Bumped the minimum supported version of Node from `0.10.x` to `0.12.x`. This + is in accordance with the Node support policy established in `v2.45.0`. + +## v2.46.1 + +* Fixed internal module loading on Windows. +* Fixed error message format on timeouts for `until.elementLocated()` + and `until.elementsLocated()`. + +## v2.46.0 + +* Exposed a new logging API via the `webdriver.logging` module. For usage, see + `example/logging.js`. +* Added support for using a proxy server for WebDriver commands. + See `Builder#usingWebDriverProxy()` for more info. +* Removed deprecated functions: + * Capabilities#toJSON() + * UnhandledAlertError#getAlert() + * chrome.createDriver() + * phantomjs.createDriver() + * promise.ControlFlow#annotateError() + * promise.ControlFlow#await() + * promise.ControlFlow#clearHistory() + * promise.ControlFlow#getHistory() +* Removed deprecated enum values: `ErrorCode.NO_MODAL_DIALOG_OPEN` and + `ErrorCode.MODAL_DIALOG_OPENED`. Use `ErrorCode.NO_SUCH_ALERT` and + `ErrorCode.UNEXPECTED_ALERT_OPEN`, respectively. +* FIXED: The `promise.ControlFlow` will maintain state for promise chains + generated in a loop. +* FIXED: Correct serialize target elements used in an action sequence. +* FIXED: `promise.ControlFlow#wait()` now has consistent semantics for an + omitted or 0-timeout: it will wait indefinitely. +* FIXED: `remote.DriverService#start()` will now fail if the child process dies + while waiting for the server to start accepting requests. Previously, start + would continue to poll the server address until the timeout expired. +* FIXED: Skip launching Firefox with the `-silent` flag to preheat the profile. + Starting with Firefox 38, this would cause the browser to crash. This step, + which was first introduced for Selenium's java client back with Firefox 2, + no longer appears to be required. +* FIXED: 8564: `firefox.Driver#quit()` will wait for the Firefox process to + terminate before deleting the temporary webdriver profile. This eliminates a + race condition where Firefox would write profile data during shutdown, + causing the `rm -rf` operation on the profile directory to fail. + +## v2.45.1 + +* FIXED: 8548: Task callbacks are once again dropped if the task was cancelled + due to a previously uncaught error within the frame. +* FIXED: 8496: Extended the `chrome.Options` API to cover all configuration + options (e.g. mobile emulation and performance logging) documented on the + ChromeDriver [project site](https://sites.google.com/a/chromium.org/chromedriver/capabilities). + +## v2.45.0 + +### Important Policy Change + +Starting with the 2.45.0 release, selenium-webdriver will support the last +two stable minor releases for Node. For 2.45.0, this means Selenium will +support Node 0.10.x and 0.12.x. Support for the intermediate, un-stable release +(0.11.x) is "best-effort". This policy will be re-evaluated once Node has a +major version release (i.e. 1.0.0). + +### Change Summary + +* Added native browser support for Internet Explorer, Opera 26+, and Safari +* With the release of [Node 0.12.0](http://blog.nodejs.org/2015/02/06/node-v0-12-0-stable/) + (finally!), the minimum supported version of Node is now `0.10.x`. +* The `promise` module is now [Promises/A+](https://promisesaplus.com/) + compliant. The biggest compliance change is that promise callbacks are now + invoked in a future turn of the JS event loop. For example: + + var promise = require('selenium-webdriver').promise; + console.log('start'); + promise.fulfilled().then(function() { + console.log('middle'); + }); + console.log('end'); + + // Output in selenium-webdriver@2.44.0 + // start + // middle + // end + // + // Output in selenium-webdriver@2.45.0 + // start + // end + // middle + + The `promise.ControlFlow` class has been updated to track the asynchronous + breaks required by Promises/A+, so there are no changes to task execution + order. +* Updated how errors are annotated on failures. When a task fails, the + stacktrace from when that task was scheduled is appended to the rejection + reason with a `From: ` prefix (if it is an Error object). For example: + + var driver = new webdriver.Builder().forBrowser('chrome').build(); + driver.get('http://www.google.com/ncr'); + driver.call(function() { + driver.wait(function() { + return driver.isElementPresent(webdriver.By.id('not-there')); + }, 2000, 'element not found'); + }); + + This code will fail an error like: + + Error: element not found + Wait timed out after 2002ms + at + From: Task: element not found + at + From: Task: WebDriver.call(function) + at + +* Changed the format of strings returned by `promise.ControlFlow#getSchedule`. + This function now accepts a boolean to control whether the returned string + should include the stacktraces for when each task was scheduled. +* Deprecating `promise.ControlFlow#getHistory`, + `promise.ControlFlow#clearHistory`, and `promise.ControlFlow#annotateError`. + These functions were all intended for internal use and are no longer + necessary, so they have been made no-ops. +* `WebDriver.wait()` may now be used to wait for a promise to resolve, with + an optional timeout. Refer to the API documentation for more information. +* Added support for copying files to a remote Selenium via `sendKeys` to test + file uploads. Refer to the API documentation for more information. Sample + usage included in `test/upload_test.js` +* Expanded the interactions API to include touch actions. + See `WebDriver.touchActions()`. +* FIXED: 8380: `firefox.Driver` will delete its temporary profile on `quit`. +* FIXED: 8306: Stack overflow in promise callbacks eliminated. +* FIXED: 8221: Added support for defining custom command mappings. Includes + support for PhantomJS's `executePhantomJS` (requires PhantomJS 1.9.7 or + GhostDriver 1.1.0). +* FIXED: 8128: When the FirefoxDriver marshals an object to the page for + `executeScript`, it defines additional properties (required by the driver's + implementation). These properties will no longer be enumerable and should + be omitted (i.e. they won't show up in JSON.stringify output). +* FIXED: 8094: The control flow will no longer deadlock when a task returns + a promise that depends on the completion of sub-tasks. + +## v2.44.0 + +* Added the `until` module, which defines common explicit wait conditions. + Sample usage: + + var firefox = require('selenium-webdriver/firefox'), + until = require('selenium-webdriver/until'); + + var driver = new firefox.Driver(); + driver.get('http://www.google.com/ncr'); + driver.wait(until.titleIs('Google Search'), 1000); + +* FIXED: 8000: `Builder.forBrowser()` now accepts an empty string since some + WebDriver implementations ignore the value. A value must still be specified, + however, since it is a required field in WebDriver's wire protocol. +* FIXED: 7994: The `stacktrace` module will not modify stack traces if the + initial parse fails (e.g. the user defined `Error.prepareStackTrace`) +* FIXED: 5855: Added a module (`until`) that defines several common conditions + for use with explicit waits. See updated examples for usage. + +## v2.43.5 + +* FIXED: 7905: `Builder.usingServer(url)` once again returns `this` for + chaining. + +## v2.43.2-4 + +* No changes; version bumps while attempting to work around an issue with + publishing to npm (a version string may only be used once). + +## v2.43.1 + +* Fixed an issue with flakiness when setting up the Firefox profile that could + prevent the driver from initializing properly. + +## v2.43.0 + +* Added native support for Firefox - the Java Selenium server is no longer + required. +* Added support for generator functions to `ControlFlow#execute` and + `ControlFlow#wait`. For more information, see documentation on + `webdriver.promise.consume`. Requires harmony support (run with + `node --harmony-generators` in `v0.11.x`). +* Various improvements to the `Builder` API. Notably, the `build()` function + will no longer default to attempting to use a server at + `http://localhost:4444/wd/hub` if it cannot start a browser directly - + you must specify the WebDriver server with `usingServer(url)`. You can + also set the target browser and WebDriver server through a pair of + environment variables. See the documentation on the `Builder` constructor + for more information. +* For consistency with the other language bindings, added browser specific + classes that can be used to start a browser without the builder. + + var webdriver = require('selenium-webdriver') + chrome = require('selenium-webdriver/chrome'); + + // The following are equivalent. + var driver1 = new webdriver.Builder().forBrowser('chrome').build(); + var driver2 = new chrome.Driver(); + +* Promise A+ compliance: a promise may no longer resolve to itself. +* For consistency with other language bindings, deprecated + `UnhandledAlertError#getAlert` and added `#getAlertText`. + `getAlert` will be removed in `2.45.0`. +* FIXED: 7641: Deprecated `ErrorCode.NO_MODAL_DIALOG_OPEN` and + `ErrorCode.MODAL_DIALOG_OPENED` in favor of the new + `ErrorCode.NO_SUCH_ALERT` and `ErrorCode.UNEXPECTED_ALERT_OPEN`, + respectively. +* FIXED: 7563: Mocha integration no longer disables timeouts. Default Mocha + timeouts apply (2000 ms) and may be changed using `this.timeout(ms)`. +* FIXED: 7470: Make it easier to create WebDriver instances in custom flows for + parallel execution. + +## v2.42.1 + +* FIXED: 7465: Fixed `net.getLoopbackAddress` on Windows +* FIXED: 7277: Support `done` callback in Mocha's BDD interface +* FIXED: 7156: `Promise#thenFinally` should not suppress original error + +## v2.42.0 + +* Removed deprecated functions `Promise#addCallback()`, + `Promise#addCallbacks()`, `Promise#addErrback()`, and `Promise#addBoth()`. +* Fail with a more descriptive error if the server returns a malformed redirect +* FIXED: 7300: Connect to ChromeDriver using the loopback address since + ChromeDriver 2.10.267517 binds to localhost by default. +* FIXED: 7339: Preserve wrapped test function's string representation for + Mocha's BDD interface. + ## v2.41.0 * FIXED: 7138: export logging API from webdriver module. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..38f6e61 --- /dev/null +++ b/NOTICE @@ -0,0 +1,2 @@ +Copyright 2011-2015 Software Freedom Conservancy +Copyright 2004-2011 Selenium committers diff --git a/README.md b/README.md index c31e4de..db55a3e 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,101 @@ -# browserstack-webdriver +# selenium-webdriver + +Selenium is a browser automation library. Most often used for testing +web-applications, Selenium may be used for any task that requires automating +interaction with the browser. ## Installation -Install the latest published version using `npm`: +Install via npm with npm install browserstack-webdriver -In addition to the npm package, you will to download the WebDriver -implementations you wish to utilize. As of 2.34.0, `browserstack-webdriver` -natively supports the [ChromeDriver](http://chromedriver.storage.googleapis.com/index.html). -Simply download a copy and make sure it can be found on your `PATH`. The other -drivers (e.g. Firefox, Internet Explorer, and Safari), still require the -[standalone Selenium server](http://selenium-release.storage.googleapis.com/index.html). - -### Running the tests - -To run the tests, you will need to download a copy of the -[ChromeDriver](http://chromedriver.storage.googleapis.com/index.html) and make -sure it can be found on your `PATH`. - - npm test browserstack-webdriver - -To run the tests against multiple browsers, download the -[Selenium server](http://selenium-release.storage.googleapis.com/index.html) and -specify its location through the `SELENIUM_SERVER_JAR` environment variable. -You can use the `SELENIUM_BROWSER` environment variable to define a -comma-separated list of browsers you wish to test against. For example: - - export SELENIUM_SERVER_JAR=path/to/selenium-server-standalone-2.33.0.jar - SELENIUM_BROWSER=chrome,firefox npm test browserstack-webdriver - ## Usage +The sample below and others are included in the `example` directory. You may +also find the tests for browserstack-webdriver informative. - var webdriver = require('browserstack-webdriver'); + var webdriver = require('browserstack-webdriver'); - var driver = new webdriver.Builder(). - withCapabilities(webdriver.Capabilities.chrome()). + // Input capabilities + var capabilities = { + 'browser' : 'firefox', + 'browserstack.user' : BROWSERSTACK_USERNAME, + 'browserstack.key' : BROWSERSTACK_KEY + } + + var driver = new webdriver.Builder(). + usingServer('http://hub.browserstack.com/wd/hub'). + withCapabilities(capabilities). build(); - driver.get('http://www.google.com'); - driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); - driver.findElement(webdriver.By.name('btnG')).click(); - driver.wait(function() { - return driver.getTitle().then(function(title) { - return title === 'webdriver - Google Search'; - }); - }, 1000); + driver.get('http://www.google.com/ncr'); + driver.findElement(webdriver.By.name('q')).sendKeys('BrowserStack'); + driver.findElement(webdriver.By.name('btnG')).click(); + + driver.getTitle().then(function(title) { + console.log(title); + }); - driver.quit(); + driver.quit(); ## Documentation -Full documentation is available on the [Selenium project wiki](http://code.google.com/p/selenium/wiki/WebDriverJs "User guide"). +API documentation is included in the `docs` directory and is also available +online from the [Selenium project][api]. Addition resources include + +- the #selenium channel on freenode IRC +- the [selenium-users@googlegroups.com][users] list +- [SeleniumHQ](http://www.seleniumhq.org/docs/) documentation ## Issues -Please report any issues using the [Selenium issue tracker](https://github.com/browserstack/selenium-webdriver-nodejs/issues). +Please report any issues using the [Selenium issue tracker][issues]. When using +the issue tracker + +- __Do__ include a detailed description of the problem. +- __Do__ include a link to a [gist](http://gist.github.com/) with any + interesting stack traces/logs (you may also attach these directly to the bug + report). +- __Do__ include a [reduced test case][reduction]. Reporting "unable to find + element on the page" is _not_ a valid report - there's nothing for us to + look into. Expect your bug report to be closed if you do not provide enough + information for us to investigate. +- __Do not__ use the issue tracker to submit basic help requests. All help + inquiries should be directed to the [user forum][users] or #selenium IRC + channel. +- __Do not__ post empty "I see this too" or "Any updates?" comments. These + provide no additional information and clutter the log. +- __Do not__ report regressions on closed bugs as they are not actively + monitored for upates (especially bugs that are >6 months old). Please open a + new issue and reference the original bug in your report. ## License -Copyright 2009-2014 Software Freedom Conservancy - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Licensed to the Software Freedom Conservancy (SFC) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The SFC licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +[api]: http://selenium.googlecode.com/git/docs/api/javascript/index.html +[cla]: http://goo.gl/qC50R +[chrome]: http://chromedriver.storage.googleapis.com/index.html +[gh]: https://github.com/SeleniumHQ/selenium/ +[issues]: https://github.com/SeleniumHQ/selenium/issues +[opera]: https://github.com/operasoftware/operachromiumdriver/releases +[phantomjs]: http://phantomjs.org/ +[reduction]: http://www.webkit.org/quality/reduction.html +[release]: http://selenium-release.storage.googleapis.com/index.html +[users]: https://groups.google.com/forum/#!forum/selenium-users diff --git a/_base.js b/_base.js index 6048179..6769c3c 100644 --- a/_base.js +++ b/_base.js @@ -1,17 +1,19 @@ -// Copyright 2012 Selenium committers -// Copyright 2012 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview The base module responsible for bootstrapping the Closure @@ -74,46 +76,78 @@ var DEPS_FILE_PATH = (function() { })(); - /** - * Synchronously loads a script into the protected Closure context. - * @param {string} src Path to the file to load. + * Maintains a unique context for Closure library-based code. + * @param {boolean=} opt_configureForTesting Whether to configure a fake DOM + * for Closure-testing code that (incorrectly) assumes a DOM is always + * present. + * @constructor */ -function loadScript(src) { - src = path.normalize(src); - var contents = fs.readFileSync(src, 'utf8'); - vm.runInContext(contents, closure, src); +function Context(opt_configureForTesting) { + var closure = this.closure = vm.createContext({ + console: console, + setTimeout: setTimeout, + setInterval: setInterval, + clearTimeout: clearTimeout, + clearInterval: clearInterval, + process: process, + require: require, + Buffer: Buffer, + Error: Error, + TypeError: TypeError, + CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', + CLOSURE_IMPORT_SCRIPT: function(src, opt_srcText) { + if (opt_srcText !== undefined) { + // Windows paths use backslashes, which must be properly escaped before + // evaluated with vm.runInContext. + opt_srcText = opt_srcText.replace(/\\/g, '/'); + vm.runInContext(opt_srcText, closure, src); + } else { + loadScript(src); + } + return true; + }, + CLOSURE_NO_DEPS: !isDevMode(), + CLOSURE_UNCOMPILED_DEFINES: {'goog.json.USE_NATIVE_JSON': true}, + goog: {} + }); + closure.window = closure.top = closure; + + if (opt_configureForTesting) { + closure.document = { + body: {}, + createElement: function() { return {}; }, + getElementsByTagName: function() { return []; } + }; + closure.document.body.ownerDocument = closure.document; + } + + loadScript(CLOSURE_BASE_FILE_PATH); + loadScript(DEPS_FILE_PATH); + + // Redefine retrieveAndExecModule_ to load modules. Closure's version + // assumes XMLHttpRequest is defined (and by extension that scripts + // are being loaded from a server). + closure.goog.retrieveAndExecModule_ = function(src) { + var normalizedSrc = path.normalize(src); + var contents = fs.readFileSync(normalizedSrc, 'utf8'); + contents = closure.goog.wrapModule_(src, contents); + vm.runInContext(contents, closure, normalizedSrc); + }; + + /** + * Synchronously loads a script into the protected Closure context. + * @param {string} src Path to the file to load. + */ + function loadScript(src) { + src = path.normalize(src); + var contents = fs.readFileSync(src, 'utf8'); + vm.runInContext(contents, closure, src); + } } -/** - * The protected context to host the Closure library. - * @type {!Object} - * @const - */ -var closure = vm.createContext({ - console: console, - setTimeout: setTimeout, - setInterval: setInterval, - clearTimeout: clearTimeout, - clearInterval: clearInterval, - process: process, - require: require, - Buffer: Buffer, - Error: Error, - CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/', - CLOSURE_IMPORT_SCRIPT: function(src) { - loadScript(src); - return true; - }, - CLOSURE_NO_DEPS: !isDevMode(), - goog: {} -}); -closure.window = closure; - - -loadScript(CLOSURE_BASE_FILE_PATH); -loadScript(DEPS_FILE_PATH); +var context = new Context(); /** @@ -123,8 +157,8 @@ loadScript(DEPS_FILE_PATH); * @throws {Error} If the symbol has not been defined. */ function closureRequire(symbol) { - closure.goog.require(symbol); - return closure.goog.getObjectByName(symbol); + context.closure.goog.require(symbol); + return context.closure.goog.getObjectByName(symbol); } @@ -159,7 +193,8 @@ exports.exportPublicApi = function(symbol) { if (isDevMode()) { - exports.closure = closure; + exports.closure = context.closure; } +exports.Context = Context; exports.isDevMode = isDevMode; exports.require = closureRequire; diff --git a/builder.js b/builder.js index fc2f40b..3a4cd46 100644 --- a/builder.js +++ b/builder.js @@ -1,109 +1,492 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. var base = require('./_base'), executors = require('./executors'); -var goog = base.require('goog'), - AbstractBuilder = base.require('webdriver.AbstractBuilder'), - Browser = base.require('webdriver.Browser'), +// Use base.require to avoid circular references between index and this module. +var Browser = base.require('webdriver.Browser'), + Capabilities = base.require('webdriver.Capabilities'), Capability = base.require('webdriver.Capability'), WebDriver = base.require('webdriver.WebDriver'), promise = base.require('webdriver.promise'); -/** - * @param {!webdriver.Capabilities} capabilities The desired capabilities. - * @return {webdriver.WebDriver} A new WebDriver instance or {@code null} - * if the requested browser is not natively supported in Node. - */ -function createNativeDriver(capabilities) { - switch (capabilities.get(Capability.BROWSER_NAME)) { - case Browser.CHROME: - // Requiring 'chrome' above would create a cycle: - // index -> builder -> chrome -> index - var chrome = require('./chrome'); - return chrome.createDriver(capabilities); - case Browser.PHANTOM_JS: - // Requiring 'phantomjs' would create a cycle: - // index -> builder -> phantomjs -> index - var phantomjs = require('./phantomjs'); - return phantomjs.createDriver(capabilities); +var seleniumServer; - default: - return null; +/** + * Starts an instance of the Selenium server if not yet running. + * @param {string} jar Path to the server jar to use. + * @return {!webdriver.promise.Promise} A promise for the server's + * addrss once started. + */ +function startSeleniumServer(jar) { + if (!seleniumServer) { + // Requiring 'chrome' above would create a cycle: + // index -> builder -> chrome -> index + var remote = require('./remote'); + seleniumServer = new remote.SeleniumServer(jar); } + return seleniumServer.start(); } - /** - * Creates new {@link webdriver.WebDriver WebDriver} instances. + * Creates new {@link webdriver.WebDriver WebDriver} instances. The environment + * variables listed below may be used to override a builder's configuration, + * allowing quick runtime changes. + * + * - {@code SELENIUM_BROWSER}: defines the target browser in the form + * {@code browser[:version][:platform]}. + * + * - {@code SELENIUM_REMOTE_URL}: defines the remote URL for all builder + * instances. This environment variable should be set to a fully qualified + * URL for a WebDriver server (e.g. http://localhost:4444/wd/hub). This + * option always takes precedence over {@code SELENIUM_SERVER_JAR}. + * + * - {@code SELENIUM_SERVER_JAR}: defines the path to the + * + * standalone Selenium server jar to use. The server will be started the + * first time a WebDriver instance and be killed when the process exits. + * + * Suppose you had mytest.js that created WebDriver with + * + * var driver = new webdriver.Builder() + * .forBrowser('chrome') + * .build(); + * + * This test could be made to use Firefox on the local machine by running with + * `SELENIUM_BROWSER=firefox node mytest.js`. Rather than change the code to + * target Google Chrome on a remote machine, you can simply set the + * `SELENIUM_BROWSER` and `SELENIUM_REMOTE_URL` environment variables: + * + * SELENIUM_BROWSER=chrome:36:LINUX \ + * SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \ + * node mytest.js + * + * You could also use a local copy of the standalone Selenium server: + * + * SELENIUM_BROWSER=chrome:36:LINUX \ + * SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \ + * node mytest.js + * * @constructor - * @extends {webdriver.AbstractBuilder} */ var Builder = function() { - goog.base(this); + + /** @private {webdriver.promise.ControlFlow} */ + this.flow_ = null; + + /** @private {string} */ + this.url_ = ''; + + /** @private {?string} */ + this.proxy_ = null; + + /** @private {!webdriver.Capabilities} */ + this.capabilities_ = new Capabilities(); + + /** @private {chrome.Options} */ + this.chromeOptions_ = null; + + /** @private {firefox.Options} */ + this.firefoxOptions_ = null; + + /** @private {opera.Options} */ + this.operaOptions_ = null; + + /** @private {ie.Options} */ + this.ieOptions_ = null; + + /** @private {safari.Options} */ + this.safariOptions_ = null; + + /** @private {boolean} */ + this.ignoreEnv_ = false; +}; + + +/** + * Configures this builder to ignore any environment variable overrides and to + * only use the configuration specified through this instance's API. + * + * @return {!Builder} A self reference. + */ +Builder.prototype.disableEnvironmentOverrides = function() { + this.ignoreEnv_ = true; + return this; +}; + + +/** + * Sets the URL of a remote WebDriver server to use. Once a remote URL has been + * specified, the builder direct all new clients to that server. If this method + * is never called, the Builder will attempt to create all clients locally. + * + * As an alternative to this method, you may also set the `SELENIUM_REMOTE_URL` + * environment variable. + * + * @param {string} url The URL of a remote server to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.usingServer = function(url) { + this.url_ = url; + return this; +}; + + +/** + * @return {string} The URL of the WebDriver server this instance is configured + * to use. + */ +Builder.prototype.getServerUrl = function() { + return this.url_; +}; + + +/** + * Sets the URL of the proxy to use for the WebDriver's HTTP connections. + * If this method is never called, the Builder will create a connection without + * a proxy. + * + * @param {string} proxy The URL of a proxy to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.usingWebDriverProxy = function(proxy) { + this.proxy_ = proxy; + return this; +}; + + +/** + * @return {string} The URL of the proxy server to use for the WebDriver's HTTP + * connections. + */ +Builder.prototype.getWebDriverProxy = function() { + return this.proxy_; +}; + + +/** + * Sets the desired capabilities when requesting a new session. This will + * overwrite any previously set capabilities. + * @param {!(Object|webdriver.Capabilities)} capabilities The desired + * capabilities for a new session. + * @return {!Builder} A self reference. + */ +Builder.prototype.withCapabilities = function(capabilities) { + this.capabilities_ = new Capabilities(capabilities); + return this; +}; + + +/** + * Returns the base set of capabilities this instance is currently configured + * to use. + * @return {!webdriver.Capabilities} The current capabilities for this builder. + */ +Builder.prototype.getCapabilities = function() { + return this.capabilities_; +}; + + +/** + * Configures the target browser for clients created by this instance. + * Any calls to {@link #withCapabilities} after this function will + * overwrite these settings. + * + * You may also define the target browser using the {@code SELENIUM_BROWSER} + * environment variable. If set, this environment variable should be of the + * form `browser[:[version][:platform]]`. + * + * @param {(string|webdriver.Browser)} name The name of the target browser; + * common defaults are available on the {@link webdriver.Browser} enum. + * @param {string=} opt_version A desired version; may be omitted if any + * version should be used. + * @param {string=} opt_platform The desired platform; may be omitted if any + * version may be used. + * @return {!Builder} A self reference. + */ +Builder.prototype.forBrowser = function(name, opt_version, opt_platform) { + this.capabilities_.set(Capability.BROWSER_NAME, name); + this.capabilities_.set(Capability.VERSION, opt_version || null); + this.capabilities_.set(Capability.PLATFORM, opt_platform || null); + return this; }; -goog.inherits(Builder, AbstractBuilder); /** * Sets the proxy configuration to use for WebDriver clients created by this * builder. Any calls to {@link #withCapabilities} after this function will * overwrite these settings. - * @param {!proxy.ProxyConfig} config The configuration to use. + * @param {!webdriver.ProxyConfig} config The configuration to use. * @return {!Builder} A self reference. */ Builder.prototype.setProxy = function(config) { - this.getCapabilities().set(Capability.PROXY, config); + this.capabilities_.setProxy(config); return this; }; /** - * Sets Chrome-specific options for drivers created by this builder. + * Sets the logging preferences for the created session. Preferences may be + * changed by repeated calls, or by calling {@link #withCapabilities}. + * @param {!(webdriver.logging.Preferences|Object.)} prefs The + * desired logging preferences. + * @return {!Builder} A self reference. + */ +Builder.prototype.setLoggingPrefs = function(prefs) { + this.capabilities_.setLoggingPrefs(prefs); + return this; +}; + + +/** + * Sets whether native events should be used. + * @param {boolean} enabled Whether to enable native events. + * @return {!Builder} A self reference. + */ +Builder.prototype.setEnableNativeEvents = function(enabled) { + this.capabilities_.setEnableNativeEvents(enabled); + return this; +}; + + +/** + * Sets how elements should be scrolled into view for interaction. + * @param {number} behavior The desired scroll behavior: either 0 to align with + * the top of the viewport or 1 to align with the bottom. + * @return {!Builder} A self reference. + */ +Builder.prototype.setScrollBehavior = function(behavior) { + this.capabilities_.setScrollBehavior(behavior); + return this; +}; + + +/** + * Sets the default action to take with an unexpected alert before returning + * an error. + * @param {string} beahvior The desired behavior; should be "accept", "dismiss", + * or "ignore". Defaults to "dismiss". + * @return {!Builder} A self reference. + */ +Builder.prototype.setAlertBehavior = function(behavior) { + this.capabilities_.setAlertBehavior(behavior); + return this; +}; + + +/** + * Sets Chrome specific {@linkplain selenium-webdriver/chrome.Options options} + * for drivers created by this builder. Any logging or proxy settings defined + * on the given options will take precedence over those set through + * {@link #setLoggingPrefs} and {@link #setProxy}, respectively. + * * @param {!chrome.Options} options The ChromeDriver options to use. * @return {!Builder} A self reference. */ Builder.prototype.setChromeOptions = function(options) { - var newCapabilities = options.toCapabilities(this.getCapabilities()); - return /** @type {!Builder} */(this.withCapabilities(newCapabilities)); + this.chromeOptions_ = options; + return this; }; /** - * @override + * Sets Firefox specific {@linkplain selenium-webdriver/firefox.Options options} + * for drivers created by this builder. Any logging or proxy settings defined + * on the given options will take precedence over those set through + * {@link #setLoggingPrefs} and {@link #setProxy}, respectively. + * + * @param {!firefox.Options} options The FirefoxDriver options to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.setFirefoxOptions = function(options) { + this.firefoxOptions_ = options; + return this; +}; + + +/** + * Sets Opera specific {@linkplain selenium-webdriver/opera.Options options} for + * drivers created by this builder. Any logging or proxy settings defined on the + * given options will take precedence over those set through + * {@link #setLoggingPrefs} and {@link #setProxy}, respectively. + * + * @param {!opera.Options} options The OperaDriver options to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.setOperaOptions = function(options) { + this.operaOptions_ = options; + return this; +}; + + +/** + * Sets Internet Explorer specific + * {@linkplain selenium-webdriver/ie.Options options} for drivers created by + * this builder. Any proxy settings defined on the given options will take + * precedence over those set through {@link #setProxy}. + * + * @param {!ie.Options} options The IEDriver options to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.setIeOptions = function(options) { + this.ieOptions_ = options; + return this; +}; + + +/** + * Sets Safari specific {@linkplain selenium-webdriver/safari.Options options} + * for drivers created by this builder. Any logging settings defined on the + * given options will take precedence over those set through + * {@link #setLoggingPrefs}. + * + * @param {!safari.Options} options The Safari options to use. + * @return {!Builder} A self reference. + */ +Builder.prototype.setSafariOptions = function(options) { + this.safariOptions_ = options; + return this; +}; + + +/** + * Sets the control flow that created drivers should execute actions in. If + * the flow is never set, or is set to {@code null}, it will use the active + * flow at the time {@link #build()} is called. + * @param {webdriver.promise.ControlFlow} flow The control flow to use, or + * {@code null} to + * @return {!Builder} A self reference. + */ +Builder.prototype.setControlFlow = function(flow) { + this.flow_ = flow; + return this; +}; + + +/** + * Creates a new WebDriver client based on this builder's current + * configuration. + * + * @return {!webdriver.WebDriver} A new WebDriver instance. + * @throws {Error} If the current configuration is invalid. */ Builder.prototype.build = function() { - var url = this.getServerUrl(); - - // If a remote server wasn't specified, check for browsers we support - // natively in node before falling back to using the java Selenium server. - if (!url) { - var driver = createNativeDriver(this.getCapabilities()); - if (driver) { - return driver; + // Create a copy for any changes we may need to make based on the current + // environment. + var capabilities = new Capabilities(this.capabilities_); + + var browser; + if (!this.ignoreEnv_ && process.env.SELENIUM_BROWSER) { + browser = process.env.SELENIUM_BROWSER.split(/:/, 3); + capabilities.set(Capability.BROWSER_NAME, browser[0]); + capabilities.set(Capability.VERSION, browser[1] || null); + capabilities.set(Capability.PLATFORM, browser[2] || null); + } + + browser = capabilities.get(Capability.BROWSER_NAME); + + if (typeof browser !== 'string') { + throw TypeError( + 'Target browser must be a string, but is <' + (typeof browser) + '>;' + + ' did you forget to call forBrowser()?'); + } + + if (browser === 'ie') { + browser = Browser.INTERNET_EXPLORER; + } + + // Apply browser specific overrides. + if (browser === Browser.CHROME && this.chromeOptions_) { + capabilities.merge(this.chromeOptions_.toCapabilities()); + + } else if (browser === Browser.FIREFOX && this.firefoxOptions_) { + capabilities.merge(this.firefoxOptions_.toCapabilities()); + + } else if (browser === Browser.INTERNET_EXPLORER && this.ieOptions_) { + capabilities.merge(this.ieOptions_.toCapabilities()); + + } else if (browser === Browser.OPERA && this.operaOptions_) { + capabilities.merge(this.operaOptions_.toCapabilities()); + + } else if (browser === Browser.SAFARI && this.safariOptions_) { + capabilities.merge(this.safariOptions_.toCapabilities()); + } + + // Check for a remote browser. + var url = this.url_; + if (!this.ignoreEnv_) { + if (process.env.SELENIUM_REMOTE_URL) { + url = process.env.SELENIUM_REMOTE_URL; + } else if (process.env.SELENIUM_SERVER_JAR) { + url = startSeleniumServer(process.env.SELENIUM_SERVER_JAR); } + } - // Nope, fall-back to using the default java server. - url = AbstractBuilder.DEFAULT_SERVER_URL; + if (url) { + var executor = executors.createExecutor(url, this.proxy_); + return WebDriver.createSession(executor, capabilities, this.flow_); } - var executor = executors.createExecutor(url); - return WebDriver.createSession(executor, this.getCapabilities()); + // Check for a native browser. + switch (browser) { + case Browser.CHROME: + // Requiring 'chrome' above would create a cycle: + // index -> builder -> chrome -> index + var chrome = require('./chrome'); + return new chrome.Driver(capabilities, null, this.flow_); + + case Browser.FIREFOX: + // Requiring 'firefox' above would create a cycle: + // index -> builder -> firefox -> index + var firefox = require('./firefox'); + return new firefox.Driver(capabilities, this.flow_); + + case Browser.INTERNET_EXPLORER: + // Requiring 'ie' above would create a cycle: + // index -> builder -> ie -> index + var ie = require('./ie'); + return new ie.Driver(capabilities, this.flow_); + + case Browser.OPERA: + // Requiring 'opera' would create a cycle: + // index -> builder -> opera -> index + var opera = require('./opera'); + return new opera.Driver(capabilities, this.flow_); + + case Browser.PHANTOM_JS: + // Requiring 'phantomjs' would create a cycle: + // index -> builder -> phantomjs -> index + var phantomjs = require('./phantomjs'); + return new phantomjs.Driver(capabilities, this.flow_); + + case Browser.SAFARI: + // Requiring 'safari' would create a cycle: + // index -> builder -> safari -> index + var safari = require('./safari'); + return new safari.Driver(capabilities, this.flow_); + + default: + throw new Error('Do not know how to build driver: ' + browser + + '; did you forget to call usingServer(url)?'); + } }; diff --git a/chrome.js b/chrome.js index 6ac6f8e..b00d0c0 100644 --- a/chrome.js +++ b/chrome.js @@ -1,17 +1,116 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines a {@linkplain Driver WebDriver} client for the Chrome + * web browser. Before using this module, you must download the latest + * [ChromeDriver release] and ensure it can be found on your system [PATH]. + * + * There are three primary classes exported by this module: + * + * 1. {@linkplain ServiceBuilder}: configures the + * {@link selenium-webdriver/remote.DriverService remote.DriverService} + * that manages the [ChromeDriver] child process. + * + * 2. {@linkplain Options}: defines configuration options for each new Chrome + * session, such as which {@linkplain Options#setProxy proxy} to use, + * what {@linkplain Options#addExtensions extensions} to install, or + * what {@linkplain Options#addArguments command-line switches} to use when + * starting the browser. + * + * 3. {@linkplain Driver}: the WebDriver client; each new instance will control + * a unique browser session with a clean user profile (unless otherwise + * configured through the {@link Options} class). + * + * __Customizing the ChromeDriver Server__ + * + * By default, every Chrome session will use a single driver service, which is + * started the first time a {@link Driver} instance is created and terminated + * when this process exits. The default service will inherit its environment + * from the current process and direct all output to /dev/null. You may obtain + * a handle to this default service using + * {@link #getDefaultService getDefaultService()} and change its configuration + * with {@link #setDefaultService setDefaultService()}. + * + * You may also create a {@link Driver} with its own driver service. This is + * useful if you need to capture the server's log output for a specific session: + * + * var chrome = require('selenium-webdriver/chrome'); + * + * var service = new chrome.ServiceBuilder() + * .loggingTo('/my/log/file.txt') + * .enableVerboseLogging() + * .build(); + * + * var options = new chrome.Options(); + * // configure browser options ... + * + * var driver = new chrome.Driver(options, service); + * + * Users should only instantiate the {@link Driver} class directly when they + * need a custom driver service configuration (as shown above). For normal + * operation, users should start Chrome using the + * {@link selenium-webdriver.Builder}. + * + * __Working with Android__ + * + * The [ChromeDriver][android] supports running tests on the Chrome browser as + * well as [WebView apps][webview] starting in Android 4.4 (KitKat). In order to + * work with Android, you must first start the adb + * + * adb start-server + * + * By default, adb will start on port 5037. You may change this port, but this + * will require configuring a [custom server](#custom-server) that will connect + * to adb on the {@linkplain ServiceBuilder#setAdbPort correct port}: + * + * var service = new chrome.ServiceBuilder() + * .setAdbPort(1234) + * build(); + * // etc. + * + * The ChromeDriver may be configured to launch Chrome on Android using + * {@link Options#androidChrome()}: + * + * var driver = new Builder() + * .forBrowser('chrome') + * .setChromeOptions(new chrome.Options().androidChrome()) + * .build(); + * + * Alternatively, you can configure the ChromeDriver to launch an app with a + * Chrome-WebView by setting the {@linkplain Options#androidActivity + * androidActivity} option: + * + * var driver = new Builder() + * .forBrowser('chrome') + * .setChromeOptions(new chrome.Options() + * .androidPackage('com.example') + * .androidActivity('com.example.Activity')) + * .build(); + * + * [Refer to the ChromeDriver site] for more information on using the + * [ChromeDriver with Android][android]. + * + * [ChromeDriver]: https://sites.google.com/a/chromium.org/chromedriver/ + * [ChromeDriver release]: http://chromedriver.storage.googleapis.com/index.html + * [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29 + * [android]: https://sites.google.com/a/chromium.org/chromedriver/getting-started/getting-started---android + * [webview]: https://developer.chrome.com/multidevice/webview/overview + */ 'use strict'; @@ -20,6 +119,7 @@ var fs = require('fs'), var webdriver = require('./index'), executors = require('./executors'), + http = require('./http'), io = require('./io'), portprober = require('./net/portprober'), remote = require('./remote'); @@ -35,8 +135,36 @@ var CHROMEDRIVER_EXE = /** - * Creates {@link remote.DriverService} instances that manage a ChromeDriver - * server. + * Custom command names supported by ChromeDriver. + * @enum {string} + */ +var Command = { + LAUNCH_APP: 'launchApp' +}; + + +/** + * Creates a command executor with support for ChromeDriver's custom commands. + * @param {!webdriver.promise.Promise} url The server's URL. + * @return {!webdriver.CommandExecutor} The new command executor. + */ +function createExecutor(url) { + return new executors.DeferredExecutor(url.then(function(url) { + var client = new http.HttpClient(url); + var executor = new http.Executor(client); + executor.defineCommand( + Command.LAUNCH_APP, + 'POST', '/session/:sessionId/chromium/launch_app'); + return executor; + })); +} + + +/** + * Creates {@link selenium-webdriver/remote.DriverService} instances that manage + * a [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) + * server in a child process. + * * @param {string=} opt_exe Path to the server executable to use. If omitted, * the builder will attempt to locate the chromedriver on the current * PATH. @@ -65,6 +193,9 @@ var ServiceBuilder = function(opt_exe) { }; +/** @private {string} */ +ServiceBuilder.prototype.path_ = null; + /** @private {number} */ ServiceBuilder.prototype.port_ = 0; @@ -92,6 +223,20 @@ ServiceBuilder.prototype.usingPort = function(port) { }; +/** + * Sets which port adb is listening to. _The ChromeDriver will connect to adb + * if an {@linkplain Options#androidPackage Android session} is requested, but + * adb **must** be started beforehand._ + * + * @param {number} port Which port adb is running on. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.setAdbPort = function(port) { + this.args_.push('--adb-port=' + port); + return this; +}; + + /** * Sets the path of the log file the driver should log to. If a log file is * not specified, the driver will log to stderr. @@ -134,6 +279,7 @@ ServiceBuilder.prototype.setNumHttpThreads = function(n) { */ ServiceBuilder.prototype.setUrlBasePath = function(path) { this.args_.push('--url-base=' + path); + this.path_ = path; return this; }; @@ -175,6 +321,8 @@ ServiceBuilder.prototype.build = function() { var args = this.args_.concat(); // Defensive copy. return new remote.DriverService(this.exe_, { + loopback: true, + path: this.path_, port: port, args: webdriver.promise.when(port, function(port) { return args.concat('--port=' + port); @@ -228,14 +376,24 @@ var OPTIONS_CAPABILITY_KEY = 'chromeOptions'; /** * Class for managing ChromeDriver specific options. * @constructor + * @extends {webdriver.Serializable} */ var Options = function() { - /** @private {!Array.} */ - this.args_ = []; + webdriver.Serializable.call(this); + + /** @private {!Object} */ + this.options_ = {}; /** @private {!Array.<(string|!Buffer)>} */ this.extensions_ = []; + + /** @private {?webdriver.logging.Preferences} */ + this.logPrefs_ = null; + + /** @private {?webdriver.ProxyConfig} */ + this.proxy_ = null; }; +util.inherits(Options, webdriver.Serializable); /** @@ -254,11 +412,15 @@ Options.fromCapabilities = function(capabilities) { options. addArguments(o.args || []). addExtensions(o.extensions || []). - detachDriver(!!o.detach). + detachDriver(o.detach). + excludeSwitches(o.excludeSwitches || []). setChromeBinaryPath(o.binary). - setChromeLogFile(o.logFile). + setChromeLogFile(o.logPath). + setChromeMinidumpPath(o.minidumpPath). setLocalState(o.localState). - setUserPreferences(o.prefs); + setMobileEmulation(o.mobileEmulation). + setUserPreferences(o.prefs). + setPerfLoggingPrefs(o.perfLoggingPrefs); } if (capabilities.has(webdriver.Capability.PROXY)) { @@ -266,7 +428,7 @@ Options.fromCapabilities = function(capabilities) { } if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) { - options.setLoggingPreferences( + options.setLoggingPrefs( capabilities.get(webdriver.Capability.LOGGING_PREFS)); } @@ -283,7 +445,28 @@ Options.fromCapabilities = function(capabilities) { * @return {!Options} A self reference. */ Options.prototype.addArguments = function(var_args) { - this.args_ = this.args_.concat.apply(this.args_, arguments); + var args = this.options_.args || []; + args = args.concat.apply(args, arguments); + if (args.length) { + this.options_.args = args; + } + return this; +}; + + +/** + * List of Chrome command line switches to exclude that ChromeDriver by default + * passes when starting Chrome. Do not prefix switches with "--". + * + * @param {...(string|!Array)} var_args The switches to exclude. + * @return {!Options} A self reference. + */ +Options.prototype.excludeSwitches = function(var_args) { + var switches = this.options_.excludeSwitches || []; + switches = switches.concat.apply(switches, arguments); + if (switches.length) { + this.options_.excludeSwitches = switches; + } return this; }; @@ -297,8 +480,7 @@ Options.prototype.addArguments = function(var_args) { * @return {!Options} A self reference. */ Options.prototype.addExtensions = function(var_args) { - this.extensions_ = this.extensions_.concat.apply( - this.extensions_, arguments); + this.extensions_ = this.extensions_.concat.apply(this.extensions_, arguments); return this; }; @@ -315,7 +497,7 @@ Options.prototype.addExtensions = function(var_args) { * @return {!Options} A self reference. */ Options.prototype.setChromeBinaryPath = function(path) { - this.binary_ = path; + this.options_.binary = path; return this; }; @@ -329,7 +511,7 @@ Options.prototype.setChromeBinaryPath = function(path) { * @return {!Options} A self reference. */ Options.prototype.detachDriver = function(detach) { - this.detach_ = detach; + this.options_.detach = detach; return this; }; @@ -341,7 +523,7 @@ Options.prototype.detachDriver = function(detach) { * @return {!Options} A self reference. */ Options.prototype.setUserPreferences = function(prefs) { - this.prefs_ = prefs; + this.options_.prefs = prefs; return this; }; @@ -351,12 +533,42 @@ Options.prototype.setUserPreferences = function(prefs) { * @param {!webdriver.logging.Preferences} prefs The logging preferences. * @return {!Options} A self reference. */ -Options.prototype.setLoggingPreferences = function(prefs) { +Options.prototype.setLoggingPrefs = function(prefs) { this.logPrefs_ = prefs; return this; }; +/** + * Sets the performance logging preferences. Options include: + * + * - `enableNetwork`: Whether or not to collect events from Network domain. + * - `enablePage`: Whether or not to collect events from Page domain. + * - `enableTimeline`: Whether or not to collect events from Timeline domain. + * Note: when tracing is enabled, Timeline domain is implicitly disabled, + * unless `enableTimeline` is explicitly set to true. + * - `tracingCategories`: A comma-separated string of Chrome tracing categories + * for which trace events should be collected. An unspecified or empty + * string disables tracing. + * - `bufferUsageReportingInterval`: The requested number of milliseconds + * between DevTools trace buffer usage events. For example, if 1000, then + * once per second, DevTools will report how full the trace buffer is. If a + * report indicates the buffer usage is 100%, a warning will be issued. + * + * @param {{enableNetwork: boolean, + * enablePage: boolean, + * enableTimeline: boolean, + * tracingCategories: string, + * bufferUsageReportingInterval: number}} prefs The performance + * logging preferences. + * @return {!Options} A self reference. + */ +Options.prototype.setPerfLoggingPrefs = function(prefs) { + this.options_.perfLoggingPrefs = prefs; + return this; +}; + + /** * Sets preferences for the "Local State" file in Chrome's user data * directory. @@ -364,7 +576,87 @@ Options.prototype.setLoggingPreferences = function(prefs) { * @return {!Options} A self reference. */ Options.prototype.setLocalState = function(state) { - this.localState_ = state; + this.options_.localState = state; + return this; +}; + + +/** + * Sets the name of the activity hosting a Chrome-based Android WebView. This + * option must be set to connect to an [Android WebView]( + * https://sites.google.com/a/chromium.org/chromedriver/getting-started/getting-started---android) + * + * @param {string} name The activity name. + * @return {!Options} A self reference. + */ +Options.prototype.androidActivity = function(name) { + this.options_.androidActivity = name; + return this; +}; + + +/** + * Sets the device serial number to connect to via ADB. If not specified, the + * ChromeDriver will select an unused device at random. An error will be + * returned if all devices already have active sessions. + * + * @param {string} serial The device serial number to connect to. + * @return {!Options} A self reference. + */ +Options.prototype.androidDeviceSerial = function(serial) { + this.options_.androidDeviceSerial = serial; + return this; +}; + + +/** + * Configures the ChromeDriver to launch Chrome on Android via adb. This + * function is shorthand for + * {@link #androidPackage options.androidPackage('com.android.chrome')}. + * @return {!Options} A self reference. + */ +Options.prototype.androidChrome = function() { + return this.androidPackage('com.android.chrome'); +}; + + +/** + * Sets the package name of the Chrome or WebView app. + * + * @param {?string} pkg The package to connect to, or `null` to disable Android + * and switch back to using desktop Chrome. + * @return {!Options} A self reference. + */ +Options.prototype.androidPackage = function(pkg) { + this.options_.androidPackage = pkg; + return this; +}; + + +/** + * Sets the process name of the Activity hosting the WebView (as given by `ps`). + * If not specified, the process name is assumed to be the same as + * {@link #androidPackage}. + * + * @param {string} processName The main activity name. + * @return {!Options} A self reference. + */ +Options.prototype.androidProcess = function(processName) { + this.options_.androidProcess = processName; + return this; +}; + + +/** + * Sets whether to connect to an already-running instead of the specified + * {@linkplain #androidProcess app} instead of launching the app with a clean + * data directory. + * + * @param {boolean} useRunning Whether to connect to a running instance. + * @return {!Options} A self reference. + */ +Options.prototype.androidUseRunningApp = function(useRunning) { + this.options_.androidUseRunningApp = useRunning; return this; }; @@ -376,14 +668,68 @@ Options.prototype.setLocalState = function(state) { * @return {!Options} A self reference. */ Options.prototype.setChromeLogFile = function(path) { - this.logFile_ = path; + this.options_.logPath = path; + return this; +}; + + +/** + * Sets the directory to store Chrome minidumps in. This option is only + * supported when ChromeDriver is running on Linux. + * @param {string} path The directory path. + * @return {!Options} A self reference. + */ +Options.prototype.setChromeMinidumpPath = function(path) { + this.options_.minidumpPath = path; + return this; +}; + + +/** + * Configures Chrome to emulate a mobile device. For more information, refer to + * the ChromeDriver project page on [mobile emulation][em]. Configuration + * options include: + * + * - `deviceName`: The name of a pre-configured [emulated device][devem] + * - `width`: screen width, in pixels + * - `height`: screen height, in pixels + * - `pixelRatio`: screen pixel ratio + * + * __Example 1: Using a Pre-configured Device__ + * + * var options = new chrome.Options().setMobileEmulation( + * {deviceName: 'Google Nexus 5'}); + * + * var driver = new chrome.Driver(options); + * + * __Example 2: Using Custom Screen Configuration__ + * + * var options = new chrome.Options().setMobileEmulation({ + * width: 360, + * height: 640, + * pixelRatio: 3.0 + * }); + * + * var driver = new chrome.Driver(options); + * + * + * [em]: https://sites.google.com/a/chromium.org/chromedriver/mobile-emulation + * [devem]: https://developer.chrome.com/devtools/docs/device-mode + * + * @param {?({deviceName: string}| + * {width: number, height: number, pixelRatio: number})} config The + * mobile emulation configuration, or `null` to disable emulation. + * @return {!Options} A self reference. + */ +Options.prototype.setMobileEmulation = function(config) { + this.options_.mobileEmulation = config; return this; }; /** * Sets the proxy settings for the new session. - * @param {ProxyConfig} proxy The proxy configuration to use. + * @param {webdriver.ProxyConfig} proxy The proxy configuration to use. * @return {!Options} A self reference. */ Options.prototype.setProxy = function(proxy) { @@ -414,59 +760,89 @@ Options.prototype.toCapabilities = function(opt_capabilities) { * @return {{args: !Array., * binary: (string|undefined), * detach: boolean, - * extensions: !Array., + * extensions: !Array.<(string|!webdriver.promise.Promise.)>, * localState: (Object|undefined), - * logFile: (string|undefined), + * logPath: (string|undefined), * prefs: (Object|undefined)}} The JSON wire protocol representation * of this instance. + * @override */ -Options.prototype.toJSON = function() { - return { - args: this.args_, - binary: this.binary_, - detach: !!this.detach_, - extensions: this.extensions_.map(function(extension) { +Options.prototype.serialize = function() { + var json = {}; + for (var key in this.options_) { + if (this.options_[key] != null) { + json[key] = this.options_[key]; + } + } + if (this.extensions_.length) { + json.extensions = this.extensions_.map(function(extension) { if (Buffer.isBuffer(extension)) { return extension.toString('base64'); } - return fs.readFileSync(extension, 'base64'); - }), - localState: this.localState_, - logFile: this.logFile_, - prefs: this.prefs_ - }; + return webdriver.promise.checkedNodeCall( + fs.readFile, extension, 'base64'); + }); + } + return json; }; /** - * Creates a new ChromeDriver session. - * @param {(webdriver.Capabilities|Options)=} opt_options The session options. + * Creates a new WebDriver client for Chrome. + * + * @param {(webdriver.Capabilities|Options)=} opt_config The configuration + * options. * @param {remote.DriverService=} opt_service The session to use; will use - * the {@link getDefaultService default service} by default. - * @return {!webdriver.WebDriver} A new WebDriver instance. + * the {@linkplain #getDefaultService default service} by default. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or + * {@code null} to use the currently active flow. + * @constructor + * @extends {webdriver.WebDriver} */ -function createDriver(opt_options, opt_service) { +var Driver = function(opt_config, opt_service, opt_flow) { var service = opt_service || getDefaultService(); - var executor = executors.createExecutor(service.start()); + var executor = createExecutor(service.start()); - var options = opt_options || new Options(); - if (opt_options instanceof webdriver.Capabilities) { - // Extract the Chrome-specific options so we do not send unnecessary - // data across the wire. - options = Options.fromCapabilities(options); - } + var capabilities = + opt_config instanceof Options ? opt_config.toCapabilities() : + (opt_config || webdriver.Capabilities.chrome()); + + var driver = webdriver.WebDriver.createSession( + executor, capabilities, opt_flow); + + webdriver.WebDriver.call( + this, driver.getSession(), executor, driver.controlFlow()); +}; +util.inherits(Driver, webdriver.WebDriver); - return webdriver.WebDriver.createSession( - executor, options.toCapabilities()); -} +/** + * This function is a no-op as file detectors are not supported by this + * implementation. + * @override + */ +Driver.prototype.setFileDetector = function() { +}; + + +/** + * Schedules a command to launch Chrome App with given ID. + * @param {string} id ID of the App to launch. + * @return {!webdriver.promise.Promise} A promise that will be resolved + * when app is launched. + */ +Driver.prototype.launchApp = function(id) { + return this.schedule( + new webdriver.Command(Command.LAUNCH_APP).setParameter('id', id), + 'Driver.launchApp()'); +}; // PUBLIC API -exports.ServiceBuilder = ServiceBuilder; +exports.Driver = Driver; exports.Options = Options; -exports.createDriver = createDriver; +exports.ServiceBuilder = ServiceBuilder; exports.getDefaultService = getDefaultService; exports.setDefaultService = setDefaultService; diff --git a/docs/Changes.html b/docs/Changes.html new file mode 100644 index 0000000..70f4cff --- /dev/null +++ b/docs/Changes.html @@ -0,0 +1,317 @@ +Changes

v2.47.0

+

Notice

+

This is the last release for selenium-webdriver that will support ES5. +Subsequent releases will depend on ES6 features that are enabled by +default in Node v4.0.0. Node v0.12.x will +continue to be supported, but will require setting the --harmony flag.

+

Change Summary

+
  • Add support for Node v4.0.0 +
    • Updated ws dependency from 0.7.1 to 0.8.0
    +
  • Bumped the minimum supported version of Node from 0.10.x to 0.12.x. This +is in accordance with the Node support policy established in v2.45.0.
+

v2.46.1

+
  • Fixed internal module loading on Windows.
  • Fixed error message format on timeouts for until.elementLocated() +and until.elementsLocated().
+

v2.46.0

+
  • Exposed a new logging API via the webdriver.logging module. For usage, see +example/logging.js.
  • Added support for using a proxy server for WebDriver commands. +See Builder#usingWebDriverProxy() for more info.
  • Removed deprecated functions: +
    • Capabilities#toJSON()
    • UnhandledAlertError#getAlert()
    • chrome.createDriver()
    • phantomjs.createDriver()
    • promise.ControlFlow#annotateError()
    • promise.ControlFlow#await()
    • promise.ControlFlow#clearHistory()
    • promise.ControlFlow#getHistory()
    +
  • Removed deprecated enum values: ErrorCode.NO_MODAL_DIALOG_OPEN and +ErrorCode.MODAL_DIALOG_OPENED. Use ErrorCode.NO_SUCH_ALERT and +ErrorCode.UNEXPECTED_ALERT_OPEN, respectively.
  • FIXED: The promise.ControlFlow will maintain state for promise chains +generated in a loop.
  • FIXED: Correct serialize target elements used in an action sequence.
  • FIXED: promise.ControlFlow#wait() now has consistent semantics for an +omitted or 0-timeout: it will wait indefinitely.
  • FIXED: remote.DriverService#start() will now fail if the child process dies +while waiting for the server to start accepting requests. Previously, start +would continue to poll the server address until the timeout expired.
  • FIXED: Skip launching Firefox with the -silent flag to preheat the profile. +Starting with Firefox 38, this would cause the browser to crash. This step, +which was first introduced for Selenium's java client back with Firefox 2, +no longer appears to be required.
  • FIXED: 8564: firefox.Driver#quit() will wait for the Firefox process to +terminate before deleting the temporary webdriver profile. This eliminates a +race condition where Firefox would write profile data during shutdown, +causing the rm -rf operation on the profile directory to fail.
+

v2.45.1

+
  • FIXED: 8548: Task callbacks are once again dropped if the task was cancelled +due to a previously uncaught error within the frame.
  • FIXED: 8496: Extended the chrome.Options API to cover all configuration +options (e.g. mobile emulation and performance logging) documented on the +ChromeDriver project site.
+

v2.45.0

+

Important Policy Change

+

Starting with the 2.45.0 release, selenium-webdriver will support the last +two stable minor releases for Node. For 2.45.0, this means Selenium will +support Node 0.10.x and 0.12.x. Support for the intermediate, un-stable release +(0.11.x) is "best-effort". This policy will be re-evaluated once Node has a +major version release (i.e. 1.0.0).

+

Change Summary

+
  • +

    Added native browser support for Internet Explorer, Opera 26+, and Safari

    +
  • +

    With the release of Node 0.12.0 +(finally!), the minimum supported version of Node is now 0.10.x.

    +
  • +

    The promise module is now Promises/A+ +compliant. The biggest compliance change is that promise callbacks are now +invoked in a future turn of the JS event loop. For example:

    +
      var promise = require('selenium-webdriver').promise;
    +  console.log('start');
    +  promise.fulfilled().then(function() {
    +    console.log('middle');
    +  });
    +  console.log('end');
    +
    +  // Output in selenium-webdriver@2.44.0
    +  // start
    +  // middle
    +  // end
    +  //
    +  // Output in selenium-webdriver@2.45.0
    +  // start
    +  // end
    +  // middle
    +
    +

    The promise.ControlFlow class has been updated to track the asynchronous +breaks required by Promises/A+, so there are no changes to task execution +order.

    +
  • +

    Updated how errors are annotated on failures. When a task fails, the +stacktrace from when that task was scheduled is appended to the rejection +reason with a From: prefix (if it is an Error object). For example:

    +
      var driver = new webdriver.Builder().forBrowser('chrome').build();
    +  driver.get('http://www.google.com/ncr');
    +  driver.call(function() {
    +    driver.wait(function() {
    +      return driver.isElementPresent(webdriver.By.id('not-there'));
    +    }, 2000, 'element not found');
    +  });
    +
    +

    This code will fail an error like:

    +
      Error: element not found
    +  Wait timed out after 2002ms
    +      at <stack trace>
    +  From: Task: element not found
    +      at <stack trace>
    +  From: Task: WebDriver.call(function)
    +      at <stack trace>
    +
    +
  • +

    Changed the format of strings returned by promise.ControlFlow#getSchedule. +This function now accepts a boolean to control whether the returned string +should include the stacktraces for when each task was scheduled.

    +
  • +

    Deprecating promise.ControlFlow#getHistory, +promise.ControlFlow#clearHistory, and promise.ControlFlow#annotateError. +These functions were all intended for internal use and are no longer +necessary, so they have been made no-ops.

    +
  • +

    WebDriver.wait() may now be used to wait for a promise to resolve, with +an optional timeout. Refer to the API documentation for more information.

    +
  • +

    Added support for copying files to a remote Selenium via sendKeys to test +file uploads. Refer to the API documentation for more information. Sample +usage included in test/upload_test.js

    +
  • +

    Expanded the interactions API to include touch actions. +See WebDriver.touchActions().

    +
  • +

    FIXED: 8380: firefox.Driver will delete its temporary profile on quit.

    +
  • +

    FIXED: 8306: Stack overflow in promise callbacks eliminated.

    +
  • +

    FIXED: 8221: Added support for defining custom command mappings. Includes +support for PhantomJS's executePhantomJS (requires PhantomJS 1.9.7 or +GhostDriver 1.1.0).

    +
  • +

    FIXED: 8128: When the FirefoxDriver marshals an object to the page for +executeScript, it defines additional properties (required by the driver's +implementation). These properties will no longer be enumerable and should +be omitted (i.e. they won't show up in JSON.stringify output).

    +
  • +

    FIXED: 8094: The control flow will no longer deadlock when a task returns +a promise that depends on the completion of sub-tasks.

    +
+

v2.44.0

+
  • +

    Added the until module, which defines common explicit wait conditions. +Sample usage:

    +
      var firefox = require('selenium-webdriver/firefox'),
    +      until = require('selenium-webdriver/until');
    +
    +  var driver = new firefox.Driver();
    +  driver.get('http://www.google.com/ncr');
    +  driver.wait(until.titleIs('Google Search'), 1000);
    +
    +
  • +

    FIXED: 8000: Builder.forBrowser() now accepts an empty string since some +WebDriver implementations ignore the value. A value must still be specified, +however, since it is a required field in WebDriver's wire protocol.

    +
  • +

    FIXED: 7994: The stacktrace module will not modify stack traces if the +initial parse fails (e.g. the user defined Error.prepareStackTrace)

    +
  • +

    FIXED: 5855: Added a module (until) that defines several common conditions +for use with explicit waits. See updated examples for usage.

    +
+

v2.43.5

+
  • FIXED: 7905: Builder.usingServer(url) once again returns this for +chaining.
+

v2.43.2-4

+
  • No changes; version bumps while attempting to work around an issue with +publishing to npm (a version string may only be used once).
+

v2.43.1

+
  • Fixed an issue with flakiness when setting up the Firefox profile that could +prevent the driver from initializing properly.
+

v2.43.0

+
  • +

    Added native support for Firefox - the Java Selenium server is no longer +required.

    +
  • +

    Added support for generator functions to ControlFlow#execute and +ControlFlow#wait. For more information, see documentation on +webdriver.promise.consume. Requires harmony support (run with +node --harmony-generators in v0.11.x).

    +
  • +

    Various improvements to the Builder API. Notably, the build() function +will no longer default to attempting to use a server at +http://localhost:4444/wd/hub if it cannot start a browser directly - +you must specify the WebDriver server with usingServer(url). You can +also set the target browser and WebDriver server through a pair of +environment variables. See the documentation on the Builder constructor +for more information.

    +
  • +

    For consistency with the other language bindings, added browser specific +classes that can be used to start a browser without the builder.

    +
      var webdriver = require('selenium-webdriver')
    +      chrome = require('selenium-webdriver/chrome');
    +
    +  // The following are equivalent.
    +  var driver1 = new webdriver.Builder().forBrowser('chrome').build();
    +  var driver2 = new chrome.Driver();
    +
    +
  • +

    Promise A+ compliance: a promise may no longer resolve to itself.

    +
  • +

    For consistency with other language bindings, deprecated +UnhandledAlertError#getAlert and added #getAlertText. +getAlert will be removed in 2.45.0.

    +
  • +

    FIXED: 7641: Deprecated ErrorCode.NO_MODAL_DIALOG_OPEN and +ErrorCode.MODAL_DIALOG_OPENED in favor of the new +ErrorCode.NO_SUCH_ALERT and ErrorCode.UNEXPECTED_ALERT_OPEN, +respectively.

    +
  • +

    FIXED: 7563: Mocha integration no longer disables timeouts. Default Mocha +timeouts apply (2000 ms) and may be changed using this.timeout(ms).

    +
  • +

    FIXED: 7470: Make it easier to create WebDriver instances in custom flows for +parallel execution.

    +
+

v2.42.1

+
  • FIXED: 7465: Fixed net.getLoopbackAddress on Windows
  • FIXED: 7277: Support done callback in Mocha's BDD interface
  • FIXED: 7156: Promise#thenFinally should not suppress original error
+

v2.42.0

+
  • Removed deprecated functions Promise#addCallback(), +Promise#addCallbacks(), Promise#addErrback(), and Promise#addBoth().
  • Fail with a more descriptive error if the server returns a malformed redirect
  • FIXED: 7300: Connect to ChromeDriver using the loopback address since +ChromeDriver 2.10.267517 binds to localhost by default.
  • FIXED: 7339: Preserve wrapped test function's string representation for +Mocha's BDD interface.
+

v2.41.0

+
  • FIXED: 7138: export logging API from webdriver module.
  • FIXED: 7105: beforeEach/it/afterEach properly bind this for Mocha tests.
+

v2.40.0

+
  • API documentation is now included in the docs directory.
  • Added utility functions for working with an array of promises: +promise.all, promise.map, and promise.filter
  • Introduced Promise#thenCatch() and Promise#thenFinally().
  • Deprecated Promise#addCallback(), Promise#addCallbacks(), +Promise#addErrback(), and Promise#addBoth().
  • Removed deprecated function webdriver.WebDriver#getCapability.
  • FIXED: 6826: Added support for custom locators.
+

v2.39.0

+
  • Version bump to stay in sync with the Selenium project.
+

v2.38.1

+
  • FIXED: 6686: Changed webdriver.promise.Deferred#cancel() to silently no-op +if the deferred has already been resolved.
+

v2.38.0

+
  • When a promise is rejected, always annotate the stacktrace with the parent +flow state so users can identify the source of an error.
  • Updated tests to reflect features not working correctly in the SafariDriver +(cookie management and proxy support; see issues 5051, 5212, and 5503)
  • FIXED: 6284: For mouse moves, correctly omit the x/y offsets if not +specified as a function argument (instead of passing (0,0)).
  • FIXED: 6471: Updated documentation on webdriver.WebElement#getAttribute
  • FIXED: 6612: On Unix, use the default IANA ephemeral port range if unable to +retrieve the current system's port range.
  • FIXED: 6617: Avoid triggering the node debugger when initializing the +stacktrace module.
  • FIXED: 6627: Safely rebuild chrome.Options from a partial JSON spec.
+

v2.37.0

+
  • FIXED: 6346: The remote.SeleniumServer class now accepts JVM arguments using +the jvmArgs option.
+

v2.36.0

+
  • Release skipped to stay in sync with main Selenium project.
+

v2.35.2

+
  • FIXED: 6200: Pass arguments to the Selenium server instead of to the JVM.
+

v2.35.1

+
  • FIXED: 6090: Changed example scripts to use chromedriver.
+

v2.35.0

+
  • Version bump to stay in sync with the Selenium project.
+

v2.34.1

+
  • FIXED: 6079: The parent process should not wait for spawn driver service +processes (chromedriver, phantomjs, etc.)
+

v2.34.0

+
  • +

    Added the selenium-webdriver/testing/assert module. This module +simplifies writing assertions against promised values (see +example in module documentation).

    +
  • +

    Added the webdriver.Capabilities class.

    +
  • +

    Added native support for the ChromeDriver. When using the Builder, +requesting chrome without specifying a remote server URL will default to +the native ChromeDriver implementation. The +ChromeDriver server +must be downloaded separately.

    +
      // Will start ChromeDriver locally.
    +  var driver = new webdriver.Builder().
    +      withCapabilities(webdriver.Capabilities.chrome()).
    +      build();
    +
    +  // Will start ChromeDriver using the remote server.
    +  var driver = new webdriver.Builder().
    +      withCapabilities(webdriver.Capabilities.chrome()).
    +      usingServer('http://server:1234/wd/hub').
    +      build();
    +
    +
  • +

    Added support for configuring proxies through the builder. For examples, see +selenium-webdriver/test/proxy_test.

    +
  • +

    Added native support for PhantomJS.

    +
  • +

    Changed signature of SeleniumServer to SeleniumServer(jar, options).

    +
  • +

    Tests are now included in the npm published package. See README.md for +execution instructions

    +
  • +

    Removed the deprecated webdriver.Deferred#resolve and +webdriver.promise.resolved functions.

    +
  • +

    Removed the ability to connect to an existing session from the Builder. This +feature is intended for use with the browser-based client.

    +
+

v2.33.0

+
  • Added support for WebDriver's logging API
  • FIXED: 5511: Added webdriver.manage().timeouts().pageLoadTimeout(ms)
+

v2.32.1

+
  • FIXED: 5541: Added missing return statement for windows in +portprober.findFreePort()
+

v2.32.0

+
  • Added the selenium-webdriver/testing package, which provides a basic +framework for writing tests using Mocha. See +selenium-webdriver/example/google_search_test.js for usage.
  • For Promises/A+ compatibility, backing out the change in 2.30.0 that ensured +rejections were always Error objects. Rejection reasons are now left as is.
  • Removed deprecated functions originally scheduled for removal in 2.31.0 +
    • promise.Application.getInstance()
    • promise.ControlFlow#schedule()
    • promise.ControlFlow#scheduleTimeout()
    • promise.ControlFlow#scheduleWait()
    +
  • Renamed some functions for consistency with Promises/A+ terminology. The +original functions have been deprecated and will be removed in 2.34.0: +
    • promise.resolved() -> promise.fulfilled()
    • promise.Deferred#resolve() -> promise.Deferred#fulfill()
    +
  • FIXED: remote.SeleniumServer#stop now shuts down within the active control +flow, allowing scripts to finish. Use #kill to shutdown immediately.
  • FIXED: 5321: cookie deletion commands
+

v2.31.0

+
  • Added an example script.
  • Added a class for controlling the standalone Selenium server (server +available separately)
  • Added a portprober for finding free ports
  • FIXED: WebElements now belong to the same flow as their parent driver.
+

v2.30.0

+
  • Ensures promise rejections are always Error values.
  • Version bump to keep in sync with the Selenium project.
+

v2.29.1

+
  • Fixed a bug that could lead to an infinite loop.
  • Added a README.md
+

v2.29.0

+
  • +

    Initial release for npm:

    +
      npm install selenium-webdriver
    +
    +
+
\ No newline at end of file diff --git a/docs/class_bot_Error.html b/docs/class_bot_Error.html index 2171f78..80d2347 100644 --- a/docs/class_bot_Error.html +++ b/docs/class_bot_Error.html @@ -1,6 +1,15 @@ -bot.Error

Class bot.Error

code »
Error
-  └ bot.Error

Error extension that includes error status codes from the WebDriver wire - protocol: - http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes

Constructor

bot.Error ( code, opt_message )
Parameters
code: !bot.ErrorCode
The error's status code.
opt_message: string=
Optional error message.

Enumerations

bot.Error.State
Status strings enumerated in the W3C WebDriver working draft.
Show:

Instance Methods

Defined in bot.Error

Returns
he string representation of this error.

Instance Properties

Defined in bot.Error

Flag used for duck-typing when this code is embedded in a Firefox extension. - This is required since an Error thrown in one component and then reported - to another will fail instanceof checks in the second component.

Static Properties

A map of error codes to state string.

\ No newline at end of file +Error

class Error

Error
+  └ bot.Error

Represents an error returned from a WebDriver command request.

+

new Error(code, opt_message)

Parameters
codenumber

The error's status code.

+
opt_messagestring=

Optional error message.

+

Instance Methods

toString()code »

Returns
string

The string representation of this error.

+

Instance Properties

codenumber

This error's status code.

+
descriptionstring

IE-only.

+
fileNamestring

Mozilla-only

+
isAutomationErrorboolean

Flag used for duck-typing when this code is embedded in a Firefox extension. +This is required since an Error thrown in one component and then reported +to another will fail instanceof checks in the second component.

+
lineNumbernumber

Mozilla-only.

+
messagestring
No description.
namestring
No description.
sourceURL?

Doesn't seem to exist, but closure/debug.js references it.

+
stackstring
No description.
statestring
No description.

Types

Error.State

Status strings enumerated in the W3C WebDriver protocol.

+
\ No newline at end of file diff --git a/docs/class_goog_Disposable.html b/docs/class_goog_Disposable.html new file mode 100644 index 0000000..0ab639e --- /dev/null +++ b/docs/class_goog_Disposable.html @@ -0,0 +1,37 @@ +goog.Disposable

Class goog.Disposable

code »
All implemented interfaces:
goog.disposable.IDisposable

Class that provides the basic implementation for disposable objects. If your + class holds one or more references to COM objects, DOM nodes, or other + disposable objects, it should extend this class or implement the disposable + interface (defined in goog.disposable.IDisposable).

Constructor

goog.Disposable ( )

Enumerations

Show:

Instance Methods

code »<T> addOnDisposeCallback ( callback, opt_scope )

Invokes a callback function when this object is disposed. Callbacks are + invoked in the order in which they were added.

Parameters
callback: function(this: T): ?
The callback function.
opt_scope: T=
An optional scope to call the callback in.
code »dispose ( )void

Disposes of the object. If the object hasn't already been disposed of, calls + #disposeInternal. Classes that extend goog.Disposable should + override #disposeInternal in order to delete references to COM + objects, DOM nodes, and other disposable objects. Reentrant.

Returns
Nothing.

Deletes or nulls out any references to COM objects, DOM nodes, or other + disposable objects. Classes that extend goog.Disposable should + override this method. + Not reentrant. To avoid calling it twice, it must only be called from the + subclass' disposeInternal method. Everywhere else the public + dispose method must be used. + For example: +

+   mypackage.MyClass = function() {
+     mypackage.MyClass.base(this, 'constructor');
+     // Constructor logic specific to MyClass.
+     ...
+   };
+   goog.inherits(mypackage.MyClass, goog.Disposable);
+
+   mypackage.MyClass.prototype.disposeInternal = function() {
+     // Dispose logic specific to MyClass.
+     ...
+     // Call superclass's disposeInternal at the end of the subclass's, like
+     // in C++, to avoid hard-to-catch issues.
+     mypackage.MyClass.base(this, 'disposeInternal');
+   };
+ 
Deprecated: Use #isDisposed instead.
Returns
Whether the object has been disposed of.
Returns
Whether the object has been disposed of.

Associates a disposable object with this object so that they will be disposed + together.

Parameters
disposable: goog.disposable.IDisposable
that will be disposed when + this object is disposed.

Instance Properties

If monitoring the goog.Disposable instances is enabled, stores the creation + stack trace of the Disposable instance.

Whether the object has been disposed of.

Callbacks to invoke when this object is disposed.

Static Functions

Clears the registry of undisposed objects but doesn't dispose of them.

Returns
All goog.Disposable objects that + haven't been disposed of.

Returns True if we can verify the object is disposed. + Calls isDisposed on the argument if it supports it. If obj + is not an object with an isDisposed() method, return false.

Parameters
obj: *
The object to investigate.
Returns
True if we can verify the object is disposed.

Static Properties

Maps the unique ID of every undisposed goog.Disposable object to + the object itself.

Compiler Constants

\ No newline at end of file diff --git a/docs/class_goog_Uri.html b/docs/class_goog_Uri.html index b7fd597..1474bcb 100644 --- a/docs/class_goog_Uri.html +++ b/docs/class_goog_Uri.html @@ -1,8 +1,14 @@ -goog.Uri

Class goog.Uri

code »

This class contains setters and getters for the parts of the URI. +goog.Uri

Class goog.Uri

code »

This class contains setters and getters for the parts of the URI. The getXyz/setXyz methods return the decoded part -- sogoog.Uri.parse('/foo%20bar').getPath() will return the decoded path, /foo bar. + Reserved characters (see RFC 3986 section 2.2) can be present in + their percent-encoded form in scheme, domain, and path URI components and + will not be auto-decoded. For example: + goog.Uri.parse('rel%61tive/path%2fto/resource').getPath() will + return relative/path%2fto/resource. + The constructor accepts an optional unparsed, raw URI string. The parser is relaxed, so special characters that aren't escaped but don't cause ambiguities will not cause parse failures. @@ -11,16 +17,16 @@ goog.Uri.parse('/foo').setFragment('part').toString().

Constructor

goog.Uri ( opt_uri, opt_ignoreCase )
Parameters
opt_uri: *=
Optional string URI to parse (use goog.Uri.create() to create a URI from parts), or if a goog.Uri is passed, a clone is created.
opt_ignoreCase: boolean=
If true, #getParameterValue will ignore - the case of the parameter name.

Classes

goog.Uri.QueryData
Class used to represent URI query parameters.
Show:

Instance Methods

Clones the URI instance.

Returns
New instance of the URI objcet.

Checks if this Uri has been marked as read only, and if so, throws an error. - This should be called whenever any modifying function is called.

Returns
The decoded URI query, not including the ?.
Returns
The decoded domain.
Returns
The encoded URI query, not including the ?.
Returns
The URI fragment, not including the #.
Returns
Whether to ignore case.

Returns the first value for a given cgi parameter or undefined if the given + the case of the parameter name.

Classes

goog.Uri.QueryData
Class used to represent URI query parameters.
Show:

Instance Methods

Clones the URI instance.

Returns
New instance of the URI object.

Checks if this Uri has been marked as read only, and if so, throws an error. + This should be called whenever any modifying function is called.

Returns
The decoded URI query, not including the ?.
Returns
The decoded domain.
Returns
The encoded URI query, not including the ?.
Returns
The URI fragment, not including the #.
Returns
Whether to ignore case.

Returns the first value for a given cgi parameter or undefined if the given parameter name does not appear in the query string.

Parameters
paramName: string
Unescaped parameter name.
Returns
The first value for a given cgi parameter or undefined if the given parameter name does not appear in the query - string.

Returns the values for a given cgi parameter as a list of decoded + string.

Returns the values for a given cgi parameter as a list of decoded query parameter values.

Parameters
name: string
The parameter to get values for.
Returns
The values for a given cgi parameter as a list of - decoded query parameter values.
Returns
The decoded path.
Returns
The port number.
Returns
The encoded URI query, not including the ?. + decoded query parameter values.
Returns
The decoded path.
Returns
The port number.
Returns
The encoded URI query, not including the ?. Warning: This method, unlike other getter methods, returns encoded - value, instead of decoded one.

Returns the query data.

Returns
QueryData object.
Returns
The encoded scheme/protocol for the URI.
Returns
The decoded user info.
Returns
Whether the domain has been set.
Returns
Whether the URI has a fragment set.
Returns
Whether the path has been set.
Returns
Whether the port has been set.
Returns
Whether the query string has been set.

Returns true if this has the same domain as that of uri2.

Parameters
uri2: goog.Uri
The URI object to compare to.
Returns
true if same domain; false otherwise.
Returns
Whether the scheme has been set.
Returns
Whether the user info has been set.
Returns
Whether the URI is read only.

Adds a random parameter to the Uri.

Returns
Reference to this Uri object.

Removes the named query parameter.

Parameters
key: string
The parameter to remove.
Returns
Reference to this URI object.
code »resolve ( relativeUri )!goog.Uri

Resolves the given relative URI (a goog.Uri object), using the URI + value, instead of decoded one.

Returns the query data.

Returns
QueryData object.
Returns
The encoded scheme/protocol for the URI.
Returns
The decoded user info.
Returns
Whether the domain has been set.
Returns
Whether the URI has a fragment set.
Returns
Whether the path has been set.
Returns
Whether the port has been set.
Returns
Whether the query string has been set.

Returns true if this has the same domain as that of uri2.

Parameters
uri2: goog.Uri
The URI object to compare to.
Returns
true if same domain; false otherwise.
Returns
Whether the scheme has been set.
Returns
Whether the user info has been set.
Returns
Whether the URI is read only.

Adds a random parameter to the Uri.

Returns
Reference to this Uri object.

Removes the named query parameter.

Parameters
key: string
The parameter to remove.
Returns
Reference to this URI object.
code »resolve ( relativeUri )!goog.Uri

Resolves the given relative URI (a goog.Uri object), using the URI represented by this instance as the base URI. There are several kinds of relative URIs:
@@ -31,27 +37,29 @@ 5. #foo - replace the fragment only Additionally, if relative URI has a non-empty path, all ".." and "." - segments will be resolved, as described in RFC 3986.

Parameters
relativeUri: goog.Uri
The relative URI to resolve.
Returns
The resolved URI.
code »setDomain ( newDomain, opt_decode )!goog.Uri

Sets the domain.

Parameters
newDomain: string
New domain value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setFragment ( newFragment, opt_decode )!goog.Uri

Sets the URI fragment.

Parameters
newFragment: string
New fragment value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setIgnoreCase ( ignoreCase )!goog.Uri

Sets whether to ignore case. + segments will be resolved, as described in RFC 3986.

Parameters
relativeUri: goog.Uri
The relative URI to resolve.
Returns
The resolved URI.
code »setDomain ( newDomain, opt_decode )!goog.Uri

Sets the domain.

Parameters
newDomain: string
New domain value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setFragment ( newFragment, opt_decode )!goog.Uri

Sets the URI fragment.

Parameters
newFragment: string
New fragment value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setIgnoreCase ( ignoreCase )!goog.Uri

Sets whether to ignore case. NOTE: If there are already key/value pairs in the QueryData, and - ignoreCase_ is set to false, the keys will all be lower-cased.

Parameters
ignoreCase: boolean
whether this goog.Uri should ignore case.
Returns
Reference to this Uri object.

Sets the value of the named query parameters, clearing previous values for - that key.

Parameters
key: string
The parameter to set.
value: *
The new value.
Returns
Reference to this URI object.
code »setParameterValues ( key, values )!goog.Uri

Sets the values of the named query parameters, clearing previous values for + ignoreCase_ is set to false, the keys will all be lower-cased.

Parameters
ignoreCase: boolean
whether this goog.Uri should ignore case.
Returns
Reference to this Uri object.

Sets the value of the named query parameters, clearing previous values for + that key.

Parameters
key: string
The parameter to set.
value: *
The new value.
Returns
Reference to this URI object.
code »setParameterValues ( key, values )!goog.Uri

Sets the values of the named query parameters, clearing previous values for that key. Not new values will currently be moved to the end of the query string. So, goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new']) yields foo?a=b&e=f&c=new.

Parameters
key: string
The parameter to set.
values: *
The new values. If values is a single - string then it will be treated as the sole value.
Returns
Reference to this URI object.
code »setPath ( newPath, opt_decode )!goog.Uri

Sets the path.

Parameters
newPath: string
New path value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setPort ( newPort )!goog.Uri

Sets the port number.

Parameters
newPort: *
Port number. Will be explicitly casted to a number.
Returns
Reference to this URI object.
code »setQuery ( newQuery, opt_decode )!goog.Uri

Sets the URI query.

Parameters
newQuery: string
New query value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setQueryData ( queryData, opt_decode )!goog.Uri

Sets the query data.

Parameters
queryData: (goog.Uri.QueryData|string|undefined)
QueryData object.
opt_decode: boolean=
Optional param for whether to decode new value. - Applies only if queryData is a string.
Returns
Reference to this URI object.
code »setReadOnly ( isReadOnly )!goog.Uri

Sets whether Uri is read only. If this goog.Uri is read-only, + string then it will be treated as the sole value.Returns

Reference to this URI object.
code »setPath ( newPath, opt_decode )!goog.Uri

Sets the path.

Parameters
newPath: string
New path value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setPort ( newPort )!goog.Uri

Sets the port number.

Parameters
newPort: *
Port number. Will be explicitly casted to a number.
Returns
Reference to this URI object.
code »setQuery ( newQuery, opt_decode )!goog.Uri

Sets the URI query.

Parameters
newQuery: string
New query value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setQueryData ( queryData, opt_decode )!goog.Uri

Sets the query data.

Parameters
queryData: (goog.Uri.QueryData|string|undefined)
QueryData object.
opt_decode: boolean=
Optional param for whether to decode new value. + Applies only if queryData is a string.
Returns
Reference to this URI object.
code »setReadOnly ( isReadOnly )!goog.Uri

Sets whether Uri is read only. If this goog.Uri is read-only, enforceReadOnly_ will be called at the start of any function that may modify - this Uri.

Parameters
isReadOnly: boolean
whether this goog.Uri should be read only.
Returns
Reference to this Uri object.
code »setScheme ( newScheme, opt_decode )!goog.Uri

Sets the scheme/protocol.

Parameters
newScheme: string
New scheme value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setUserInfo ( newUserInfo, opt_decode )!goog.Uri

Sets the userInfo.

Parameters
newUserInfo: string
New userInfo value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
Returns
The string form of the url.

Instance Properties

Domain part, e.g. "www.google.com".

The fragment without the #.

Whether or not to ignore case when comparing query params.

Whether or not this Uri should be treated as Read Only.

Path, e.g. "/tests/img.png".

Port, e.g. 8080.

Object representing query data.

Scheme such as "http".

User credentials in the form "username:password".

Static Functions

code »goog.Uri.create ( opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query, opt_fragment, opt_ignoreCase )!goog.Uri

Creates a new goog.Uri object from unencoded parts.

Parameters
opt_scheme: ?string=
Scheme/protocol or full URI to parse.
opt_userInfo: ?string=
username:password.
opt_domain: ?string=
www.google.com.
opt_port: ?number=
9830.
opt_path: ?string=
/some/path/to/a/file.html.
opt_query: (string|goog.Uri.QueryData)=
a=1&b=2.
opt_fragment: ?string=
The fragment without the #.
opt_ignoreCase: boolean=
Whether to ignore parameter name case in - #getParameterValue.
Returns
The new URI object.

Decodes a value or returns the empty string if it isn't defined or empty.

Parameters
val: (string|undefined)
Value to decode.
Returns
Decoded value.

Converts a character in [\01-\177] to its unicode character equivalent.

Parameters
ch: string
One character string.
Returns
Encoded string.
code »goog.Uri.encodeSpecialChars_ ( unescapedPart, extra )?string

If unescapedPart is non null, then escapes any characters in it that aren't + this Uri.

Parameters
isReadOnly: boolean
whether this goog.Uri should be read only.
Returns
Reference to this Uri object.
code »setScheme ( newScheme, opt_decode )!goog.Uri

Sets the scheme/protocol.

Parameters
newScheme: string
New scheme value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
code »setUserInfo ( newUserInfo, opt_decode )!goog.Uri

Sets the userInfo.

Parameters
newUserInfo: string
New userInfo value.
opt_decode: boolean=
Optional param for whether to decode new value.
Returns
Reference to this URI object.
Returns
The string form of the url.

Instance Properties

Domain part, e.g. "www.google.com".

The fragment without the #.

Whether or not to ignore case when comparing query params.

Whether or not this Uri should be treated as Read Only.

Path, e.g. "/tests/img.png".

Port, e.g. 8080.

Object representing query data.

Scheme such as "http".

User credentials in the form "username:password".

Static Functions

code »goog.Uri.create ( opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_query, opt_fragment, opt_ignoreCase )!goog.Uri

Creates a new goog.Uri object from unencoded parts.

Parameters
opt_scheme: ?string=
Scheme/protocol or full URI to parse.
opt_userInfo: ?string=
username:password.
opt_domain: ?string=
www.google.com.
opt_port: ?number=
9830.
opt_path: ?string=
/some/path/to/a/file.html.
opt_query: (string|goog.Uri.QueryData)=
a=1&b=2.
opt_fragment: ?string=
The fragment without the #.
opt_ignoreCase: boolean=
Whether to ignore parameter name case in + #getParameterValue.
Returns
The new URI object.
code »goog.Uri.decodeOrEmpty_ ( val, opt_preserveReserved )string

Decodes a value or returns the empty string if it isn't defined or empty.

Parameters
val: (string|undefined)
Value to decode.
opt_preserveReserved: boolean=
If true, restricted characters will + not be decoded.
Returns
Decoded value.

Converts a character in [\01-\177] to its unicode character equivalent.

Parameters
ch: string
One character string.
Returns
Encoded string.
code »goog.Uri.encodeSpecialChars_ ( unescapedPart, extra, opt_removeDoubleEncoding )?string

If unescapedPart is non null, then escapes any characters in it that aren't valid characters in a url and also escapes any special characters that - appear in extra.

Parameters
unescapedPart: *
The string to encode.
extra: RegExp
A character set of characters in [\01-\177].
Returns
null iff unescapedPart == null.
code »goog.Uri.haveSameDomain ( uri1String, uri2String )boolean

Checks whether two URIs have the same domain.

Parameters
uri1String: string
First URI string.
uri2String: string
Second URI string.
Returns
true if the two URIs have the same domain; false otherwise.
code »goog.Uri.parse ( uri, opt_ignoreCase )!goog.Uri

Creates a uri from the string form. Basically an alias of new goog.Uri(). + appear in extra.

Parameters
unescapedPart: *
The string to encode.
extra: RegExp
A character set of characters in [\01-\177].
opt_removeDoubleEncoding: boolean=
If true, remove double percent + encoding.
Returns
null iff unescapedPart == null.
code »goog.Uri.haveSameDomain ( uri1String, uri2String )boolean

Checks whether two URIs have the same domain.

Parameters
uri1String: string
First URI string.
uri2String: string
Second URI string.
Returns
true if the two URIs have the same domain; false otherwise.
code »goog.Uri.parse ( uri, opt_ignoreCase )!goog.Uri

Creates a uri from the string form. Basically an alias of new goog.Uri(). If a Uri object is passed to parse then it will return a clone of the object.

Parameters
uri: *
Raw URI string or instance of Uri object.
opt_ignoreCase: boolean=
Whether to ignore the case of parameter - names in #getParameterValue.
Returns
The new URI object.

Removes dot segments in given path component, as described in - RFC 3986, section 5.2.4.

Parameters
path: string
A non-empty path component.
Returns
Path component with removed dot segments.

Resolves a relative Uri against a base Uri, accepting both strings and - Uri objects.

Parameters
base: *
Base Uri.
rel: *
Relative Uri.
Returns
Resolved uri.

Static Properties

Parameter name added to stop caching.

If true, we preserve the type of query parameters set programmatically. + names in #getParameterValue.Returns

The new URI object.

Removes dot segments in given path component, as described in + RFC 3986, section 5.2.4.

Parameters
path: string
A non-empty path component.
Returns
Path component with removed dot segments.
code »goog.Uri.removeDoubleEncoding_ ( doubleEncodedString )string

Removes double percent-encoding from a string.

Parameters
doubleEncodedString: string
String
Returns
String with double encoding removed.

Resolves a relative Uri against a base Uri, accepting both strings and + Uri objects.

Parameters
base: *
Base Uri.
rel: *
Relative Uri.
Returns
Resolved uri.

Static Properties

Parameter name added to stop caching.

If true, we preserve the type of query parameters set programmatically. This means that if you set a parameter to a boolean, and then call getParameterValue, you will get a boolean back. @@ -59,5 +67,6 @@ If false, we will coerce parameters to strings, just as they would appear in real URIs. - TODO(nicksantos): Remove this once people have time to fix all tests.

Regular expression for characters that are disallowed in an absolute path.

Regular expression for characters that are disallowed in the fragment.

Regular expression for characters that are disallowed in the query.

Regular expression for characters that are disallowed in a relative path.

Regular expression for characters that are disallowed in the scheme or - userInfo part of the URI.

\ No newline at end of file + TODO(nicksantos): Remove this once people have time to fix all tests.

Regular expression for characters that are disallowed in an absolute path.

Regular expression for characters that are disallowed in the fragment.

Regular expression for characters that are disallowed in the query.

Regular expression for characters that are disallowed in a relative path. + Colon is included due to RFC 3986 3.3.

Regular expression for characters that are disallowed in the scheme or + userInfo part of the URI.

\ No newline at end of file diff --git a/docs/class_goog_Uri_QueryData.html b/docs/class_goog_Uri_QueryData.html index 2af4722..f24f8dd 100644 --- a/docs/class_goog_Uri_QueryData.html +++ b/docs/class_goog_Uri_QueryData.html @@ -1,34 +1,34 @@ -goog.Uri.QueryData

Class goog.Uri.QueryData

code »

Class used to represent URI query parameters. It is essentially a hash of +goog.Uri.QueryData

Class goog.Uri.QueryData

code »

Class used to represent URI query parameters. It is essentially a hash of name-value pairs, though a name can be present more than once. Has the same interface as the collections in goog.structs.

Constructor

goog.Uri.QueryData ( opt_query, opt_uri, opt_ignoreCase )
Parameters
opt_query: ?string=
Optional encoded query string to parse into the object.
opt_uri: goog.Uri=
Optional uri object that should have its cache invalidated when this object updates. Deprecated -- this is no longer required.
opt_ignoreCase: boolean=
If true, ignore the case of the parameter - name in #get.
Show:

Instance Methods

code »add ( key, value )!goog.Uri.QueryData

Adds a key value pair.

Parameters
key: string
Name.
value: *
Value.
Returns
Instance of this object.

Clears the parameters.

Clone the query data instance.

Returns
New instance of the QueryData object.

Whether there is a parameter with the given name

Parameters
key: string
The parameter name to check for.
Returns
Whether there is a parameter with the given name.

Whether there is a parameter with the given value.

Parameters
value: *
The value to check for.
Returns
Whether there is a parameter with the given value.

If the underlying key map is not yet initialized, it parses the - query string and fills the map with parsed data.

code »extend ( var_args )

Extends a query data object with another query data or map like object. This + name in #get.

Show:

Instance Methods

code »add ( key, value )!goog.Uri.QueryData

Adds a key value pair.

Parameters
key: string
Name.
value: *
Value.
Returns
Instance of this object.

Clears the parameters.

Clone the query data instance.

Returns
New instance of the QueryData object.

Whether there is a parameter with the given name

Parameters
key: string
The parameter name to check for.
Returns
Whether there is a parameter with the given name.

Whether there is a parameter with the given value.

Parameters
value: *
The value to check for.
Returns
Whether there is a parameter with the given value.

If the underlying key map is not yet initialized, it parses the + query string and fills the map with parsed data.

code »extend ( var_args )

Extends a query data object with another query data or map like object. This operates 'in-place', it does not create a new QueryData object.

Parameters
var_args: ...(goog.Uri.QueryData|goog.structs.Map|Object)
The object - from which key value pairs will be copied.

Removes all keys that are not in the provided list. (Modifies this object.)

Parameters
keys: Array.<string>
The desired keys.
Returns
a reference to this object.
code »get ( key, opt_default )*

Returns the first value associated with the key. If the query data has no + from which key value pairs will be copied.

Removes all keys that are not in the provided list. (Modifies this object.)

Parameters
keys: Array.<string>
The desired keys.
Returns
a reference to this object.
code »get ( key, opt_default )*

Returns the first value associated with the key. If the query data has no such key this will return undefined or the optional default.

Parameters
key: string
The name of the parameter to get the value for.
opt_default: *=
The default value to return if the query data has no such key.
Returns
The first string value associated with the key, or opt_default - if there's no value.
Returns
The number of parameters.

Helper function to get the key name from a JavaScript object. Converts - the object to a string, and to lower case if necessary.

Parameters
arg: *
The object to get a key name from.
Returns
valid key name which can be looked up in #keyMap_.

Returns all the keys of the parameters. If a key is used multiple times - it will be included multiple times in the returned array

Returns
All the keys of the parameters.
code »getValues ( opt_key )!Array

Returns all the values of the parameters with the given name. If the query + if there's no value.

Returns
The number of parameters.

Helper function to get the key name from a JavaScript object. Converts + the object to a string, and to lower case if necessary.

Parameters
arg: *
The object to get a key name from.
Returns
valid key name which can be looked up in #keyMap_.

Returns all the keys of the parameters. If a key is used multiple times + it will be included multiple times in the returned array

Returns
All the keys of the parameters.
code »getValues ( opt_key )!Array

Returns all the values of the parameters with the given name. If the query data has no such key this will return an empty array. If no key is given - all values wil be returned.

Parameters
opt_key: string=
The name of the parameter to get the values for.
Returns
All the values of the parameters with the given name.

Invalidate the cache.

Returns
Whether we have any parameters.

Removes all the params with the given key.

Parameters
key: string
Name.
Returns
Whether any parameter was removed.
code »set ( key, value )!goog.Uri.QueryData

Sets a key value pair and removes all other keys with the same value.

Parameters
key: string
Name.
value: *
Value.
Returns
Instance of this object.
code »setIgnoreCase ( ignoreCase )

Ignore case in parameter names. + all values wil be returned.

Parameters
opt_key: string=
The name of the parameter to get the values for.
Returns
All the values of the parameters with the given name.

Invalidate the cache.

Returns
Whether we have any parameters.

Removes all the params with the given key.

Parameters
key: string
Name.
Returns
Whether any parameter was removed.
code »set ( key, value )!goog.Uri.QueryData

Sets a key value pair and removes all other keys with the same value.

Parameters
key: string
Name.
value: *
Value.
Returns
Instance of this object.
code »setIgnoreCase ( ignoreCase )

Ignore case in parameter names. NOTE: If there are already key/value pairs in the QueryData, and - ignoreCase_ is set to false, the keys will all be lower-cased.

Parameters
ignoreCase: boolean
whether this goog.Uri should ignore case.
code »setValues ( key, values )

Sets the values for a key. If the key already exists, this will - override all of the existing values that correspond to the key.

Parameters
key: string
The key to set values for.
values: Array
The values to set.
Returns
Decoded query string.
Returns
Encoded query string.

Instance Properties

The number of params, or null if it requires computing.

Encoded query string, or null if it requires computing from the key map.

If true, ignore the case of the parameter name in #get.

The map containing name/value or name/array-of-values pairs. + ignoreCase_ is set to false, the keys will all be lower-cased.

Parameters
ignoreCase: boolean
whether this goog.Uri should ignore case.
code »setValues ( key, values )

Sets the values for a key. If the key already exists, this will + override all of the existing values that correspond to the key.

Parameters
key: string
The key to set values for.
values: Array
The values to set.
Returns
Decoded query string.
Returns
Encoded query string.

Instance Properties

The number of params, or null if it requires computing.

Encoded query string, or null if it requires computing from the key map.

If true, ignore the case of the parameter name in #get.

The map containing name/value or name/array-of-values pairs. May be null if it requires parsing from the query string. We need to use a Map because we cannot guarantee that the key names will - not be problematic for IE.

Static Functions

code »goog.Uri.QueryData.createFromKeysValues ( keys, values, opt_uri, opt_ignoreCase )!goog.Uri.QueryData

Creates a new query data instance from parallel arrays of parameter names + not be problematic for IE.

Static Functions

code »goog.Uri.QueryData.createFromKeysValues ( keys, values, opt_uri, opt_ignoreCase )!goog.Uri.QueryData

Creates a new query data instance from parallel arrays of parameter names and values. Allows for duplicate parameter names. Throws an error if the lengths of the arrays differ.

Parameters
keys: Array.<string>
Parameter names.
values: Array
Parameter values.
opt_uri: goog.Uri=
URI object that should have its cache invalidated when this object updates.
opt_ignoreCase: boolean=
If true, ignore the case of the parameter - name in #get.
Returns
The populated query data instance.
code »goog.Uri.QueryData.createFromMap ( map, opt_uri, opt_ignoreCase )!goog.Uri.QueryData

Creates a new query data instance from a map of names and values.

Parameters
map: (!goog.structs.Map|!Object)
Map of string parameter + name in #get.
Returns
The populated query data instance.
code »goog.Uri.QueryData.createFromMap ( map, opt_uri, opt_ignoreCase )!goog.Uri.QueryData

Creates a new query data instance from a map of names and values.

Parameters
map: (!goog.structs.Map|!Object)
Map of string parameter names to parameter value. If parameter value is an array, it is treated as if the key maps to each individual value in the array.
opt_uri: goog.Uri=
URI object that should have its cache invalidated when this object updates.
opt_ignoreCase: boolean=
If true, ignore the case of the parameter - name in #get.
Returns
The populated query data instance.
\ No newline at end of file + name in #get.Returns
The populated query data instance.
\ No newline at end of file diff --git a/docs/class_goog_asserts_AssertionError.html b/docs/class_goog_asserts_AssertionError.html index 5ad5eab..9b81597 100644 --- a/docs/class_goog_asserts_AssertionError.html +++ b/docs/class_goog_asserts_AssertionError.html @@ -1,4 +1,4 @@ -goog.asserts.AssertionError

Class goog.asserts.AssertionError

code »
Error
+goog.asserts.AssertionError

Class goog.asserts.AssertionError

code »
Errorgoog.debug.Error
-      └ goog.asserts.AssertionError

Error object for failed assertions.

Constructor

goog.asserts.AssertionError ( messagePattern, messageArgs )
Parameters
messagePattern: string
The pattern that was used to form message.
messageArgs: !Array
The items to substitute into the pattern.
Show:

Instance Properties

Defined in goog.asserts.AssertionError

The message pattern used to format the error message. Error handlers can - use this to uniquely identify the assertion.

Static Properties

\ No newline at end of file + └ goog.asserts.AssertionError

Error object for failed assertions.

Constructor

goog.asserts.AssertionError ( messagePattern, messageArgs )
Parameters
messagePattern: string
The pattern that was used to form message.
messageArgs: !Array
The items to substitute into the pattern.
Show:

Instance Properties

Defined in goog.asserts.AssertionError

The message pattern used to format the error message. Error handlers can + use this to uniquely identify the assertion.

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_async_run_WorkItem_.html b/docs/class_goog_async_run_WorkItem_.html new file mode 100644 index 0000000..4cb9c4d --- /dev/null +++ b/docs/class_goog_async_run_WorkItem_.html @@ -0,0 +1 @@ +goog.async.run.WorkItem_

Class goog.async.run.WorkItem_

code »

Constructor

goog.async.run.WorkItem_ ( fn, scope )
Parameters
fn
scope
Show:

Instance Methods

Instance Properties

code »scope : (Object|null|undefined)
\ No newline at end of file diff --git a/docs/class_goog_debug_Error.html b/docs/class_goog_debug_Error.html index 15eb56f..0cf370c 100644 --- a/docs/class_goog_debug_Error.html +++ b/docs/class_goog_debug_Error.html @@ -1,2 +1,2 @@ goog.debug.Error

Class goog.debug.Error

code »
Error
-  └ goog.debug.Error

Base class for custom error objects.

Constructor

goog.debug.Error ( opt_msg )
Parameters
opt_msg: *=
The message associated with the error.
Show:

Static Properties

\ No newline at end of file + └ goog.debug.Error

Base class for custom error objects.

Constructor

goog.debug.Error ( opt_msg )
Parameters
opt_msg: *=
The message associated with the error.
Show:

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_debug_LogBuffer.html b/docs/class_goog_debug_LogBuffer.html new file mode 100644 index 0000000..51f289c --- /dev/null +++ b/docs/class_goog_debug_LogBuffer.html @@ -0,0 +1,2 @@ +goog.debug.LogBuffer

Class goog.debug.LogBuffer

code »

Creates the log buffer.

Constructor

goog.debug.LogBuffer ( )

Classes

goog.debug.LogBuffer.instance_
Creates the log buffer.
Show:

Instance Methods

code »addRecord ( level, msg, loggerName )!goog.debug.LogRecord

Adds a log record to the buffer, possibly overwriting the oldest record.

Parameters
level: goog.debug.Logger.Level
One of the level identifiers.
msg: string
The string message.
loggerName: string
The name of the source logger.
Returns
The log record.

Removes all buffered log records.

Calls the given function for each buffered log record, starting with the + oldest one.

Parameters
func: function(!goog.debug.LogRecord)
The function to call.

Instance Properties

The array to store the records.

The index of the most recently added record or -1 if there are no records.

Whether the buffer is at capacity.

Static Functions

A static method that always returns the same instance of LogBuffer.

Returns
The LogBuffer singleton instance.
Returns
Whether the log buffer is enabled.

Static Properties

Compiler Constants

\ No newline at end of file diff --git a/docs/class_goog_debug_LogRecord.html b/docs/class_goog_debug_LogRecord.html new file mode 100644 index 0000000..32c248e --- /dev/null +++ b/docs/class_goog_debug_LogRecord.html @@ -0,0 +1,12 @@ +goog.debug.LogRecord

Class goog.debug.LogRecord

code »

LogRecord objects are used to pass logging requests between + the logging framework and individual log Handlers.

Constructor

goog.debug.LogRecord ( level, msg, loggerName, opt_time, opt_sequenceNumber )
Parameters
level: goog.debug.Logger.Level
One of the level identifiers.
msg: string
The string message.
loggerName: string
The name of the source logger.
opt_time: number=
Time this log record was created if other than now. + If 0, we use #goog.now.
opt_sequenceNumber: number=
Sequence number of this log record. This + should only be passed in when restoring a log record from persistence.
Show:

Instance Methods

Get the exception that is part of the log record.

Returns
the exception.

Get the exception text that is part of the log record.

Returns
Exception text.

Get the logging message level, for example Level.SEVERE.

Returns
the logging message level.

Get the source Logger's name.

Returns
source logger name (may be null).

Get the "raw" log message, before localization or formatting.

Returns
the raw message string.

Get event time in milliseconds since 1970.

Returns
event time in millis since 1970.

Get the sequence number. +

+ Sequence numbers are normally assigned in the LogRecord + constructor, which assigns unique sequence numbers to + each new LogRecord in increasing order.

Returns
the sequence number.
code »reset ( level, msg, loggerName, opt_time, opt_sequenceNumber )

Sets all fields of the log record.

Parameters
level: goog.debug.Logger.Level
One of the level identifiers.
msg: string
The string message.
loggerName: string
The name of the source logger.
opt_time: number=
Time this log record was created if other than now. + If 0, we use #goog.now.
opt_sequenceNumber: number=
Sequence number of this log record. This + should only be passed in when restoring a log record from persistence.
code »setException ( exception )

Set the exception that is part of the log record.

Parameters
exception: Object
the exception.

Set the exception text that is part of the log record.

Parameters
text: string
The exception text.
code »setLevel ( level )

Set the logging message level, for example Level.SEVERE.

Parameters
level: goog.debug.Logger.Level
the logging message level.
code »setLoggerName ( loggerName )

Get the source Logger's name.

Parameters
loggerName: string
source logger name (may be null).

Set the "raw" log message, before localization or formatting.

Parameters
msg: string
the raw message string.

Set event time in milliseconds since 1970.

Parameters
time: number
event time in millis since 1970.

Instance Properties

Exception text associated with the record

Exception associated with the record

Level of the LogRecord

Name of the logger that created the record.

Message associated with the record

Sequence number for the LogRecord. Each record has a unique sequence number + that is greater than all log records created before it.

Time the LogRecord was created.

Static Properties

A sequence counter for assigning increasing sequence numbers to LogRecord + objects.

Compiler Constants

\ No newline at end of file diff --git a/docs/class_goog_debug_Logger.html b/docs/class_goog_debug_Logger.html new file mode 100644 index 0000000..6d70260 --- /dev/null +++ b/docs/class_goog_debug_Logger.html @@ -0,0 +1,59 @@ +goog.debug.Logger

Class goog.debug.Logger

code »

The Logger is an object used for logging debug messages. Loggers are + normally named, using a hierarchical dot-separated namespace. Logger names + can be arbitrary strings, but they should normally be based on the package + name or class name of the logged component, such as goog.net.BrowserChannel. + + The Logger object is loosely based on the java class + java.util.logging.Logger. It supports different levels of filtering for + different loggers. + + The logger object should never be instantiated by application code. It + should always use the goog.debug.Logger.getLogger function.

Constructor

goog.debug.Logger ( name )
Parameters
name: string
The name of the Logger.

Classes

goog.debug.Logger.Level
The Level class defines a set of standard logging levels that + can be used to control logging output.
Show:

Instance Methods

code »addChild_ ( name, logger )

Adds a child to this logger. This is used for setting up the logger tree.

Parameters
name: string
The leaf name of the child.
logger: goog.debug.Logger
The child logger.
code »addHandler ( handler )

Adds a handler to the logger. This doesn't use the event system because + we want to be able to add logging to the event system.

Parameters
handler: Function
Handler function to add.
code »callPublish_ ( logRecord )

Calls the handlers for publish.

Parameters
logRecord: goog.debug.LogRecord
The log record to publish.
code »config ( msg, opt_exception )

Logs a message at the Logger.Level.CONFIG level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.
code »doLogRecord_ ( logRecord )

Logs a LogRecord.

Parameters
logRecord: goog.debug.LogRecord
A log record to log.
code »fine ( msg, opt_exception )

Logs a message at the Logger.Level.FINE level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.
code »finer ( msg, opt_exception )

Logs a message at the Logger.Level.FINER level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.
code »finest ( msg, opt_exception )

Logs a message at the Logger.Level.FINEST level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.

Returns the children of this logger as a map of the child name to the logger.

Returns
The map where the keys are the child leaf names and the + values are the Logger objects.

Returns the effective level of the logger based on its ancestors' levels.

Returns
The level.

Gets the log level specifying which message levels will be logged by this + logger. Message levels lower than this value will be discarded. + The level value Level.OFF can be used to turn off logging. If the level + is null, it means that this node should inherit its level from its nearest + ancestor with a specific (non-null) level value.

Returns
The level.
code »getLogRecord ( level, msg, opt_exception, opt_fnStackContext )!goog.debug.LogRecord

Creates a new log record and adds the exception (if present) to it.

Parameters
level: goog.debug.Logger.Level
One of the level identifiers.
msg: string
The string message.
opt_exception: (Error|Object)=
An exception associated with the + message.
opt_fnStackContext: Function=
A function to use as the base + of the stack trace used in the log record.
Returns
A log record.

Gets the name of this logger.

Returns
The name of this logger.

Returns the parent of this logger.

Returns
The parent logger or null if this is the root.
code »info ( msg, opt_exception )

Logs a message at the Logger.Level.INFO level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.

Checks if a message of the given level would actually be logged by this + logger. This check is based on the Loggers effective level, which may be + inherited from its parent.

Parameters
level: goog.debug.Logger.Level
The level to check.
Returns
Whether the message would be logged.
code »log ( level, msg, opt_exception )

Logs a message. If the logger is currently enabled for the + given message level then the given message is forwarded to all the + registered output Handler objects.

Parameters
level: goog.debug.Logger.Level
One of the level identifiers.
msg: goog.debug.Loggable
The message to log.
opt_exception: (Error|Object)=
An exception associated with the + message.
code »logRecord ( logRecord )

Logs a LogRecord. If the logger is currently enabled for the + given message level then the given message is forwarded to all the + registered output Handler objects.

Parameters
logRecord: goog.debug.LogRecord
A log record to log.

Removes a handler from the logger. This doesn't use the event system because + we want to be able to add logging to the event system.

Parameters
handler: Function
Handler function to remove.
Returns
Whether the handler was removed.
code »setLevel ( level )

Set the log level specifying which message levels will be logged by this + logger. Message levels lower than this value will be discarded. + The level value Level.OFF can be used to turn off logging. If the new level + is null, it means that this node should inherit its level from its nearest + ancestor with a specific (non-null) level value.

Parameters
level: goog.debug.Logger.Level
The new level.

Sets the parent of this logger. This is used for setting up the logger tree.

Parameters
parent: goog.debug.Logger
The parent logger.
code »severe ( msg, opt_exception )

Logs a message at the Logger.Level.SEVERE level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.
code »shout ( msg, opt_exception )

Logs a message at the Logger.Level.SHOUT level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.
code »warning ( msg, opt_exception )

Logs a message at the Logger.Level.WARNING level. + If the logger is currently enabled for the given message level then the + given message is forwarded to all the registered output Handler objects.

Parameters
msg: goog.debug.Loggable
The message to log.
opt_exception: Error=
An exception associated with the message.

Instance Properties

Map of children loggers. The keys are the leaf names of the children and + the values are the child loggers.

Handlers that are listening to this logger.

Level that this logger only filters above. Null indicates it should + inherit from the parent.

Name of the Logger. Generally a dot-separated namespace

Static Functions

Deprecated: use goog.log instead. http://go/goog-debug-logger-deprecated

Finds or creates a logger for a named subsystem. If a logger has already been + created with the given name it is returned. Otherwise a new logger is + created. If a new logger is created its log level will be configured based + on the LogManager configuration and it will configured to also send logging + output to its parent's handlers. It will be registered in the LogManager + global namespace.

Parameters
name: string
A name for the logger. This should be a dot-separated + name and should normally be based on the package name or class name of the + subsystem, such as goog.net.BrowserChannel.
Returns
The named logger.

Logs a message to profiling tools, if available. + https://developers.google.com/web-toolkit/speedtracer/logging-api + http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx

Parameters
msg: string
The message to log.

Static Properties

Compiler Constants

\ No newline at end of file diff --git a/docs/class_goog_debug_Logger_Level.html b/docs/class_goog_debug_Logger_Level.html new file mode 100644 index 0000000..3454ffd --- /dev/null +++ b/docs/class_goog_debug_Logger_Level.html @@ -0,0 +1,32 @@ +goog.debug.Logger.Level

Class goog.debug.Logger.Level

code »

The Level class defines a set of standard logging levels that + can be used to control logging output. The logging Level objects + are ordered and are specified by ordered integers. Enabling logging + at a given level also enables logging at all higher levels. +

+ Clients should normally use the predefined Level constants such + as Level.SEVERE. +

+ The levels in descending order are: +

    +
  • SEVERE (highest value) +
  • WARNING +
  • INFO +
  • CONFIG +
  • FINE +
  • FINER +
  • FINEST (lowest value) +
+ In addition there is a level OFF that can be used to turn + off logging, and a level ALL that can be used to enable + logging of all messages.

Constructor

goog.debug.Logger.Level ( name, value )
Parameters
name: string
The name of the level.
value: number
The numeric value of the level.
Show:

Instance Methods

Returns
String representation of the logger level.

Instance Properties

The name of the level

The numeric value of the level

Static Functions

Creates the predefined levels cache and populates it.

Gets the predefined level with the given name.

Parameters
name: string
The name of the level.
Returns
The level, or null if none found.

Gets the highest predefined level <= #value.

Parameters
value: number
Level value.
Returns
The level, or null if none found.

Static Properties

ALL indicates that all messages should be logged. + This level is initialized to 0.

CONFIG is a message level for static configuration messages. + This level is initialized to 700.

FINE is a message level providing tracing information. + This level is initialized to 500.

FINER indicates a fairly detailed tracing message. + This level is initialized to 400.

FINEST indicates a highly detailed tracing message. + This level is initialized to 300.

INFO is a message level for informational messages. + This level is initialized to 800.

OFF is a special level that can be used to turn off logging. + This level is initialized to Infinity.

SEVERE is a message level indicating a serious failure. + This level is initialized to 1000.

SHOUT is a message level for extra debugging loudness. + This level is initialized to 1200.

WARNING is a message level indicating a potential problem. + This level is initialized to 900.

A lookup map used to find the level object based on the name or value of + the level object.

\ No newline at end of file diff --git a/docs/class_goog_dom_DomHelper.html b/docs/class_goog_dom_DomHelper.html new file mode 100644 index 0000000..caa6f2e --- /dev/null +++ b/docs/class_goog_dom_DomHelper.html @@ -0,0 +1,104 @@ +goog.dom.DomHelper

Class goog.dom.DomHelper

code »

Create an instance of a DOM helper with a new document object.

Constructor

goog.dom.DomHelper ( opt_document )
Parameters
opt_document: Document=
Document object to associate with this + DOM helper.
Show:

Instance Methods

code »$ ( element )Element

Alias for getElement.

Parameters
element: (string|Element)
Element ID or a DOM node.
Returns
The element with the given ID, or the node passed in.
code »$$ ( opt_tag, opt_class, opt_el ){length: number}
Deprecated: Use DomHelper getElementsByTagNameAndClass.

Alias for getElementsByTagNameAndClass.

Parameters
opt_tag: ?string=
Element tag name.
opt_class: ?string=
Optional class name.
opt_el: Element=
Optional element to look in.
Returns
Array-like list of elements (only a length + property and numerical indices are guaranteed to exist).
code »$dom ( tagName, opt_attributes, var_args )!Element

Alias for createDom.

Parameters
tagName: string
Tag to create.
opt_attributes: (Object|string)=
If object, then a map of name-value + pairs for attributes. If a string, then this is the className of the new + element.
var_args: ...goog.dom.Appendable
Further DOM nodes or strings for + text nodes. If one of the var_args is an array, its children will be + added as childNodes instead.
Returns
Reference to a DOM node.
code »append ( parent, var_args )

Appends a node with text or other nodes.

Parameters
parent: !Node
The node to append nodes to.
var_args: ...goog.dom.Appendable
The things to append to the node. + If this is a Node it is appended as is. + If this is a string then a text node is appended. + If this is an array like object then fields 0 to length - 1 are appended.
code »appendChild ( parent, child )

Appends a child to a node.

Parameters
parent: Node
Parent.
child: Node
Child.

Determines if the given node can contain children, intended to be used for + HTML generation.

Parameters
node: Node
The node to check.
Returns
Whether the node can contain children.
code »compareNodeOrder ( node1, node2 )number

Compares the document order of two nodes, returning 0 if they are the same + node, a negative number if node1 is before node2, and a positive number if + node2 is before node1. Note that we compare the order the tags appear in the + document so in the tree text the B node is considered to be + before the I node.

Parameters
node1: Node
The first node to compare.
node2: Node
The second node to compare.
Returns
0 if the nodes are the same node, a negative number if node1 + is before node2, and a positive number if node2 is before node1.
code »contains ( parent, descendant )boolean

Whether a node contains another node.

Parameters
parent: Node
The node that should contain the other node.
descendant: Node
The node to test presence of.
Returns
Whether the parent node contains the descendent node.
code »createDom ( tagName, opt_attributes, var_args )!Element

Returns a dom node with a set of attributes. This function accepts varargs + for subsequent nodes to be added. Subsequent nodes will be added to the + first node as childNodes. + + So: + createDom('div', null, createDom('p'), createDom('p')); + would return a div with two child paragraphs + + An easy way to move all child nodes of an existing element to a new parent + element is: + createDom('div', null, oldElement.childNodes); + which will remove all child nodes from the old element and add them as + child nodes of the new DIV.

Parameters
tagName: string
Tag to create.
opt_attributes: (Object|string)=
If object, then a map of name-value + pairs for attributes. If a string, then this is the className of the new + element.
var_args: ...goog.dom.Appendable
Further DOM nodes or + strings for text nodes. If one of the var_args is an array or + NodeList, its elements will be added as childNodes instead.
Returns
Reference to a DOM node.

Creates a new element.

Parameters
name: string
Tag name.
Returns
The new element.
code »createTable ( rows, columns, opt_fillWithNbsp )!Element

Create a table.

Parameters
rows: number
The number of rows in the table. Must be >= 1.
columns: number
The number of columns in the table. Must be >= 1.
opt_fillWithNbsp: boolean=
If true, fills table entries with nsbps.
Returns
The created table.
code »createTextNode ( content )!Text

Creates a new text node.

Parameters
content: (number|string)
Content.
Returns
The new text node.

Find the deepest common ancestor of the given nodes.

Parameters
var_args: ...Node
The nodes to find a common ancestor of.
Returns
The common ancestor of the nodes, or null if there is none. + null will only be returned if two or more of the nodes are from different + documents.
code »findNode ( root, p )(Node|undefined)

Finds the first descendant node that matches the filter function. This does + a depth first search.

Parameters
root: Node
The root of the tree to search.
p: function(Node): boolean
The filter function.
Returns
The found node or undefined if none is found.
code »findNodes ( root, p )Array.<Node>

Finds all the descendant nodes that matches the filter function. This does a + depth first search.

Parameters
root: Node
The root of the tree to search.
p: function(Node): boolean
The filter function.
Returns
The found nodes or an empty array if none are found.

Flattens an element. That is, removes it and replace it with its children.

Parameters
element: Element
The element to flatten.
Returns
The original element, detached from the document + tree, sans children, or undefined if the element was already not in the + document.

Determines the active element in the given document.

Parameters
opt_doc: Document=
The document to look in.
Returns
The active element.
code »getAncestor ( element, matcher, opt_includeNode, opt_maxSearchSteps )Node

Walks up the DOM hierarchy returning the first ancestor that passes the + matcher function.

Parameters
element: Node
The DOM node to start with.
matcher: function(Node): boolean
A function that returns true if the + passed node matches the desired criteria.
opt_includeNode: boolean=
If true, the node itself is included in + the search (the first call to the matcher will pass startElement as + the node to test).
opt_maxSearchSteps: number=
Maximum number of levels to search up the + dom.
Returns
DOM node that matched the matcher, or null if there was + no match.
code »getAncestorByClass ( element, class )Element

Walks up the DOM hierarchy returning the first ancestor that has the passed + class name. If the passed element matches the specified criteria, the + element itself is returned.

Parameters
element: Node
The DOM node to start with.
class: string
The class name to match.
Returns
The first ancestor that matches the passed criteria, or + null if none match.
code »getAncestorByTagNameAndClass ( element, opt_tag, opt_class )Element

Walks up the DOM hierarchy returning the first ancestor that has the passed + tag name and/or class name. If the passed element matches the specified + criteria, the element itself is returned.

Parameters
element: Node
The DOM node to start with.
opt_tag: ?(goog.dom.TagName|string)=
The tag name to match (or + null/undefined to match only based on class name).
opt_class: ?string=
The class name to match (or null/undefined to + match only based on tag name).
Returns
The first ancestor that matches the passed criteria, or + null if no match is found.
code »getChildren ( element )!(Array|NodeList)

Returns an array containing just the element children of the given element.

Parameters
element: Element
The element whose element children we want.
Returns
An array or array-like list of just the element + children of the given element.

Gets the document object being used by the dom library.

Returns
Document object.

Calculates the height of the document.

Returns
The height of the document.

Gets the document scroll distance as a coordinate object.

Returns
Object with properties 'x' and 'y'.

Gets the document scroll element.

Returns
Scrolling element.

Gets the dom helper object for the document where the element resides.

Parameters
opt_node: Node=
If present, gets the DomHelper for this node.
Returns
The DomHelper.
code »getElement ( element )Element

Alias for getElementById. If a DOM node is passed in then we just + return that.

Parameters
element: (string|Element)
Element ID or a DOM node.
Returns
The element with the given ID, or the node passed in.
code »getElementByClass ( className, opt_el )Element

Returns the first element we find matching the provided class name.

Parameters
className: string
the name of the class to look for.
opt_el: (Element|Document)=
Optional element to look in.
Returns
The first item found with the class name provided.
code »getElementsByClass ( className, opt_el ){length: number}

Returns an array of all the elements with the provided className.

Parameters
className: string
the name of the class to look for.
opt_el: (Element|Document)=
Optional element to look in.
Returns
The items found with the class name provided.
code »getElementsByTagNameAndClass ( opt_tag, opt_class, opt_el ){length: number}

Looks up elements by both tag and class name, using browser native functions + (querySelectorAll, getElementsByTagName or + getElementsByClassName) where possible. The returned array is a live + NodeList or a static list depending on the code path taken.

Parameters
opt_tag: ?string=
Element tag name or * for all tags.
opt_class: ?string=
Optional class name.
opt_el: (Document|Element)=
Optional element to look in.
Returns
Array-like list of elements (only a length + property and numerical indices are guaranteed to exist).

Returns the first child node that is an element.

Parameters
node: Node
The node to get the first child element of.
Returns
The first child node of node that is an element.

Cross browser function for getting the document element of an iframe.

Parameters
iframe: Element
Iframe element.
Returns
The frame content document.
code »getFrameContentWindow ( frame )Window

Cross browser function for getting the window of a frame or iframe.

Parameters
frame: Element
Frame element.
Returns
The window associated with the given frame.

Returns the last child node that is an element.

Parameters
node: Node
The node to get the last child element of.
Returns
The last child node of node that is an element.

Returns the first next sibling that is an element.

Parameters
node: Node
The node to get the next sibling element of.
Returns
The next sibling of node that is an element.

Returns the next node in source order from the given node.

Parameters
node: Node
The node.
Returns
The next node in the DOM tree, or null if this was the last + node.
code »getNodeAtOffset ( parent, offset, opt_result )Node

Returns the node at a given offset in a parent node. If an object is + provided for the optional third parameter, the node and the remainder of the + offset will stored as properties of this object.

Parameters
parent: Node
The parent node.
offset: number
The offset into the parent node.
opt_result: Object=
Object to be used to store the return value. The + return value will be stored in the form {node: Node, remainder: number} + if this object is provided.
Returns
The node at the given offset.

Returns the text length of the text contained in a node, without markup. This + is equivalent to the selection length if the node was selected, or the number + of cursor movements to traverse the node. Images & BRs take one space. New + lines are ignored.

Parameters
node: Node
The node whose text content length is being calculated.
Returns
The length of node's text content.
code »getNodeTextOffset ( node, opt_offsetParent )number

Returns the text offset of a node relative to one of its ancestors. The text + length is the same as the length calculated by + goog.dom.getNodeTextLength.

Parameters
node: Node
The node whose offset is being calculated.
opt_offsetParent: Node=
Defaults to the node's owner document's body.
Returns
The text offset.
code »getOuterHtml ( element )string

Gets the outerHTML of a node, which islike innerHTML, except that it + actually contains the HTML of the node itself.

Parameters
element: Element
The element to get the HTML of.
Returns
The outerHTML of the given element.

Returns the owner document for a node.

Parameters
node: Node
The node to get the document for.
Returns
The document owning the node.

Returns an element's parent, if it's an Element.

Parameters
element: Element
The DOM element.
Returns
The parent, or null if not an Element.

Returns the first previous sibling that is an element.

Parameters
node: Node
The node to get the previous sibling element of.
Returns
The first previous sibling of node that is + an element.

Returns the previous node in source order from the given node.

Parameters
node: Node
The node.
Returns
The previous node in the DOM tree, or null if this was the + first node.

Gets an element by id, asserting that the element is found. + + This is used when an element is expected to exist, and should fail with + an assertion error if it does not (if assertions are enabled).

Parameters
id: string
Element ID.
Returns
The element with the given ID, if it exists.
code »getRequiredElementByClass ( className, opt_root )!Element

Ensures an element with the given className exists, and then returns the + first element with the provided className.

Parameters
className: string
the name of the class to look for.
opt_root: (!Element|!Document)=
Optional element or document to look + in.
Returns
The first item found with the class name provided.
Throws
goog.asserts.AssertionError
Thrown if no element is found.

Returns the text contents of the current node, without markup. New lines are + stripped and whitespace is collapsed, such that each character would be + visible. + + In browsers that support it, innerText is used. Other browsers attempt to + simulate it via node traversal. Line breaks are canonicalized in IE.

Parameters
node: Node
The node from which we are getting content.
Returns
The text content.

Gets the dimensions of the viewport.

Parameters
opt_window: Window=
Optional window element to test. Defaults to + the window of the Dom Helper.
Returns
Object with values 'width' and 'height'.
code »getWindow ( )!Window

Gets the window object associated with the document.

Returns
The window associated with the given document.

Converts an HTML string into a node or a document fragment. A single Node + is used if the htmlString only generates a single node. If the + htmlString generates multiple nodes then these are put inside a + DocumentFragment.

Parameters
htmlString: string
The HTML string to convert.
Returns
The resulting node.
code »insertChildAt ( parent, child, index )

Insert a child at a given index. If index is larger than the number of child + nodes that the parent currently has, the node is inserted as the last child + node.

Parameters
parent: Element
The element into which to insert the child.
child: Node
The element to insert.
index: number
The index at which to insert the new child node. Must + not be negative.
code »insertSiblingAfter ( newNode, refNode )

Inserts a new node after an existing reference node (i.e., as the next + sibling). If the reference node has no parent, then does nothing.

Parameters
newNode: Node
Node to insert.
refNode: Node
Reference node to insert after.
code »insertSiblingBefore ( newNode, refNode )

Inserts a new node before an existing reference node (i.e., as the previous + sibling). If the reference node has no parent, then does nothing.

Parameters
newNode: Node
Node to insert.
refNode: Node
Reference node to insert before.

Returns true if the browser is in "CSS1-compatible" (standards-compliant) + mode, false otherwise.

Returns
True if in CSS1-compatible mode.

Whether the object looks like an Element.

Parameters
obj: ?
The object being tested for Element likeness.
Returns
Whether the object looks like an Element.
code »isFocusable ( element )boolean

Returns true if the element can be focused, i.e. it has a tab index that + allows it to receive keyboard focus (tabIndex >= 0), or it is an element + that natively supports keyboard focus.

Parameters
element: Element
Element to check.
Returns
Whether the element allows keyboard focus.

Returns true if the element has a tab index that allows it to receive + keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + natively support keyboard focus, even if they have no tab index.

Parameters
element: Element
Element to check.
Returns
Whether the element has a tab index that allows keyboard + focus.

Whether the object looks like a DOM node.

Parameters
obj: ?
The object being tested for node likeness.
Returns
Whether the object looks like a DOM node.

Returns true if the object is a NodeList. To qualify as a NodeList, + the object must have a numeric length property and an item function (which + has type 'string' on IE for some reason).

Parameters
val: Object
Object to test.
Returns
Whether the object is a NodeList.

Returns true if the specified value is a Window object. This includes the + global window for HTML pages, and iframe windows.

Parameters
obj: ?
Variable to test.
Returns
Whether the variable is a window.

Removes all the child nodes on a DOM node.

Parameters
node: Node
Node to remove children from.
code »removeNode ( node )Node

Removes a node from its parent.

Parameters
node: Node
The node to remove.
Returns
The node removed if removed; else, null.
code »replaceNode ( newNode, oldNode )

Replaces a node in the DOM tree. Will do nothing if oldNode has no + parent.

Parameters
newNode: Node
Node to insert.
oldNode: Node
Node to replace.
code »setDocument ( document )

Sets the document object.

Parameters
document: !Document
Document object.
code »setFocusableTabIndex ( element, enable )

Enables or disables keyboard focus support on the element via its tab index. + Only elements for which goog.dom.isFocusableTabIndex returns true + (or elements that natively support keyboard focus, like form elements) can + receive keyboard focus. See http://go/tabindex for more info.

Parameters
element: Element
Element whose tab index is to be changed.
enable: boolean
Whether to set or remove a tab index on the element + that supports keyboard focus.
code »setProperties ( element, properties )

Sets a number of properties on a node.

Parameters
element: Element
DOM node to set properties on.
properties: Object
Hash of property:value pairs.
code »setTextContent ( node, text )

Sets the text content of a node, with cross-browser support.

Parameters
node: Node
The node to change the text content of.
text: (string|number)
The value that should replace the node's content.

Instance Properties

Reference to the document object to use

\ No newline at end of file diff --git a/docs/class_goog_events_BrowserEvent.html b/docs/class_goog_events_BrowserEvent.html new file mode 100644 index 0000000..ed9fdc9 --- /dev/null +++ b/docs/class_goog_events_BrowserEvent.html @@ -0,0 +1,25 @@ +goog.events.BrowserEvent

Class goog.events.BrowserEvent

code »
goog.events.Event
+  └ goog.events.BrowserEvent

Accepts a browser event object and creates a patched, cross browser event + object. + The content of this object will not be initialized if no event object is + provided. If this is the case, init() needs to be invoked separately.

Constructor

goog.events.BrowserEvent ( opt_e, opt_currentTarget )
Parameters
opt_e: Event=
Browser event object.
opt_currentTarget: EventTarget=
Current target for event.

Enumerations

goog.events.BrowserEvent.MouseButton
Normalized button constants for the mouse.
Show:

Instance Methods

Defined in goog.events.BrowserEvent

Returns
The underlying browser event object.
code »init ( e, opt_currentTarget )

Accepts a browser event object and creates a patched, cross browser event + object.

Parameters
e: Event
Browser event object.
opt_currentTarget: EventTarget=
Current target for event.
code »isButton ( button )boolean

Tests to see which button was pressed during the event. This is really only + useful in IE and Gecko browsers. And in IE, it's only useful for + mousedown/mouseup events, because click only fires for the left mouse button. + + Safari 2 only reports the left button being clicked, and uses the value '1' + instead of 0. Opera only reports a mousedown event for the middle button, and + no mouse events for the right button. Opera has default behavior for left and + middle click that can only be overridden via a configuration setting. + + There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.

Parameters
button: goog.events.BrowserEvent.MouseButton
The button + to test for.
Returns
True if button was pressed.

Whether this has an "action"-producing mouse button. + + By definition, this includes left-click on windows/linux, and left-click + without the ctrl key on Macs.

Returns
The result.

Defined in goog.events.Event

Deprecated: Events don't need to be disposed.

For backwards compatibility (goog.events.Event used to inherit + goog.Disposable).

Instance Properties

Defined in goog.events.BrowserEvent

Whether alt was pressed at time of event.

Which mouse button was pressed.

Keycode of key press.

X-coordinate relative to the window.

Y-coordinate relative to the window.

Whether control was pressed at time of event.

Node that had the listener attached.

The browser event object.

Keycode of key press.

Whether the meta key was pressed at time of event.

X-coordinate relative to target.

Y-coordinate relative to target.

Whether the default platform modifier key was pressed at time of event. + (This is control for all platforms except Mac, where it's Meta.)

For mouseover and mouseout events, the related object for the event.

X-coordinate relative to the monitor.

Y-coordinate relative to the monitor.

Whether shift was pressed at time of event.

History state object, only set for PopState events where it's a copy of the + state object provided to pushState or replaceState.

Target that fired the event.

Defined in goog.events.Event

Whether the default action has been prevented. + This is a property to match the W3C specification at + #events-event-type-defaultPrevented. + Must be treated as read-only outside the class.

Whether to cancel the event in internal capture/bubble processing for IE.

Return value for in internal capture/bubble processing for IE.

Event type.

Static Properties

Static data for mapping mouse buttons.

\ No newline at end of file diff --git a/docs/class_goog_events_Event.html b/docs/class_goog_events_Event.html new file mode 100644 index 0000000..654a97d --- /dev/null +++ b/docs/class_goog_events_Event.html @@ -0,0 +1,16 @@ +goog.events.Event

Class goog.events.Event

code »

A base class for event objects, so that they can support preventDefault and + stopPropagation.

Constructor

goog.events.Event ( type, opt_target )
Parameters
type: (string|!goog.events.EventId)
Event Type.
opt_target: Object=
Reference to the object that is the target of + this event. It has to implement the EventTarget interface + declared at http://developer.mozilla.org/en/DOM/EventTarget.
Show:

Instance Methods

Deprecated: Events don't need to be disposed.

For backwards compatibility (goog.events.Event used to inherit + goog.Disposable).

Deprecated: Events don't need to be disposed.

For backwards compatibility (goog.events.Event used to inherit + goog.Disposable).

Prevents the default action, for example a link redirecting to a url.

Stops event propagation.

Instance Properties

Object that had the listener attached.

Whether the default action has been prevented. + This is a property to match the W3C specification at + #events-event-type-defaultPrevented. + Must be treated as read-only outside the class.

Whether to cancel the event in internal capture/bubble processing for IE.

Return value for in internal capture/bubble processing for IE.

TODO(user): The type should probably be + EventTarget|goog.events.EventTarget. + + Target of the event.

Event type.

Static Functions

Prevents the default action. It is equivalent to + e.preventDefault(), but can be used as the callback argument of + goog.events.listen without declaring another function.

Parameters
e: !goog.events.Event
An event.

Stops the propagation of the event. It is equivalent to + e.stopPropagation(), but can be used as the callback argument of + goog.events.listen without declaring another function.

Parameters
e: !goog.events.Event
An event.
\ No newline at end of file diff --git a/docs/class_goog_events_EventId.html b/docs/class_goog_events_EventId.html new file mode 100644 index 0000000..4511526 --- /dev/null +++ b/docs/class_goog_events_EventId.html @@ -0,0 +1,10 @@ +goog.events.EventId

Class goog.events.EventId.<T>

code »

A templated class that is used when registering for events. Typical usage: + + /** @type {goog.events.EventId.} + var myEventId = new goog.events.EventId( + goog.events.getUniqueId(('someEvent')); + + // No need to cast or declare here since the compiler knows the correct + // type of 'evt' (MyEventObj). + something.listen(myEventId, function(evt) {}); +

Constructor

goog.events.EventId ( eventId )
Parameters
eventId
Show:

Instance Methods

code »toString ( )string

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_events_EventTarget.html b/docs/class_goog_events_EventTarget.html new file mode 100644 index 0000000..2cc42c8 --- /dev/null +++ b/docs/class_goog_events_EventTarget.html @@ -0,0 +1,68 @@ +goog.events.EventTarget

Class goog.events.EventTarget

code »
goog.Disposable
+  └ goog.events.EventTarget
All implemented interfaces:
goog.disposable.IDisposable, goog.events.Listenable

An implementation of goog.events.Listenable with full W3C + EventTarget-like support (capture/bubble mechanism, stopping event + propagation, preventing default actions). + + You may subclass this class to turn your class into a Listenable. + + Unless propagation is stopped, an event dispatched by an + EventTarget will bubble to the parent returned by + getParentEventTarget. To set the parent, call + setParentEventTarget. Subclasses that don't support + changing the parent can override the setter to throw an error. + + Example usage: +

+   var source = new goog.events.EventTarget();
+   function handleEvent(e) {
+     alert('Type: ' + e.type + '; Target: ' + e.target);
+   }
+   source.listen('foo', handleEvent);
+   // Or: goog.events.listen(source, 'foo', handleEvent);
+   ...
+   source.dispatchEvent('foo');  // will call handleEvent
+   ...
+   source.unlisten('foo', handleEvent);
+   // Or: goog.events.unlisten(source, 'foo', handleEvent);
+ 

Constructor

goog.events.EventTarget ( )
Show:

Instance Methods

Defined in goog.events.EventTarget

code »addEventListener ( type, handler, opt_capture, opt_handlerScope )
Deprecated: Use #listen instead, when possible. Otherwise, use + goog.events.listen if you are passing Object + (instead of Function) as handler.

Adds an event listener to the event target. The same handler can only be + added once per the type. Even if you add the same handler multiple times + using the same type then it will only be called once when the event is + dispatched.

Parameters
type: string
The type of the event to listen for.
handler: (function(?): ?|{handleEvent: function(?): ?}|null)
The function + to handle the event. The handler can also be an object that implements + the handleEvent method which takes the event object as argument.
opt_capture: boolean=
In DOM-compliant browsers, this determines + whether the listener is fired during the capture or bubble phase + of the event.
opt_handlerScope: Object=
Object in whose scope to call + the listener.

Asserts that the event target instance is initialized properly.

code »dispatchEvent ( e )boolean
Parameters
e

Removes listeners from this object. Classes that extend EventTarget may + need to override this method in order to remove references to DOM Elements + and additional listeners.

code »fireListeners ( type, capture, eventObject )boolean
Parameters
type
capture
eventObject
code »getListener ( type, listener, capture, opt_listenerScope )(goog.events.ListenableKey|null)
Parameters
type
listener
capture
opt_listenerScope
code »getListeners ( type, capture )Array.<(goog.events.ListenableKey|null)>
Parameters
type
capture

Returns the parent of this event target to use for bubbling.

Returns
The parent EventTarget or null if + there is no parent.
code »hasListener ( opt_type, opt_capture )boolean
Parameters
opt_type
opt_capture
code »listen ( type, listener, opt_useCapture, opt_listenerScope )(goog.events.ListenableKey|null)
Parameters
type
listener
opt_useCapture
opt_listenerScope
code »listenOnce ( type, listener, opt_useCapture, opt_listenerScope )(goog.events.ListenableKey|null)
Parameters
type
listener
opt_useCapture
opt_listenerScope
code »removeAllListeners ( opt_type )number
Parameters
opt_type
code »removeEventListener ( type, handler, opt_capture, opt_handlerScope )
Deprecated: Use #unlisten instead, when possible. Otherwise, use + goog.events.unlisten if you are passing Object + (instead of Function) as handler.

Removes an event listener from the event target. The handler must be the + same object as the one added. If the handler has not been added then + nothing is done.

Parameters
type: string
The type of the event to listen for.
handler: (function(?): ?|{handleEvent: function(?): ?}|null)
The function + to handle the event. The handler can also be an object that implements + the handleEvent method which takes the event object as argument.
opt_capture: boolean=
In DOM-compliant browsers, this determines + whether the listener is fired during the capture or bubble phase + of the event.
opt_handlerScope: Object=
Object in whose scope to call + the listener.

Sets the parent of this event target to use for capture/bubble + mechanism.

Parameters
parent: goog.events.EventTarget
Parent listenable (null if none).

Sets the target to be used for event.target when firing + event. Mainly used for testing. For example, see + goog.testing.events.mixinListenable.

Parameters
target: !Object
The target.
code »unlisten ( type, listener, opt_useCapture, opt_listenerScope )boolean
Parameters
type
listener
opt_useCapture
opt_listenerScope
code »unlistenByKey ( key )boolean
Parameters
key

Defined in goog.Disposable

code »<T> addOnDisposeCallback ( callback, opt_scope )

Invokes a callback function when this object is disposed. Callbacks are + invoked in the order in which they were added.

Parameters
callback: function(this: T): ?
The callback function.
opt_scope: T=
An optional scope to call the callback in.
code »dispose ( )void

Disposes of the object. If the object hasn't already been disposed of, calls + #disposeInternal. Classes that extend goog.Disposable should + override #disposeInternal in order to delete references to COM + objects, DOM nodes, and other disposable objects. Reentrant.

Returns
Nothing.
Deprecated: Use #isDisposed instead.
Returns
Whether the object has been disposed of.
Returns
Whether the object has been disposed of.

Associates a disposable object with this object so that they will be disposed + together.

Parameters
disposable: goog.disposable.IDisposable
that will be disposed when + this object is disposed.

Instance Properties

Defined in goog.events.EventTarget

The object to use for event.target. Useful when mixing in an + EventTarget to another object.

Maps of event type to an array of listeners.

Parent event target, used during event bubbling. + + TODO(user): Change this to goog.events.Listenable. This + currently breaks people who expect getParentEventTarget to return + goog.events.EventTarget.

Defined in goog.Disposable

If monitoring the goog.Disposable instances is enabled, stores the creation + stack trace of the Disposable instance.

Whether the object has been disposed of.

Callbacks to invoke when this object is disposed.

Static Functions

Dispatches the given event on the ancestorsTree.

Parameters
target: !Object
The target to dispatch on.
e: (goog.events.Event|Object|string)
The event object.
opt_ancestorsTree: Array.<goog.events.Listenable>=
The ancestors + tree of the target, in reverse order from the closest ancestor + to the root event target. May be null if the target has no ancestor.
Returns
If anyone called preventDefault on the event object (or + if any of the listeners returns false) this will also return false.

Static Properties

An artificial cap on the number of ancestors you can have. This is mainly + for loop detection.

\ No newline at end of file diff --git a/docs/class_goog_events_Listener.html b/docs/class_goog_events_Listener.html new file mode 100644 index 0000000..7b718bc --- /dev/null +++ b/docs/class_goog_events_Listener.html @@ -0,0 +1,6 @@ +goog.events.Listener

Class goog.events.Listener

code »
All implemented interfaces:
goog.events.ListenableKey

Simple class that stores information about a listener

Constructor

goog.events.Listener ( listener, proxy, src, type, capture, opt_handler )
Parameters
listener: !Function
Callback function.
proxy: Function
Wrapper for the listener that patches the event.
src: (EventTarget|goog.events.Listenable)
Source object for + the event.
type: string
Event type.
capture: boolean
Whether in capture or bubble phase.
opt_handler: Object=
Object in whose context to execute the callback.
Show:

Instance Methods

Marks this listener as removed. This also remove references held by + this listener object (such as listener and event source).

Instance Properties

Whether to remove the listener after it has been called.

Whether the listener is being called in the capture or bubble phase

If monitoring the goog.events.Listener instances is enabled, stores the + creation stack trace of the Disposable instance.

Optional object whose context to execute the listener in

The key of the listener.

Callback function.

A wrapper over the original listener. This is used solely to + handle native browser events (it is used to simulate the capture + phase and to patch the event object).

Whether the listener has been removed.

Object or node that callback is listening to

The event type.

Compiler Constants

\ No newline at end of file diff --git a/docs/class_goog_events_ListenerMap.html b/docs/class_goog_events_ListenerMap.html new file mode 100644 index 0000000..2e5abb3 --- /dev/null +++ b/docs/class_goog_events_ListenerMap.html @@ -0,0 +1,23 @@ +goog.events.ListenerMap

Class goog.events.ListenerMap

code »

Creates a new listener map.

Constructor

goog.events.ListenerMap ( src )
Parameters
src: (EventTarget|goog.events.Listenable)
The src object.
Show:

Instance Methods

code »add ( type, listener, callOnce, opt_useCapture, opt_listenerScope )goog.events.ListenableKey

Adds an event listener. A listener can only be added once to an + object and if it is added again the key for the listener is + returned. + + Note that a one-off listener will not change an existing listener, + if any. On the other hand a normal listener will change existing + one-off listener to become a normal listener.

Parameters
type: (string|!goog.events.EventId)
The listener event type.
listener: !Function
This listener callback method.
callOnce: boolean
Whether the listener is a one-off + listener.
opt_useCapture: boolean=
The capture mode of the listener.
opt_listenerScope: Object=
Object in whose scope to call the + listener.
Returns
Unique key for the listener.
code »getListener ( type, listener, capture, opt_listenerScope )goog.events.ListenableKey

Gets the goog.events.ListenableKey for the event or null if no such + listener is in use.

Parameters
type: (string|!goog.events.EventId)
The type of the listener + to retrieve.
listener: !Function
The listener function to get.
capture: boolean
Whether the listener is a capturing listener.
opt_listenerScope: Object=
Object in whose scope to call the + listener.
Returns
the found listener or null if not found.
Returns
Total number of registered listeners.

Gets all listeners that match the given type and capture mode. The + returned array is a copy (but the listener objects are not).

Parameters
type: (string|!goog.events.EventId)
The type of the listeners + to retrieve.
capture: boolean
The capture mode of the listeners to retrieve.
Returns
An array of matching + listeners.
Returns
The count of event types in this map that actually + have registered listeners.
code »hasListener ( opt_type, opt_capture )boolean

Whether there is a matching listener. If either the type or capture + parameters are unspecified, the function will match on the + remaining criteria.

Parameters
opt_type: (string|!goog.events.EventId)=
The type of the listener.
opt_capture: boolean=
The capture mode of the listener.
Returns
Whether there is an active listener matching + the requested type and/or capture phase.
code »remove ( type, listener, opt_useCapture, opt_listenerScope )boolean

Removes a matching listener.

Parameters
type: (string|!goog.events.EventId)
The listener event type.
listener: !Function
This listener callback method.
opt_useCapture: boolean=
The capture mode of the listener.
opt_listenerScope: Object=
Object in whose scope to call the + listener.
Returns
Whether any listener was removed.
code »removeAll ( opt_type )number

Removes all listeners from this map. If opt_type is provided, only + listeners that match the given type are removed.

Parameters
opt_type: (string|!goog.events.EventId)=
Type of event to remove.
Returns
Number of listeners removed.
code »removeByKey ( listener )boolean

Removes the given listener object.

Parameters
listener: goog.events.ListenableKey
The listener to remove.
Returns
Whether the listener is removed.

Instance Properties

Maps of event type to an array of listeners.

The count of types in this map that have registered listeners.

Static Functions

code »goog.events.ListenerMap.findListenerIndex_ ( listenerArray, listener, opt_useCapture, opt_listenerScope )number

Finds the index of a matching goog.events.Listener in the given + listenerArray.

Parameters
listenerArray: !Array
Array of listener.
listener: !Function
The listener function.
opt_useCapture: boolean=
The capture flag for the listener.
opt_listenerScope: Object=
The listener scope.
Returns
The index of the matching listener within the + listenerArray.
\ No newline at end of file diff --git a/docs/class_goog_iter_GroupByIterator_.html b/docs/class_goog_iter_GroupByIterator_.html new file mode 100644 index 0000000..c2e0c02 --- /dev/null +++ b/docs/class_goog_iter_GroupByIterator_.html @@ -0,0 +1,12 @@ +goog.iter.GroupByIterator_

Class goog.iter.GroupByIterator_.<KEY, VALUE>

code »
goog.iter.Iterator.<Array>
+  └ goog.iter.GroupByIterator_

Implements the goog.iter.groupBy iterator.

Constructor

goog.iter.GroupByIterator_ ( iterable, opt_keyFunc )
Parameters
iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
The + iterable to group.
opt_keyFunc: function(VALUE): KEY=
Optional function for + determining the key value for each group in the iterable. Default + is the identity function.
Show:

Instance Methods

Defined in goog.iter.GroupByIterator_

code »groupItems_ ( targetKey )!Array.<VALUE>

Performs the grouping of objects using the given key.

Parameters
targetKey: KEY
The target key object for the group.
Returns
An array of grouped objects.
code »keyFunc ( )KEY

A function for determining the key value for each element in the iterable. + If no function is provided, the identity function is used and returns the + element unchanged.

code »next ( )Array

Defined in goog.iter.Iterator.<Array>

code »__iterator__ ( opt_keys )!goog.iter.Iterator.<VALUE>

Returns the Iterator object itself. This is used to implement + the iterator protocol in JavaScript 1.7

Parameters
opt_keys: boolean=
Whether to return the keys or values. Default is + to only return the values. This is being used by the for-in loop (true) + and the for-each-in loop (false). Even though the param gives a hint + about what the iterator will return there is no guarantee that it will + return the keys when true is passed.
Returns
The object itself.

Instance Properties

Defined in goog.iter.GroupByIterator_

The current key visited during iteration.

The current value being added to the group.

The iterable to group, coerced to an iterator.

The target key for determining the start of a group.

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_iter_Iterator.html b/docs/class_goog_iter_Iterator.html index 2d55016..c1ed2cd 100644 --- a/docs/class_goog_iter_Iterator.html +++ b/docs/class_goog_iter_Iterator.html @@ -1,11 +1,11 @@ -goog.iter.Iterator

Class goog.iter.Iterator

code »

Class/interface for iterators. An iterator needs to implement a next +goog.iter.Iterator

Class goog.iter.Iterator.<VALUE>

code »

Class/interface for iterators. An iterator needs to implement a next method and it needs to throw a goog.iter.StopIteration when the iteration passes beyond the end. Iterators have no hasNext method. It is recommended to always use the helper functions to iterate over the - iterator or in case you are only targeting JavaScript 1.7 for in loops.

Constructor

goog.iter.Iterator ( )
Show:

Instance Methods

Returns the Iterator object itself. This is used to implement + iterator or in case you are only targeting JavaScript 1.7 for in loops.

Constructor

goog.iter.Iterator ( )
Show:

Instance Methods

code »__iterator__ ( opt_keys )!goog.iter.Iterator.<VALUE>

Returns the Iterator object itself. This is used to implement the iterator protocol in JavaScript 1.7

Parameters
opt_keys: boolean=
Whether to return the keys or values. Default is to only return the values. This is being used by the for-in loop (true) and the for-each-in loop (false). Even though the param gives a hint about what the iterator will return there is no guarantee that it will - return the keys when true is passed.
Returns
The object itself.
code »next ( )*

Returns the next value of the iteration. This will throw the object - goog.iter#StopIteration when the iteration passes the end.

Returns
Any object or value.
\ No newline at end of file + return the keys when true is passed.Returns
The object itself.
code »next ( )VALUE

Returns the next value of the iteration. This will throw the object + goog.iter#StopIteration when the iteration passes the end.

Returns
Any object or value.
\ No newline at end of file diff --git a/docs/class_goog_json_Serializer.html b/docs/class_goog_json_Serializer.html index 94211df..9b83d58 100644 --- a/docs/class_goog_json_Serializer.html +++ b/docs/class_goog_json_Serializer.html @@ -1,4 +1,4 @@ -goog.json.Serializer

Class goog.json.Serializer

code »

Class that is used to serialize JSON objects to a string.

Constructor

goog.json.Serializer ( opt_replacer )
Parameters
opt_replacer: ?goog.json.Replacer=
Replacer.
Show:

Instance Methods

code »serialize ( object )string

Serializes an object or a value to a JSON string.

Parameters
object: *
The object to serialize.
Returns
A JSON string representation of the input.
Throws
if there are loops in the object graph.

Serializes an array to a JSON string

Parameters
arr: Array
The array to serialize.
sb: Array
Array used as a string builder.

Serializes a number to a JSON string

Parameters
n: number
The number to serialize.
sb: Array
Array used as a string builder.

Serializes an object to a JSON string

Parameters
obj: Object
The object to serialize.
sb: Array
Array used as a string builder.

Serializes a string to a JSON string

Parameters
s: string
The string to serialize.
sb: Array
Array used as a string builder.
code »serialize_ ( object, sb )

Serializes a generic value to a JSON string

Parameters
object: *
The object to serialize.
sb: Array
Array used as a string builder.
Throws
if there are loops in the object graph.

Instance Properties

Static Properties

Character mappings used internally for goog.string.quote

Regular expression used to match characters that need to be replaced. +goog.json.Serializer

Class goog.json.Serializer

code »

Class that is used to serialize JSON objects to a string.

Constructor

goog.json.Serializer ( opt_replacer )
Parameters
opt_replacer: ?goog.json.Replacer=
Replacer.
Show:

Instance Methods

code »serialize ( object )string

Serializes an object or a value to a JSON string.

Parameters
object: *
The object to serialize.
Returns
A JSON string representation of the input.
Throws
if there are loops in the object graph.

Serializes an array to a JSON string

Parameters
arr: Array
The array to serialize.
sb: Array
Array used as a string builder.

Serializes a generic value to a JSON string

Parameters
object: *
The object to serialize.
sb: Array
Array used as a string builder.
Throws
if there are loops in the object graph.

Serializes a number to a JSON string

Parameters
n: number
The number to serialize.
sb: Array
Array used as a string builder.

Serializes an object to a JSON string

Parameters
obj: Object
The object to serialize.
sb: Array
Array used as a string builder.

Serializes a string to a JSON string

Parameters
s: string
The string to serialize.
sb: Array
Array used as a string builder.

Instance Properties

Static Properties

Character mappings used internally for goog.string.quote

Regular expression used to match characters that need to be replaced. The S60 browser has a bug where unicode characters are not matched by regular expressions. The condition below detects such behaviour and - adjusts the regular expression accordingly.

\ No newline at end of file + adjusts the regular expression accordingly.
\ No newline at end of file diff --git a/docs/class_goog_labs_testing_AllOfMatcher.html b/docs/class_goog_labs_testing_AllOfMatcher.html index 72579bf..b407e60 100644 --- a/docs/class_goog_labs_testing_AllOfMatcher.html +++ b/docs/class_goog_labs_testing_AllOfMatcher.html @@ -1,2 +1,2 @@ -goog.labs.testing.AllOfMatcher

Class goog.labs.testing.AllOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The AllOf matcher.

Constructor

goog.labs.testing.AllOfMatcher ( matchers )
Parameters
matchers: !Array
Input matchers.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed. The returned string is a concatenation of - all the failed matchers' error strings.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if all of the matchers match the input value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.AllOfMatcher

Class goog.labs.testing.AllOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The AllOf matcher.

Constructor

goog.labs.testing.AllOfMatcher ( matchers )
Parameters
matchers: !Array
Input matchers.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed. The returned string is a concatenation of + all the failed matchers' error strings.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if all of the matchers match the input value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_AnyOfMatcher.html b/docs/class_goog_labs_testing_AnyOfMatcher.html index 0cd3102..dfa3331 100644 --- a/docs/class_goog_labs_testing_AnyOfMatcher.html +++ b/docs/class_goog_labs_testing_AnyOfMatcher.html @@ -1 +1 @@ -goog.labs.testing.AnyOfMatcher

Class goog.labs.testing.AnyOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The AnyOf matcher.

Constructor

goog.labs.testing.AnyOfMatcher ( matchers )
Parameters
matchers: !Array
Input matchers.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if any of the matchers matches the input value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.AnyOfMatcher

Class goog.labs.testing.AnyOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The AnyOf matcher.

Constructor

goog.labs.testing.AnyOfMatcher ( matchers )
Parameters
matchers: !Array
Input matchers.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if any of the matchers matches the input value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_CloseToMatcher.html b/docs/class_goog_labs_testing_CloseToMatcher.html index ac2e402..790d379 100644 --- a/docs/class_goog_labs_testing_CloseToMatcher.html +++ b/docs/class_goog_labs_testing_CloseToMatcher.html @@ -1 +1 @@ -goog.labs.testing.CloseToMatcher

Class goog.labs.testing.CloseToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The CloseTo matcher.

Constructor

goog.labs.testing.CloseToMatcher ( value, range )
Parameters
value: number
The value to compare.
range: number
The range to check within.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is within a certain range of the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.CloseToMatcher

Class goog.labs.testing.CloseToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The CloseTo matcher.

Constructor

goog.labs.testing.CloseToMatcher ( value, range )
Parameters
value: number
The value to compare.
range: number
The range to check within.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is within a certain range of the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_ContainsStringMatcher.html b/docs/class_goog_labs_testing_ContainsStringMatcher.html index 66d6afb..772335e 100644 --- a/docs/class_goog_labs_testing_ContainsStringMatcher.html +++ b/docs/class_goog_labs_testing_ContainsStringMatcher.html @@ -1 +1 @@ -goog.labs.testing.ContainsStringMatcher

Class goog.labs.testing.ContainsStringMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The ContainsString matcher.

Constructor

goog.labs.testing.ContainsStringMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.ContainsStringMatcher

Class goog.labs.testing.ContainsStringMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The ContainsString matcher.

Constructor

goog.labs.testing.ContainsStringMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_EndsWithMatcher.html b/docs/class_goog_labs_testing_EndsWithMatcher.html index 32b7c41..b6e434f 100644 --- a/docs/class_goog_labs_testing_EndsWithMatcher.html +++ b/docs/class_goog_labs_testing_EndsWithMatcher.html @@ -1 +1 @@ -goog.labs.testing.EndsWithMatcher

Class goog.labs.testing.EndsWithMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EndsWith matcher.

Constructor

goog.labs.testing.EndsWithMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string ends with the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.EndsWithMatcher

Class goog.labs.testing.EndsWithMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EndsWith matcher.

Constructor

goog.labs.testing.EndsWithMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string ends with the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_EqualToIgnoringWhitespaceMatcher.html b/docs/class_goog_labs_testing_EqualToIgnoringWhitespaceMatcher.html index 04e6f59..f82d598 100644 --- a/docs/class_goog_labs_testing_EqualToIgnoringWhitespaceMatcher.html +++ b/docs/class_goog_labs_testing_EqualToIgnoringWhitespaceMatcher.html @@ -1 +1 @@ -goog.labs.testing.EqualToIgnoringWhitespaceMatcher

Class goog.labs.testing.EqualToIgnoringWhitespaceMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EqualToIgnoringWhitespace matcher.

Constructor

goog.labs.testing.EqualToIgnoringWhitespaceMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.EqualToIgnoringWhitespaceMatcher

Class goog.labs.testing.EqualToIgnoringWhitespaceMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EqualToIgnoringWhitespace matcher.

Constructor

goog.labs.testing.EqualToIgnoringWhitespaceMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_EqualToMatcher.html b/docs/class_goog_labs_testing_EqualToMatcher.html index 4dd288f..81c5dcb 100644 --- a/docs/class_goog_labs_testing_EqualToMatcher.html +++ b/docs/class_goog_labs_testing_EqualToMatcher.html @@ -1 +1 @@ -goog.labs.testing.EqualToMatcher

Class goog.labs.testing.EqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EqualTo matcher.

Constructor

goog.labs.testing.EqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.EqualToMatcher

Class goog.labs.testing.EqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The EqualTo matcher.

Constructor

goog.labs.testing.EqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_EqualsMatcher.html b/docs/class_goog_labs_testing_EqualsMatcher.html index 3147f62..ab302cc 100644 --- a/docs/class_goog_labs_testing_EqualsMatcher.html +++ b/docs/class_goog_labs_testing_EqualsMatcher.html @@ -1 +1 @@ -goog.labs.testing.EqualsMatcher

Class goog.labs.testing.EqualsMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The Equals matcher.

Constructor

goog.labs.testing.EqualsMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string is equal to the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.EqualsMatcher

Class goog.labs.testing.EqualsMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The Equals matcher.

Constructor

goog.labs.testing.EqualsMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string is equal to the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_GreaterThanEqualToMatcher.html b/docs/class_goog_labs_testing_GreaterThanEqualToMatcher.html index 8c1b81e..75cfdab 100644 --- a/docs/class_goog_labs_testing_GreaterThanEqualToMatcher.html +++ b/docs/class_goog_labs_testing_GreaterThanEqualToMatcher.html @@ -1 +1 @@ -goog.labs.testing.GreaterThanEqualToMatcher

Class goog.labs.testing.GreaterThanEqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The GreaterThanEqualTo matcher.

Constructor

goog.labs.testing.GreaterThanEqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is greater than equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.GreaterThanEqualToMatcher

Class goog.labs.testing.GreaterThanEqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The GreaterThanEqualTo matcher.

Constructor

goog.labs.testing.GreaterThanEqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is greater than equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_GreaterThanMatcher.html b/docs/class_goog_labs_testing_GreaterThanMatcher.html index d54c332..841df16 100644 --- a/docs/class_goog_labs_testing_GreaterThanMatcher.html +++ b/docs/class_goog_labs_testing_GreaterThanMatcher.html @@ -1 +1 @@ -goog.labs.testing.GreaterThanMatcher

Class goog.labs.testing.GreaterThanMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The GreaterThan matcher.

Constructor

goog.labs.testing.GreaterThanMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is greater than the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.GreaterThanMatcher

Class goog.labs.testing.GreaterThanMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The GreaterThan matcher.

Constructor

goog.labs.testing.GreaterThanMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is greater than the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_HasPropertyMatcher.html b/docs/class_goog_labs_testing_HasPropertyMatcher.html index c92f19f..457b6c5 100644 --- a/docs/class_goog_labs_testing_HasPropertyMatcher.html +++ b/docs/class_goog_labs_testing_HasPropertyMatcher.html @@ -1 +1 @@ -goog.labs.testing.HasPropertyMatcher

Class goog.labs.testing.HasPropertyMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The HasProperty matcher.

Constructor

goog.labs.testing.HasPropertyMatcher ( property )
Parameters
property: string
Name of the property to test.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if an object has a property.

Parameters
actualObject

Instance Properties

\ No newline at end of file +goog.labs.testing.HasPropertyMatcher

Class goog.labs.testing.HasPropertyMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The HasProperty matcher.

Constructor

goog.labs.testing.HasPropertyMatcher ( property )
Parameters
property: string
Name of the property to test.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if an object has a property.

Parameters
actualObject

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_InstanceOfMatcher.html b/docs/class_goog_labs_testing_InstanceOfMatcher.html index f95c046..255bd2e 100644 --- a/docs/class_goog_labs_testing_InstanceOfMatcher.html +++ b/docs/class_goog_labs_testing_InstanceOfMatcher.html @@ -1 +1 @@ -goog.labs.testing.InstanceOfMatcher

Class goog.labs.testing.InstanceOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The InstanceOf matcher.

Constructor

goog.labs.testing.InstanceOfMatcher ( object )
Parameters
object: !Object
The expected class object.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if an object is an instance of another object.

Parameters
actualObject

Instance Properties

\ No newline at end of file +goog.labs.testing.InstanceOfMatcher

Class goog.labs.testing.InstanceOfMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The InstanceOf matcher.

Constructor

goog.labs.testing.InstanceOfMatcher ( object )
Parameters
object: !Object
The expected class object.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if an object is an instance of another object.

Parameters
actualObject

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_IsNotMatcher.html b/docs/class_goog_labs_testing_IsNotMatcher.html index 90d8178..a36c7ce 100644 --- a/docs/class_goog_labs_testing_IsNotMatcher.html +++ b/docs/class_goog_labs_testing_IsNotMatcher.html @@ -1 +1 @@ -goog.labs.testing.IsNotMatcher

Class goog.labs.testing.IsNotMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNot matcher.

Constructor

goog.labs.testing.IsNotMatcher ( matcher )
Parameters
matcher: !goog.labs.testing.Matcher
The matcher to negate.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value doesn't satisfy a matcher.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.IsNotMatcher

Class goog.labs.testing.IsNotMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNot matcher.

Constructor

goog.labs.testing.IsNotMatcher ( matcher )
Parameters
matcher: !goog.labs.testing.Matcher
The matcher to negate.
Show:

Instance Methods

code »describe ( actualValue )string

Describes why the matcher failed.

Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value doesn't satisfy a matcher.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_IsNullMatcher.html b/docs/class_goog_labs_testing_IsNullMatcher.html index 1517849..2873c4f 100644 --- a/docs/class_goog_labs_testing_IsNullMatcher.html +++ b/docs/class_goog_labs_testing_IsNullMatcher.html @@ -1 +1 @@ -goog.labs.testing.IsNullMatcher

Class goog.labs.testing.IsNullMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNull matcher.

Constructor

goog.labs.testing.IsNullMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is null.

Parameters
actualValue
\ No newline at end of file +goog.labs.testing.IsNullMatcher

Class goog.labs.testing.IsNullMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNull matcher.

Constructor

goog.labs.testing.IsNullMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is null.

Parameters
actualValue
\ No newline at end of file diff --git a/docs/class_goog_labs_testing_IsNullOrUndefinedMatcher.html b/docs/class_goog_labs_testing_IsNullOrUndefinedMatcher.html index 513e9ce..40eff35 100644 --- a/docs/class_goog_labs_testing_IsNullOrUndefinedMatcher.html +++ b/docs/class_goog_labs_testing_IsNullOrUndefinedMatcher.html @@ -1 +1 @@ -goog.labs.testing.IsNullOrUndefinedMatcher

Class goog.labs.testing.IsNullOrUndefinedMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNullOrUndefined matcher.

Constructor

goog.labs.testing.IsNullOrUndefinedMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is null or undefined.

Parameters
actualValue
\ No newline at end of file +goog.labs.testing.IsNullOrUndefinedMatcher

Class goog.labs.testing.IsNullOrUndefinedMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsNullOrUndefined matcher.

Constructor

goog.labs.testing.IsNullOrUndefinedMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is null or undefined.

Parameters
actualValue
\ No newline at end of file diff --git a/docs/class_goog_labs_testing_IsUndefinedMatcher.html b/docs/class_goog_labs_testing_IsUndefinedMatcher.html index 7297062..e541471 100644 --- a/docs/class_goog_labs_testing_IsUndefinedMatcher.html +++ b/docs/class_goog_labs_testing_IsUndefinedMatcher.html @@ -1 +1 @@ -goog.labs.testing.IsUndefinedMatcher

Class goog.labs.testing.IsUndefinedMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsUndefined matcher.

Constructor

goog.labs.testing.IsUndefinedMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is undefined.

Parameters
actualValue
\ No newline at end of file +goog.labs.testing.IsUndefinedMatcher

Class goog.labs.testing.IsUndefinedMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The IsUndefined matcher.

Constructor

goog.labs.testing.IsUndefinedMatcher ( )
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input value is undefined.

Parameters
actualValue
\ No newline at end of file diff --git a/docs/class_goog_labs_testing_LessThanEqualToMatcher.html b/docs/class_goog_labs_testing_LessThanEqualToMatcher.html index 33ae0fc..ffd26c3 100644 --- a/docs/class_goog_labs_testing_LessThanEqualToMatcher.html +++ b/docs/class_goog_labs_testing_LessThanEqualToMatcher.html @@ -1 +1 @@ -goog.labs.testing.LessThanEqualToMatcher

Class goog.labs.testing.LessThanEqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The LessThanEqualTo matcher.

Constructor

goog.labs.testing.LessThanEqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is less than or equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.LessThanEqualToMatcher

Class goog.labs.testing.LessThanEqualToMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The LessThanEqualTo matcher.

Constructor

goog.labs.testing.LessThanEqualToMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is less than or equal to the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_LessThanMatcher.html b/docs/class_goog_labs_testing_LessThanMatcher.html index 66195f9..3dc64a1 100644 --- a/docs/class_goog_labs_testing_LessThanMatcher.html +++ b/docs/class_goog_labs_testing_LessThanMatcher.html @@ -1 +1 @@ -goog.labs.testing.LessThanMatcher

Class goog.labs.testing.LessThanMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The lessThan matcher.

Constructor

goog.labs.testing.LessThanMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is less than the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.LessThanMatcher

Class goog.labs.testing.LessThanMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The lessThan matcher.

Constructor

goog.labs.testing.LessThanMatcher ( value )
Parameters
value: number
The value to compare.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if the input value is less than the expected value.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_MatcherError.html b/docs/class_goog_labs_testing_MatcherError.html index deb71c5..8edf8d8 100644 --- a/docs/class_goog_labs_testing_MatcherError.html +++ b/docs/class_goog_labs_testing_MatcherError.html @@ -1,3 +1,3 @@ -goog.labs.testing.MatcherError

Class goog.labs.testing.MatcherError

code »
Error
+goog.labs.testing.MatcherError

Class goog.labs.testing.MatcherError

code »
Errorgoog.debug.Error
-      └ goog.labs.testing.MatcherError

Error thrown when a Matcher fails to match the input value.

Constructor

goog.labs.testing.MatcherError ( opt_message )
Parameters
opt_message: string=
The error message.
Show:

Static Properties

\ No newline at end of file + └ goog.labs.testing.MatcherError

Error thrown when a Matcher fails to match the input value.

Constructor

goog.labs.testing.MatcherError ( opt_message )
Parameters
opt_message: string=
The error message.
Show:

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_ObjectEqualsMatcher.html b/docs/class_goog_labs_testing_ObjectEqualsMatcher.html index 8d0dc16..12d8f28 100644 --- a/docs/class_goog_labs_testing_ObjectEqualsMatcher.html +++ b/docs/class_goog_labs_testing_ObjectEqualsMatcher.html @@ -1 +1 @@ -goog.labs.testing.ObjectEqualsMatcher

Class goog.labs.testing.ObjectEqualsMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The Equals matcher.

Constructor

goog.labs.testing.ObjectEqualsMatcher ( expectedObject )
Parameters
expectedObject: !Object
The expected object.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if two objects are the same.

Parameters
actualObject

Instance Properties

\ No newline at end of file +goog.labs.testing.ObjectEqualsMatcher

Class goog.labs.testing.ObjectEqualsMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The Equals matcher.

Constructor

goog.labs.testing.ObjectEqualsMatcher ( expectedObject )
Parameters
expectedObject: !Object
The expected object.
Show:

Instance Methods

code »describe ( actualObject )string
Parameters
actualObject
code »matches ( actualObject )boolean

Determines if two objects are the same.

Parameters
actualObject

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_RegexMatcher.html b/docs/class_goog_labs_testing_RegexMatcher.html index 57c53c8..db2df2e 100644 --- a/docs/class_goog_labs_testing_RegexMatcher.html +++ b/docs/class_goog_labs_testing_RegexMatcher.html @@ -1 +1 @@ -goog.labs.testing.RegexMatcher

Class goog.labs.testing.RegexMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The MatchesRegex matcher.

Constructor

goog.labs.testing.RegexMatcher ( regex )
Parameters
regex: !RegExp
The expected regex.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string is equal to the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.RegexMatcher

Class goog.labs.testing.RegexMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The MatchesRegex matcher.

Constructor

goog.labs.testing.RegexMatcher ( regex )
Parameters
regex: !RegExp
The expected regex.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string is equal to the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_StartsWithMatcher.html b/docs/class_goog_labs_testing_StartsWithMatcher.html index e73ad5a..df9f738 100644 --- a/docs/class_goog_labs_testing_StartsWithMatcher.html +++ b/docs/class_goog_labs_testing_StartsWithMatcher.html @@ -1 +1 @@ -goog.labs.testing.StartsWithMatcher

Class goog.labs.testing.StartsWithMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The StartsWith matcher.

Constructor

goog.labs.testing.StartsWithMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string starts with the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.StartsWithMatcher

Class goog.labs.testing.StartsWithMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The StartsWith matcher.

Constructor

goog.labs.testing.StartsWithMatcher ( value )
Parameters
value: string
The expected string.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string starts with the expected string.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_labs_testing_StringContainsInOrderMatcher.html b/docs/class_goog_labs_testing_StringContainsInOrderMatcher.html index 183a59b..02b0b87 100644 --- a/docs/class_goog_labs_testing_StringContainsInOrderMatcher.html +++ b/docs/class_goog_labs_testing_StringContainsInOrderMatcher.html @@ -1 +1 @@ -goog.labs.testing.StringContainsInOrderMatcher

Class goog.labs.testing.StringContainsInOrderMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The StringContainsInOrdermatcher.

Constructor

goog.labs.testing.StringContainsInOrderMatcher ( values )
Parameters
values: Array.<string>
The expected string values.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains, in order, the expected array of strings.

Parameters
actualValue

Instance Properties

\ No newline at end of file +goog.labs.testing.StringContainsInOrderMatcher

Class goog.labs.testing.StringContainsInOrderMatcher

code »
All implemented interfaces:
goog.labs.testing.Matcher

The StringContainsInOrdermatcher.

Constructor

goog.labs.testing.StringContainsInOrderMatcher ( values )
Parameters
values: Array.<string>
The expected string values.
Show:

Instance Methods

code »describe ( actualValue )string
Parameters
actualValue
code »matches ( actualValue )boolean

Determines if input string contains, in order, the expected array of strings.

Parameters
actualValue

Instance Properties

\ No newline at end of file diff --git a/docs/class_goog_math_Box.html b/docs/class_goog_math_Box.html new file mode 100644 index 0000000..84531c7 --- /dev/null +++ b/docs/class_goog_math_Box.html @@ -0,0 +1,23 @@ +goog.math.Box

Class goog.math.Box

code »

Class for representing a box. A box is specified as a top, right, bottom, + and left. A box is useful for representing margins and padding. + + This class assumes 'screen coordinates': larger Y coordinates are further + from the top of the screen.

Constructor

goog.math.Box ( top, right, bottom, left )
Parameters
top: number
Top.
right: number
Right.
bottom: number
Bottom.
left: number
Left.
Show:

Instance Methods

Rounds the fields to the next larger integer values.

Returns
This box with ceil'd fields.

Creates a copy of the box with the same dimensions.

Returns
A clone of this Box.
code »contains ( other )boolean

Returns whether the box contains a coordinate or another box.

Parameters
other: (goog.math.Coordinate|goog.math.Box)
A Coordinate or a Box.
Returns
Whether the box contains the coordinate or other box.
code »expand ( top, opt_right, opt_bottom, opt_left )!goog.math.Box

Expands box with the given margins.

Parameters
top: (number|goog.math.Box)
Top margin or box with all margins.
opt_right: number=
Right margin.
opt_bottom: number=
Bottom margin.
opt_left: number=
Left margin.
Returns
A reference to this Box.

Expand this box to include another box. + NOTE(user): This is used in code that needs to be very fast, please don't + add functionality to this function at the expense of speed (variable + arguments, accepting multiple argument types, etc).

Parameters
box: goog.math.Box
The box to include in this one.

Rounds the fields to the next smaller integer values.

Returns
This box with floored fields.
Returns
height The height of this Box.
Returns
width The width of this Box.

Rounds the fields to nearest integer values.

Returns
This box with rounded fields.
code »scale ( sx, opt_sy )!goog.math.Box

Scales this coordinate by the given scale factors. The x and y dimension + values are scaled by sx and opt_sy respectively. + If opt_sy is not given, then sx is used for both x and y.

Parameters
sx: number
The scale factor to use for the x dimension.
opt_sy: number=
The scale factor to use for the y dimension.
Returns
This box after scaling.

Returns a nice string representing the box.

Returns
In the form (50t, 73r, 24b, 13l).
code »translate ( tx, opt_ty )!goog.math.Box

Translates this box by the given offsets. If a goog.math.Coordinate + is given, then the left and right values are translated by the coordinate's + x value and the top and bottom values are translated by the coordinate's y + value. Otherwise, tx and opt_ty are used to translate the x + and y dimension values.

Parameters
tx: (number|goog.math.Coordinate)
The value to translate the x + dimension values by or the the coordinate to translate this box by.
opt_ty: number=
The value to translate y dimension values by.
Returns
This box after translating.

Instance Properties

Static Functions

Creates a Box by bounding a collection of goog.math.Coordinate objects

Parameters
var_args: ...goog.math.Coordinate
Coordinates to be included inside + the box.
Returns
A Box containing all the specified Coordinates.

Returns whether a box contains a coordinate or another box.

Parameters
box: goog.math.Box
A Box.
other: (goog.math.Coordinate|goog.math.Box)
A Coordinate or a Box.
Returns
Whether the box contains the coordinate or other box.

Returns the distance between a coordinate and the nearest corner/side of a + box. Returns zero if the coordinate is inside the box.

Parameters
box: goog.math.Box
A Box.
coord: goog.math.Coordinate
A Coordinate.
Returns
The distance between coord and the nearest + corner/side of box, or zero if coord is inside + box.

Compares boxes for equality.

Parameters
a: goog.math.Box
A Box.
b: goog.math.Box
A Box.
Returns
True iff the boxes are equal, or if both are null.

Returns whether two boxes intersect.

Parameters
a: goog.math.Box
A Box.
b: goog.math.Box
A second Box.
Returns
Whether the boxes intersect.

Returns whether two boxes would intersect with additional padding.

Parameters
a: goog.math.Box
A Box.
b: goog.math.Box
A second Box.
padding: number
The additional padding.
Returns
Whether the boxes intersect.

Returns the relative x position of a coordinate compared to a box. Returns + zero if the coordinate is inside the box.

Parameters
box: goog.math.Box
A Box.
coord: goog.math.Coordinate
A Coordinate.
Returns
The x position of coord relative to the nearest + side of box, or zero if coord is inside box.

Returns the relative y position of a coordinate compared to a box. Returns + zero if the coordinate is inside the box.

Parameters
box: goog.math.Box
A Box.
coord: goog.math.Coordinate
A Coordinate.
Returns
The y position of coord relative to the nearest + side of box, or zero if coord is inside box.
\ No newline at end of file diff --git a/docs/class_goog_math_Coordinate.html b/docs/class_goog_math_Coordinate.html new file mode 100644 index 0000000..c1e4989 --- /dev/null +++ b/docs/class_goog_math_Coordinate.html @@ -0,0 +1,22 @@ +goog.math.Coordinate

Class goog.math.Coordinate

code »

Class for representing coordinates and positions.

Constructor

goog.math.Coordinate ( opt_x, opt_y )
Parameters
opt_x: number=
Left, defaults to 0.
opt_y: number=
Top, defaults to 0.
Show:

Instance Methods

Rounds the x and y fields to the next larger integer values.

Returns
This coordinate with ceil'd fields.

Returns a new copy of the coordinate.

Returns
A clone of this coordinate.

Rounds the x and y fields to the next smaller integer values.

Returns
This coordinate with floored fields.
code »rotateDegrees ( degrees, opt_center )

Rotates this coordinate clockwise about the origin (or, optionally, the given + center) by the given angle, in degrees.

Parameters
degrees: number
The angle by which to rotate this coordinate + clockwise about the given center, in degrees.
opt_center: !goog.math.Coordinate=
The center of rotation. Defaults + to (0, 0) if not given.
code »rotateRadians ( radians, opt_center )

Rotates this coordinate clockwise about the origin (or, optionally, the given + center) by the given angle, in radians.

Parameters
radians: number
The angle by which to rotate this coordinate + clockwise about the given center, in radians.
opt_center: !goog.math.Coordinate=
The center of rotation. Defaults + to (0, 0) if not given.

Rounds the x and y fields to the nearest integer values.

Returns
This coordinate with rounded fields.

Scales this coordinate by the given scale factors. The x and y values are + scaled by sx and opt_sy respectively. If opt_sy + is not given, then sx is used for both x and y.

Parameters
sx: number
The scale factor to use for the x dimension.
opt_sy: number=
The scale factor to use for the y dimension.
Returns
This coordinate after scaling.

Returns a nice string representing the coordinate.

Returns
In the form (50, 73).

Translates this box by the given offsets. If a goog.math.Coordinate + is given, then the x and y values are translated by the coordinate's x and y. + Otherwise, x and y are translated by tx and opt_ty + respectively.

Parameters
tx: (number|goog.math.Coordinate)
The value to translate x by or the + the coordinate to translate this coordinate by.
opt_ty: number=
The value to translate y by.
Returns
This coordinate after translating.

Instance Properties

X-value

Y-value

Static Functions

Returns the angle from the origin to a coordinate.

Parameters
a: !goog.math.Coordinate
A Coordinate.
Returns
The angle, in degrees, clockwise from the positive X + axis to a.

Returns the difference between two coordinates as a new + goog.math.Coordinate.

Parameters
a: !goog.math.Coordinate
A Coordinate.
b: !goog.math.Coordinate
A Coordinate.
Returns
A Coordinate representing the difference + between a and b.

Returns the distance between two coordinates.

Parameters
a: !goog.math.Coordinate
A Coordinate.
b: !goog.math.Coordinate
A Coordinate.
Returns
The distance between a and b.

Compares coordinates for equality.

Parameters
a: goog.math.Coordinate
A Coordinate.
b: goog.math.Coordinate
A Coordinate.
Returns
True iff the coordinates are equal, or if both are null.

Returns the magnitude of a coordinate.

Parameters
a: !goog.math.Coordinate
A Coordinate.
Returns
The distance between the origin and a.

Returns the squared distance between two coordinates. Squared distances can + be used for comparisons when the actual value is not required. + + Performance note: eliminating the square root is an optimization often used + in lower-level languages, but the speed difference is not nearly as + pronounced in JavaScript (only a few percent.)

Parameters
a: !goog.math.Coordinate
A Coordinate.
b: !goog.math.Coordinate
A Coordinate.
Returns
The squared distance between a and b.

Returns the sum of two coordinates as a new goog.math.Coordinate.

Parameters
a: !goog.math.Coordinate
A Coordinate.
b: !goog.math.Coordinate
A Coordinate.
Returns
A Coordinate representing the sum of the two + coordinates.
\ No newline at end of file diff --git a/docs/class_goog_math_Rect.html b/docs/class_goog_math_Rect.html new file mode 100644 index 0000000..3b077eb --- /dev/null +++ b/docs/class_goog_math_Rect.html @@ -0,0 +1,34 @@ +goog.math.Rect

Class goog.math.Rect

code »

Class for representing rectangular regions.

Constructor

goog.math.Rect ( x, y, w, h )
Parameters
x: number
Left.
y: number
Top.
w: number
Width.
h: number
Height.
Show:

Instance Methods

Expand this rectangle to also include the area of the given rectangle.

Parameters
rect: goog.math.Rect
The other rectangle.

Rounds the fields to the next larger integer values.

Returns
This rectangle with ceil'd fields.
Returns
A new copy of this Rectangle.
code »contains ( another )boolean

Tests whether this rectangle entirely contains another rectangle or + coordinate.

Parameters
another: (goog.math.Rect|goog.math.Coordinate)
The rectangle or + coordinate to test for containment.
Returns
Whether this rectangle contains given rectangle or + coordinate.
code »difference ( rect )!Array

Computes the difference regions between this rectangle and rect. The + return value is an array of 0 to 4 rectangles defining the remaining regions + of this rectangle after the other has been subtracted.

Parameters
rect: goog.math.Rect
A Rectangle.
Returns
An array with 0 to 4 rectangles which + together define the difference area of rectangle a minus rectangle b.
code »distance ( point )number
Parameters
point: !goog.math.Coordinate
A coordinate.
Returns
The distance between the point and the closest point + inside the rectangle. Returns 0 if the point is inside the rectangle.

Rounds the fields to the next smaller integer values.

Returns
This rectangle with floored fields.
Returns
A new coordinate for the bottom-right corner + of the rectangle.
Returns
A new coordinate for the center of the + rectangle.
Returns
The size of this rectangle.
Returns
A new coordinate for the top-left corner of + the rectangle.

Computes the intersection of this rectangle and the rectangle parameter. If + there is no intersection, returns false and leaves this rectangle as is.

Parameters
rect: goog.math.Rect
A Rectangle.
Returns
True iff this rectangle intersects with the parameter.

Returns whether a rectangle intersects this rectangle.

Parameters
rect: goog.math.Rect
A rectangle.
Returns
Whether rect intersects this rectangle.

Rounds the fields to nearest integer values.

Returns
This rectangle with rounded fields.
code »scale ( sx, opt_sy )!goog.math.Rect

Scales this rectangle by the given scale factors. The left and width values + are scaled by sx and the top and height values are scaled by + opt_sy. If opt_sy is not given, then all fields are scaled + by sx.

Parameters
sx: number
The scale factor to use for the x dimension.
opt_sy: number=
The scale factor to use for the y dimension.
Returns
This rectangle after scaling.
Parameters
point: !goog.math.Coordinate
A coordinate.
Returns
The squared distance between the point and the closest + point inside the rectangle. Returns 0 if the point is inside the + rectangle.

Returns a new Box object with the same position and dimensions as this + rectangle.

Returns
A new Box representation of this Rectangle.

Returns a nice string representing size and dimensions of rectangle.

Returns
In the form (50, 73 - 75w x 25h).
code »translate ( tx, opt_ty )!goog.math.Rect

Translates this rectangle by the given offsets. If a + goog.math.Coordinate is given, then the left and top values are + translated by the coordinate's x and y values. Otherwise, top and left are + translated by tx and opt_ty respectively.

Parameters
tx: (number|goog.math.Coordinate)
The value to translate left by or the + the coordinate to translate this rect by.
opt_ty: number=
The value to translate top by.
Returns
This rectangle after translating.

Instance Properties

Static Functions

Returns a new rectangle which completely contains both input rectangles.

Parameters
a: goog.math.Rect
A rectangle.
b: goog.math.Rect
A rectangle.
Returns
A new bounding rect, or null if either rect is + null.

Creates a new Rect object with the same position and dimensions as a given + Box. Note that this is only the inverse of toBox if left/top are defined.

Parameters
box: goog.math.Box
A box.
Returns
A new Rect initialized with the box's position + and size.

Computes the difference regions between two rectangles. The return value is + an array of 0 to 4 rectangles defining the remaining regions of the first + rectangle after the second has been subtracted.

Parameters
a: goog.math.Rect
A Rectangle.
b: goog.math.Rect
A Rectangle.
Returns
An array with 0 to 4 rectangles which + together define the difference area of rectangle a minus rectangle b.

Compares rectangles for equality.

Parameters
a: goog.math.Rect
A Rectangle.
b: goog.math.Rect
A Rectangle.
Returns
True iff the rectangles have the same left, top, width, + and height, or if both are null.

Returns the intersection of two rectangles. Two rectangles intersect if they + touch at all, for example, two zero width and height rectangles would + intersect if they had the same top and left.

Parameters
a: goog.math.Rect
A Rectangle.
b: goog.math.Rect
A Rectangle.
Returns
A new intersection rect (even if width and height + are 0), or null if there is no intersection.

Returns whether two rectangles intersect. Two rectangles intersect if they + touch at all, for example, two zero width and height rectangles would + intersect if they had the same top and left.

Parameters
a: goog.math.Rect
A Rectangle.
b: goog.math.Rect
A Rectangle.
Returns
Whether a and b intersect.
\ No newline at end of file diff --git a/docs/class_goog_math_Size.html b/docs/class_goog_math_Size.html new file mode 100644 index 0000000..efb7ff6 --- /dev/null +++ b/docs/class_goog_math_Size.html @@ -0,0 +1,10 @@ +goog.math.Size

Class goog.math.Size

code »

Class for representing sizes consisting of a width and height. Undefined + width and height support is deprecated and results in compiler warning.

Constructor

goog.math.Size ( width, height )
Parameters
width: number
Width.
height: number
Height.
Show:

Instance Methods

Returns
The area of the size (width * height).
Returns
The ratio of the size's width to its height.

Clamps the width and height parameters upward to integer values.

Returns
This size with ceil'd components.
Returns
A new copy of the Size.
code »fitsInside ( target )boolean
Parameters
target: !goog.math.Size
The target size.
Returns
True if this Size is the same size or smaller than the + target size in both dimensions.

Clamps the width and height parameters downward to integer values.

Returns
This size with floored components.
Returns
The longer of the two dimensions in the size.
Returns
The shorter of the two dimensions in the size.
Returns
True if the size has zero area, false if both dimensions + are non-zero numbers.
Returns
The perimeter of the size (width + height) * 2.

Rounds the width and height parameters to integer values.

Returns
This size with rounded components.
code »scale ( sx, opt_sy )!goog.math.Size

Scales this size by the given scale factors. The width and height are scaled + by sx and opt_sy respectively. If opt_sy is not + given, then sx is used for both the width and height.

Parameters
sx: number
The scale factor to use for the width.
opt_sy: number=
The scale factor to use for the height.
Returns
This Size object after scaling.

Uniformly scales the size to fit inside the dimensions of a given size. The + original aspect ratio will be preserved. + + This function assumes that both Sizes contain strictly positive dimensions.

Parameters
target: !goog.math.Size
The target size.
Returns
This Size object, after optional scaling.

Returns a nice string representing size.

Returns
In the form (50 x 73).

Instance Properties

Static Functions

Compares sizes for equality.

Parameters
a: goog.math.Size
A Size.
b: goog.math.Size
A Size.
Returns
True iff the sizes have equal widths and equal + heights, or if both are null.
\ No newline at end of file diff --git a/docs/class_goog_net_DefaultXmlHttpFactory.html b/docs/class_goog_net_DefaultXmlHttpFactory.html index dbe1b20..054fd93 100644 --- a/docs/class_goog_net_DefaultXmlHttpFactory.html +++ b/docs/class_goog_net_DefaultXmlHttpFactory.html @@ -1,4 +1,4 @@ -goog.net.DefaultXmlHttpFactory

Class goog.net.DefaultXmlHttpFactory

code »
goog.net.XmlHttpFactory
+goog.net.DefaultXmlHttpFactory

Class goog.net.DefaultXmlHttpFactory

code »
goog.net.XmlHttpFactory
   └ goog.net.DefaultXmlHttpFactory

Default factory to use when creating xhr objects. You probably shouldn't be - instantiating this directly, but rather using it via goog.net.XmlHttp.

Constructor

goog.net.DefaultXmlHttpFactory ( )
Show:

Instance Methods

Defined in goog.net.DefaultXmlHttpFactory

code »createInstance ( )(GearsHttpRequest|XMLHttpRequest)

Initialize the private state used by other functions.

Returns
The ActiveX PROG ID string to use to create xhr's in IE.
code »internalGetOptions ( )(Object|null)

Defined in goog.net.XmlHttpFactory

Returns
Options describing how xhr objects obtained from this - factory should be used.

Instance Properties

Defined in goog.net.DefaultXmlHttpFactory

The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.

Defined in goog.net.XmlHttpFactory

Cache of options - we only actually call internalGetOptions once.

Static Properties

\ No newline at end of file + instantiating this directly, but rather using it via goog.net.XmlHttp.

Constructor

goog.net.DefaultXmlHttpFactory ( )
Show:

Instance Methods

Defined in goog.net.DefaultXmlHttpFactory

code »createInstance ( )(XMLHttpRequest|goog.net.XhrLike)

Initialize the private state used by other functions.

Returns
The ActiveX PROG ID string to use to create xhr's in IE.
code »internalGetOptions ( )(Object|null)

Defined in goog.net.XmlHttpFactory

Returns
Options describing how xhr objects obtained from this + factory should be used.

Instance Properties

Defined in goog.net.DefaultXmlHttpFactory

The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.

Defined in goog.net.XmlHttpFactory

Cache of options - we only actually call internalGetOptions once.

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_net_WrapperXmlHttpFactory.html b/docs/class_goog_net_WrapperXmlHttpFactory.html index caf95bc..927086d 100644 --- a/docs/class_goog_net_WrapperXmlHttpFactory.html +++ b/docs/class_goog_net_WrapperXmlHttpFactory.html @@ -1,8 +1,7 @@ -goog.net.WrapperXmlHttpFactory

Class goog.net.WrapperXmlHttpFactory

code »
goog.net.XmlHttpFactory
+goog.net.WrapperXmlHttpFactory

Class goog.net.WrapperXmlHttpFactory

code »
goog.net.XmlHttpFactory
   └ goog.net.WrapperXmlHttpFactory

An xhr factory subclass which can be constructed using two factory methods. This exists partly to allow the preservation of goog.net.XmlHttp.setFactory() - with an unchanged signature.

Constructor

goog.net.WrapperXmlHttpFactory ( xhrFactory, optionsFactory )
Parameters
xhrFactory: function(): !(XMLHttpRequest|GearsHttpRequest)
A - function which returns a new XHR object.
optionsFactory: function(): !Object
A function which returns the - options associated with xhr objects from this factory.
Show:

Instance Methods

Defined in goog.net.WrapperXmlHttpFactory

code »createInstance ( )(GearsHttpRequest|XMLHttpRequest)
code »getOptions ( )(Object|null)
code »optionsFactory_ ( )Object

Options factory method.

code »xhrFactory_ ( )(GearsHttpRequest|XMLHttpRequest)

XHR factory method.

Defined in goog.net.XmlHttpFactory

Override this method in subclasses to preserve the caching offered by + with an unchanged signature.

Constructor

goog.net.WrapperXmlHttpFactory ( xhrFactory, optionsFactory )
Parameters
xhrFactory: function(): !goog.net.XhrLike.OrNative
A function which returns a new XHR object.
optionsFactory: function(): !Object
A function which returns the + options associated with xhr objects from this factory.
Show:

Instance Methods

Defined in goog.net.WrapperXmlHttpFactory

code »createInstance ( )(XMLHttpRequest|goog.net.XhrLike)
code »getOptions ( )(Object|null)
code »optionsFactory_ ( )Object

Options factory method.

code »xhrFactory_ ( )(XMLHttpRequest|goog.net.XhrLike)

XHR factory method.

Defined in goog.net.XmlHttpFactory

Override this method in subclasses to preserve the caching offered by getOptions().

Returns
Options describing how xhr objects obtained from this - factory should be used.

Instance Properties

Defined in goog.net.XmlHttpFactory

Cache of options - we only actually call internalGetOptions once.

Static Properties

\ No newline at end of file + factory should be used.

Instance Properties

Defined in goog.net.XmlHttpFactory

Cache of options - we only actually call internalGetOptions once.

Static Properties

\ No newline at end of file diff --git a/docs/class_goog_net_XmlHttpFactory.html b/docs/class_goog_net_XmlHttpFactory.html index 7850a12..849e5ee 100644 --- a/docs/class_goog_net_XmlHttpFactory.html +++ b/docs/class_goog_net_XmlHttpFactory.html @@ -1,4 +1,4 @@ -goog.net.XmlHttpFactory

Class goog.net.XmlHttpFactory

code »

Abstract base class for an XmlHttpRequest factory.

Constructor

goog.net.XmlHttpFactory ( )
Show:

Instance Methods

code »createInstance ( )!(XMLHttpRequest|GearsHttpRequest)
Returns
A new XMLHttpRequest instance.
Returns
Options describing how xhr objects obtained from this - factory should be used.

Override this method in subclasses to preserve the caching offered by +goog.net.XmlHttpFactory

Class goog.net.XmlHttpFactory

code »

Abstract base class for an XmlHttpRequest factory.

Constructor

goog.net.XmlHttpFactory ( )
Show:

Instance Methods

Returns
A new XhrLike instance.
Returns
Options describing how xhr objects obtained from this + factory should be used.

Override this method in subclasses to preserve the caching offered by getOptions().

Returns
Options describing how xhr objects obtained from this - factory should be used.

Instance Properties

Cache of options - we only actually call internalGetOptions once.

\ No newline at end of file + factory should be used.

Instance Properties

Cache of options - we only actually call internalGetOptions once.

\ No newline at end of file diff --git a/docs/class_goog_structs_Map.html b/docs/class_goog_structs_Map.html index b3b0b7b..68fa850 100644 --- a/docs/class_goog_structs_Map.html +++ b/docs/class_goog_structs_Map.html @@ -1,21 +1,21 @@ -goog.structs.Map

Class goog.structs.Map

code »

Class for Hash Map datastructure.

Constructor

goog.structs.Map ( opt_map, var_args )
Parameters
opt_map: *=
Map or Object to initialize the map with.
var_args: ...*
If 2 or more arguments are present then they - will be used as key-value pairs.
Show:

Instance Methods

Returns an iterator that iterates over the values or the keys in the map. +goog.structs.Map

Class goog.structs.Map.<K, V>

code »

Class for Hash Map datastructure.

Constructor

goog.structs.Map ( opt_map, var_args )
Parameters
opt_map: *=
Map or Object to initialize the map with.
var_args: ...*
If 2 or more arguments are present then they + will be used as key-value pairs.
Show:

Instance Methods

Returns an iterator that iterates over the values or the keys in the map. This throws an exception if the map was mutated since the iterator was created.

Parameters
opt_keys: boolean=
True to iterate over the keys. False to iterate - over the values. The default value is false.
Returns
An iterator over the values or keys in the map.

Adds multiple key-value pairs from another goog.structs.Map or Object.

Parameters
map: Object
Object containing the data to add.

Cleans up the temp keys array by removing entries that are no longer in the - map.

Removes all key-value pairs from the map.

Clones a map and returns a new map.

Returns
A new map with the same key-value pairs.

Whether the map contains the given key.

Parameters
key: *
The key to check for.
Returns
Whether the map contains the key.

Whether the map contains the given value. This is O(n).

Parameters
val: *
The value to check for.
Returns
Whether the map contains the value.
code »equals ( otherMap, opt_equalityFn )boolean

Whether this map is equal to the argument map.

Parameters
otherMap: goog.structs.Map
The map against which to test equality.
opt_equalityFn: function(?, ?): boolean=
Optional equality function + over the values. The default value is false.
Returns
An iterator over the values or keys in the map.

Adds multiple key-value pairs from another goog.structs.Map or Object.

Parameters
map: Object
Object containing the data to add.

Cleans up the temp keys array by removing entries that are no longer in the + map.

Removes all key-value pairs from the map.

Clones a map and returns a new map.

Returns
A new map with the same key-value pairs.

Whether the map contains the given key.

Parameters
key: *
The key to check for.
Returns
Whether the map contains the key.

Whether the map contains the given value. This is O(n).

Parameters
val: V
The value to check for.
Returns
Whether the map contains the value.
code »equals ( otherMap, opt_equalityFn )boolean

Whether this map is equal to the argument map.

Parameters
otherMap: goog.structs.Map
The map against which to test equality.
opt_equalityFn: function(V, V): boolean=
Optional equality function to test equality of values. If not specified, this will test whether - the values contained in each map are identical objects.
Returns
Whether the maps are equal.
code »get ( key, opt_val )*

Returns the value for the given key. If the key is not found and the default - value is not given this will return undefined.

Parameters
key: *
The key to get the value for.
opt_val: *=
The value to return if no item is found for the given - key, defaults to undefined.
Returns
The value for the given key.
Returns
The number of key-value pairs in the map.

Returns an iterator that iterates over the keys in the map. Removal of keys - while iterating might have undesired side effects.

Returns
An iterator over the keys in the map.

Returns the keys of the map.

Returns
Array of string values.

Returns an iterator that iterates over the values in the map. Removal of - keys while iterating might have undesired side effects.

Returns
An iterator over the values in the map.

Returns the values of the map.

Returns
The values in the map.
Returns
Whether the map is empty.

Removes a key-value pair based on the key. This is O(logN) amortized due to + the values contained in each map are identical objects.Returns

Whether the maps are equal.
code »<T> forEach ( f, opt_obj )

Calls the given function on each entry in the map.

Parameters
f
opt_obj: T=
The value of "this" inside f.
code »<DEFAULT> get ( key, opt_val )(V|DEFAULT)

Returns the value for the given key. If the key is not found and the default + value is not given this will return undefined.

Parameters
key: *
The key to get the value for.
opt_val: DEFAULT=
The value to return if no item is found for the + given key, defaults to undefined.
Returns
The value for the given key.
Returns
The number of key-value pairs in the map.

Returns an iterator that iterates over the keys in the map. Removal of keys + while iterating might have undesired side effects.

Returns
An iterator over the keys in the map.

Returns the keys of the map.

Returns
Array of string values.

Returns an iterator that iterates over the values in the map. Removal of + keys while iterating might have undesired side effects.

Returns
An iterator over the values in the map.
code »getValues ( )!Array.<V>

Returns the values of the map.

Returns
The values in the map.
Returns
Whether the map is empty.

Removes a key-value pair based on the key. This is O(logN) amortized due to updating the keys array whenever the count becomes half the size of the keys - in the keys array.

Parameters
key: *
The key to remove.
Returns
Whether object was removed.
code »set ( key, value )*

Adds a key-value pair to the map.

Parameters
key: *
The key.
value: *
The value to add.
Returns
Some subclasses return a value.
Returns
Object representation of the map.

Returns a new map in which all the keys and values are interchanged + in the keys array.

Parameters
key: *
The key to remove.
Returns
Whether object was removed.
code »set ( key, value )*

Adds a key-value pair to the map.

Parameters
key: *
The key.
value: V
The value to add.
Returns
Some subclasses return a value.
Returns
Object representation of the map.

Returns a new map in which all the keys and values are interchanged (keys become values and values become keys). If multiple keys map to the same value, the chosen transposed value is implementation-dependent. - It acts very similarly to {goog.object.transpose(Object)}.

Returns
The transposed map.

Instance Properties

The number of key value pairs in the map.

An array of keys. This is necessary for two reasons: + It acts very similarly to {goog.object.transpose(Object)}.

Returns
The transposed map.

Instance Properties

The number of key value pairs in the map.

An array of keys. This is necessary for two reasons: 1. Iterating the keys using for (var key in this.map_) allocates an object for every key in IE which is really bad for IE6 GC perf. 2. Without a side data structure, we would need to escape all the keys @@ -24,5 +24,5 @@ This array can contain deleted keys so it's necessary to check the map as well to see if the key is still in the map (this doesn't require a - memory allocation in IE).

Underlying JS object used to implement the map.

Version used to detect changes while iterating.

Static Functions

Default equality test for values.

Parameters
a: *
The first value.
b: *
The second value.
Returns
Whether a and b reference the same object.

Safe way to test for hasOwnProperty. It even allows testing for - 'hasOwnProperty'.

Parameters
obj: Object
The object to test for presence of the given key.
key: *
The key to check for.
Returns
Whether the object has the key.
\ No newline at end of file + memory allocation in IE).

Underlying JS object used to implement the map.

Version used to detect changes while iterating.

Static Functions

Default equality test for values.

Parameters
a: *
The first value.
b: *
The second value.
Returns
Whether a and b reference the same object.

Safe way to test for hasOwnProperty. It even allows testing for + 'hasOwnProperty'.

Parameters
obj: Object
The object to test for presence of the given key.
key: *
The key to check for.
Returns
Whether the object has the key.
\ No newline at end of file diff --git a/docs/class_goog_structs_Set.html b/docs/class_goog_structs_Set.html new file mode 100644 index 0000000..306ff30 --- /dev/null +++ b/docs/class_goog_structs_Set.html @@ -0,0 +1,28 @@ +goog.structs.Set

Class goog.structs.Set.<T>

code »
All implemented interfaces:
goog.structs.Collection.<(T|null)>

A set that can contain both primitives and objects. Adding and removing + elements is O(1). Primitives are treated as identical if they have the same + type and convert to the same string. Objects are treated as identical only + if they are references to the same object. WARNING: A goog.structs.Set can + contain both 1 and (new Number(1)), because they are not the same. WARNING: + Adding (new Number(1)) twice will yield two distinct elements, because they + are two different objects. WARNING: Any object that is added to a + goog.structs.Set will be modified! Because goog.getUid() is used to + identify objects, every object in the set will be mutated.

Constructor

goog.structs.Set ( opt_values )
Parameters
opt_values: (Array.<T>|Object)=
Initial values to start with.
Show:

Instance Methods

Returns an iterator that iterates over the elements in this set.

Parameters
opt_keys: boolean=
This argument is ignored.
Returns
An iterator over the elements in this set.
code »add ( element )

Add a primitive or an object to the set.

Parameters
element: T
The primitive or object to add.

Adds all the values in the given collection to this set.

Parameters
col: (Array.<T>|goog.structs.Collection.<T>|Object)
A collection + containing the elements to add.

Removes all elements from this set.

Creates a shallow clone of this set.

Returns
A new set containing all the same elements as + this set.
code »contains ( element )boolean

Tests whether this set contains the given element.

Parameters
element: T
The primitive or object to test for.
Returns
True if this set contains the given element.

Tests whether this set contains all the values in a given collection. + Repeated elements in the collection are ignored, e.g. (new + goog.structs.Set([1, 2])).containsAll([1, 1]) is True.

Parameters
col: (goog.structs.Collection.<T>|Object)
A collection-like object.
Returns
True if the set contains all elements.

Finds all values that are present in this set and not in the given + collection.

Parameters
col: (Array.<T>|goog.structs.Collection.<T>|Object)
A collection.
Returns
A new set containing all the values + (primitives or objects) present in this set but not in the given + collection.

Tests whether the given collection consists of the same elements as this set, + regardless of order, without repetition. Primitives are treated as equal if + they have the same type and convert to the same string; objects are treated + as equal if they are references to the same object. This operation is O(n).

Parameters
col: (goog.structs.Collection.<T>|Object)
A collection.
Returns
True if the given collection consists of the same elements + as this set, regardless of order, without repetition.
Returns
The number of elements in the set.
code »getValues ( )!Array.<T>

Returns an array containing all the elements in this set.

Returns
An array containing all the elements in this set.

Finds all values that are present in both this set and the given collection.

Parameters
col: (Array.<S>|Object)
A collection.
Returns
A new set containing all the values + (primitives or objects) present in both this set and the given + collection.

Tests whether this set is empty.

Returns
True if there are no elements in this set.

Tests whether the given collection contains all the elements in this set. + Primitives are treated as equal if they have the same type and convert to the + same string; objects are treated as equal if they are references to the same + object. This operation is O(n).

Parameters
col: (goog.structs.Collection.<T>|Object)
A collection.
Returns
True if this set is a subset of the given collection.
code »remove ( element )boolean

Removes the given element from this set.

Parameters
element: T
The primitive or object to remove.
Returns
Whether the element was found and removed.

Removes all values in the given collection from this set.

Parameters
col: (Array.<T>|goog.structs.Collection.<T>|Object)
A collection + containing the elements to remove.

Instance Properties

Class for Hash Map datastructure.

Static Functions

Obtains a unique key for an element of the set. Primitives will yield the + same key if they have the same type and convert to the same string. Object + references will yield the same key only if they refer to the same object.

Parameters
val: *
Object or primitive value to get a key for.
Returns
A unique key for this value/object.
\ No newline at end of file diff --git a/docs/class_goog_testing_AsyncTestCase.html b/docs/class_goog_testing_AsyncTestCase.html new file mode 100644 index 0000000..22a3b14 --- /dev/null +++ b/docs/class_goog_testing_AsyncTestCase.html @@ -0,0 +1,81 @@ +goog.testing.AsyncTestCase

Class goog.testing.AsyncTestCase

code »
goog.testing.TestCase
+  └ goog.testing.AsyncTestCase

A test case that is capable of running tests the contain asynchronous logic.

Constructor

goog.testing.AsyncTestCase ( opt_name )
Parameters
opt_name: string=
A descriptive name for the test case.

Classes

goog.testing.AsyncTestCase.ControlBreakingException
An exception class used solely for control flow.
Show:

Type Definitions

code »goog.testing.AsyncTestCase.TopStackFuncResult_ : {controlBreakingExceptionThrown: boolean, message: string}
Represents result of top stack function call.

Instance Methods

Defined in goog.testing.AsyncTestCase

Calls the given function, redirecting any exceptions to doAsyncError.

Parameters
func: Function
The function to call.
Returns
Returns a + TopStackFuncResult_.

Continue with the next step in the test cycle.

Starts the tests.

code »dbgLog_ ( message )

Logs the given debug message to the console (when enabled).

Parameters
message: string
The message to log.

Handles an exception thrown by a test.

Parameters
opt_e: *=
The exception object associated with the failure + or a string.
Throws
throws a ControlBreakingException.

Calls the tearDown function, catching any errors, and then moves on to + the next step in the testing cycle.

Step 3: Call test.execute().

Step 1: Move to the next test.

Step 5: Call doSuccess()

Sets up the test page and then waits untill the test case has been marked + as ready before executing the tests.

Step 2: Call setUp().

Step 4: Call tearDown().

Wraps doAsyncError() for when we are sure that the test runner has no user + code above it in the stack.

Parameters
opt_e: (string|Error)=
The exception object associated with the + failure or a string.

Enables verbose logging of what is happening inside of the AsyncTestCase.

Ends the current test step and queues the next test step to run.

Finalizes the test case, called when the tests have finished executing.

The current step name.

Returns
Step name.

Replaces the asserts.js assert_() and fail() functions with a wrappers to + catch the exceptions.

Sets a window.onerror handler for catching exceptions that happen in async + callbacks. Note that as of Safari 3.1, Safari does not support this.

code »pump_ ( opt_doFirst )

Calls the next callback when the isReady_ flag is true.

Parameters
opt_doFirst: Function=
A function to call before pumping.
Throws
a ControlBreakingException if there were any failing steps.

Sets up the test page and then waits until the test case has been marked + as ready before executing the tests.

code »setNextStep_ ( func, name )

Sets the next function to call in our sequence of async callbacks.

Parameters
func: Function
The function that executes the next step.
name: string
A description of the next step.

Signals once to continue with the test. If this is the last signal that the + test was waiting on, call continueTesting.

Enables the timeout timer. This timer fires unless continueTesting is + called.

Disables the timeout timer.

Unhooks window.onerror and _assert.

code »waitForAsync ( opt_name )

Informs the testcase not to continue to the next step in the test cycle + until continueTesting is called.

Parameters
opt_name: string=
A description of what we are waiting for.
code »waitForSignals ( times, opt_name )

Informs the testcase not to continue to the next step in the test cycle + until signal is called the specified number of times. Within a test, this + function behaves additively if called multiple times; the number of signals + to wait for will be the sum of all expected number of signals this function + was called with.

Parameters
times: number
The number of signals to receive before + continuing testing.
opt_name: string=
A description of what we are waiting for.

Defined in goog.testing.TestCase

code »add ( test )

Adds a new test to the test case.

Parameters
test: goog.testing.TestCase.Test
The test to add.
code »addNewTest ( name, ref, opt_scope )

Creates and adds a new test. + + Convenience function to make syntax less awkward when not using automatic + test discovery.

Parameters
name: string
The test name.
ref: !Function
Reference to the test function.
opt_scope: !Object=
Optional scope that the test function should be + called in.

Adds any functions defined in the global scope that correspond to + lifecycle events for the test case. Overrides setUp, tearDown, setUpPage, + tearDownPage and runTests if they are defined.

Adds any functions defined in the global scope that are prefixed with "test" + to the test case.

Clears a timeout created by this.timeout().

Parameters
id: number
A timeout id.

Counts the number of files that were loaded for dependencies that are + required to run the test.

Returns
The number of files loaded.

Creates a goog.testing.TestCase.Test from an auto-discovered + function.

Parameters
name: string
The name of the function.
ref: function(): void
The auto-discovered function.
Returns
The newly created test.
code »doError ( test, opt_e )

Handles a test that failed.

Parameters
test: goog.testing.TestCase.Test
The test that failed.
opt_e: *=
The exception object associated with the + failure or a string.

Handles a test that passed.

Parameters
test: goog.testing.TestCase.Test
The test that passed.

Executes each of the tests.

Returns the number of tests actually run in the test case, i.e. subtracting + any which are skipped.

Returns
The number of un-ignored tests.
Returns
The function name prefix used to auto-discover tests.
Returns
Time since the last batch of tests was started.

Returns the number of tests contained in the test case.

Returns
The number of tests.
code »getGlobals ( opt_prefix )!Array

Gets list of objects that potentially contain test cases. For IE 8 and below, + this is the global "this" (for properties set directly on the global this or + window) and the RuntimeObject (for global variables and functions). For all + other browsers, the array simply contains the global this.

Parameters
opt_prefix: string=
An optional prefix. If specified, only get things + under this prefix. Note that the prefix is only honored in IE, since it + supports the RuntimeObject: + http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx + TODO: Remove this option.
Returns
A list of objects that should be inspected.
Returns
The name of the test.

Returns the number of script files that were loaded in order to run the test.

Returns
The number of script files.
code »getReport ( opt_verbose )string

Returns a string detailing the results from the test.

Parameters
opt_verbose: boolean=
If true results will include data about all + tests, not just what failed.
Returns
The results from the test.

Returns the amount of time it took for the test to run.

Returns
The run time, in milliseconds.

Returns the test results object: a map from test names to a list of test + failures (if any exist).

Returns
Tests results object.

Gets the tests.

Returns
The test array.

Returns the current time.

Returns
HH:MM:SS.
Returns
Whether the test case is running inside the multi test + runner.
Returns
Whether the test was a success.
code »log ( val )

Logs an object to the console, if available.

Parameters
val: *
The value to log. Will be ToString'd.
Parameters
name: string
Failed test name.
opt_e: *=
The exception object associated with the + failure or a string.
Returns
Error object.

Checks to see if the test should be marked as failed before it is run. + + If there was an error in setUpPage, we treat that as a failure for all tests + and mark them all as having failed.

Parameters
testCase: goog.testing.TestCase.Test
The current test case.
Returns
Whether the test was marked as failed.

Returns the current test and increments the pointer.

Returns
The current test case.
Returns
The current time in milliseconds, don't use goog.now as some + tests override it.

Reorders the tests depending on the order field.

Parameters
tests: Array.<goog.testing.TestCase.Test>
An array of tests to + reorder.
code »pad_ ( number )string

Pads a number to make it have a leading zero if it's less than 10.

Parameters
number: number
The number to pad.
Returns
The resulting string.

Resets the test case pointer, so that next returns the first test.

code »saveMessage ( message )

Saves a message to the result set.

Parameters
message: string
The message to save.
code »setBatchTime ( batchTime )
Parameters
batchTime: number
Time since the last batch of tests was started.

Sets the callback function that should be executed when the tests have + completed.

Parameters
fn: Function
The callback function.
code »setTests ( tests )

Sets the tests.

Parameters
tests: !Array.<goog.testing.TestCase.Test>
A new test array.

Gets called before every goog.testing.TestCase.Test is been executed. Can be + overridden to add set up functionality to each test.

Gets called before any tests are executed. Can be overridden to set up the + environment for the whole test case.

Can be overridden in test classes to indicate whether the tests in a case + should be run in that particular situation. For example, this could be used + to stop tests running in a particular browser, where browser support for + the class under test was absent.

Returns
Whether any of the tests in the case should be run.

Gets called after every goog.testing.TestCase.Test has been executed. Can be + overriden to add tear down functionality to each test.

Gets called after all tests have been executed. Can be overridden to tear + down the entire test case.

code »timeout ( fn, time )number

Calls a function after a delay, using the protected timeout.

Parameters
fn: Function
The function to call.
time: number
Delay in milliseconds.
Returns
The timeout id.

Trims a path to be only that after google3.

Parameters
path: string
The path to trim.
Returns
The resulting string.

Instance Properties

Defined in goog.testing.AsyncTestCase

Marks if the cleanUp() function has been called for the currently running + test.

The stage of the test we are currently on.

The name of the stage of the test we are currently on.

Turn on extra logging to help debug failing async. tests.

Number of signals to wait for before continuing testing when waitForSignals + is used.

A flag to prevent recursive exception handling.

Flag used to determine if we can move to the next step in the testing loop.

The stage of the test we should run next.

The name of the stage of the test we should run next.

The number of times we have thrown a ControlBreakingException so that we + know not to complain in our window.onerror handler. In Webkit, window.onerror + is not supported, and so this counter will keep going up but we won't care + about it.

A reference to the original window.onerror function.

Number of signals received.

Flag that tells us if there is a function in the call stack that will make + a call to pump_().

How long to wait for a single step of a test to complete in milliseconds. + A step starts when a call to waitForAsync() is made.

How long to wait after a failed test before moving onto the next one. + The purpose of this is to allow any pending async callbacks from the failing + test to finish up and not cause the next test to fail.

The handle to the current setTimeout timer.

Defined in goog.testing.TestCase

Time since the last batch of tests was started, if batchTime exceeds + #maxRunTime a timeout will be used to stop the tests blocking the + browser and a new batch will be started.

Pointer to the current test.

Exception object that was detected before a test runs.

A name for the test case.

Optional callback that will be executed when the test has finalized.

The order to run the auto-discovered tests in.

Object used to encapsulate the test results.

Whether the test case is running.

Timestamp for when the test was started.

Whether the test case has ever tried to execute.

Set of test names and/or indices to execute, or null if all tests should + be executed. + + Indices are included to allow automation tools to run a subset of the + tests without knowing the exact contents of the test file. + + Indices should only be used with SORTED ordering. + + Example valid values: +

    +
  • [testName] +
  • [testName1, testName2] +
  • [2] - will run the 3rd test in the order specified +
  • [1,3,5] +
  • [testName1, testName2, 3, 5] - will work +

    Array of test functions that can be executed.

    Static Functions

    Preferred way of creating an AsyncTestCase. Creates one and initializes it + with the G_testRunner.

    Parameters
    opt_name: string=
    A descriptive name for the test case.
    Returns
    The created AsyncTestCase.

    Static Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_AsyncTestCase_ControlBreakingException.html b/docs/class_goog_testing_AsyncTestCase_ControlBreakingException.html new file mode 100644 index 0000000..1336ae2 --- /dev/null +++ b/docs/class_goog_testing_AsyncTestCase_ControlBreakingException.html @@ -0,0 +1 @@ +goog.testing.AsyncTestCase.ControlBreakingException

    Class goog.testing.AsyncTestCase.ControlBreakingException

    code »

    An exception class used solely for control flow.

    Constructor

    goog.testing.AsyncTestCase.ControlBreakingException ( opt_message )
    Parameters
    opt_message: string=
    Error message.
    Show:

    Instance Methods

    code »toString ( )string

    Instance Properties

    Marks this object as a ControlBreakingException

    The exception message.

    Static Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_FunctionCall.html b/docs/class_goog_testing_FunctionCall.html new file mode 100644 index 0000000..1b6b6e8 --- /dev/null +++ b/docs/class_goog_testing_FunctionCall.html @@ -0,0 +1,2 @@ +goog.testing.FunctionCall

    Class goog.testing.FunctionCall

    code »

    Struct for a single function call.

    Constructor

    goog.testing.FunctionCall ( func, thisContext, args, ret, error )
    Parameters
    func: !Function
    The called function.
    thisContext: !Object
    this context of called function.
    args: !Arguments
    Arguments of the called function.
    ret: *
    Return value of the function or undefined in case of error.
    error: *
    The error thrown by the function or null if none.
    Show:

    Instance Methods

    code »getArgument ( index )*

    Returns the nth argument of the called function.

    Parameters
    index: number
    0-based index of the argument.
    Returns
    The argument value or undefined if there is no such argument.
    Returns
    Arguments of the called function.
    code »getError ( )*
    Returns
    The error thrown by the function or null if none.
    Returns
    The called function.
    Returns
    Return value of the function or undefined in case of error.
    Returns
    this context of called function. It is the same as + the created object if the function is a constructor.

    Instance Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_JsUnitException.html b/docs/class_goog_testing_JsUnitException.html new file mode 100644 index 0000000..2b0f8ae --- /dev/null +++ b/docs/class_goog_testing_JsUnitException.html @@ -0,0 +1,2 @@ +goog.testing.JsUnitException

    Class goog.testing.JsUnitException

    code »
    Error
    +  └ goog.testing.JsUnitException

    Constructor

    goog.testing.JsUnitException ( comment, opt_message )
    Parameters
    comment: string
    A summary for the exception.
    opt_message: ?string=
    A description of the exception.
    Show:

    Instance Methods

    Defined in goog.testing.JsUnitException

    code »toString ( )string

    Instance Properties

    Defined in goog.testing.JsUnitException

    code »comment : (null|string)

    Static Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_LooseExpectationCollection.html b/docs/class_goog_testing_LooseExpectationCollection.html new file mode 100644 index 0000000..cf6bd52 --- /dev/null +++ b/docs/class_goog_testing_LooseExpectationCollection.html @@ -0,0 +1,4 @@ +goog.testing.LooseExpectationCollection

    Class goog.testing.LooseExpectationCollection

    code »

    This class is an ordered collection of expectations for one method. Since + the loose mock does most of its verification at the time of $verify, this + class is necessary to manage the return/throw behavior when the mock is + being called.

    Constructor

    goog.testing.LooseExpectationCollection ( )
    Show:

    Instance Methods

    code »addExpectation ( expectation )

    Adds an expectation to this collection.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation to add.

    Gets the list of expectations in this collection.

    Returns
    The array of expectations.

    Instance Properties

    The list of expectations. All of these should have the same name.

    \ No newline at end of file diff --git a/docs/class_goog_testing_LooseMock.html b/docs/class_goog_testing_LooseMock.html new file mode 100644 index 0000000..c08bb54 --- /dev/null +++ b/docs/class_goog_testing_LooseMock.html @@ -0,0 +1,25 @@ +goog.testing.LooseMock

    Class goog.testing.LooseMock

    code »
    goog.testing.Mock
    +  └ goog.testing.LooseMock
    All implemented interfaces:
    goog.testing.MockInterface

    This is a mock that does not care about the order of method calls. As a + result, it won't throw exceptions until verify() is called. The only + exception is that if a method is called that has no expectations, then an + exception will be thrown.

    Constructor

    goog.testing.LooseMock ( objectToMock, opt_ignoreUnexpectedCalls, opt_mockStaticMethods, opt_createProxy )
    Parameters
    objectToMock: (Object|Function)
    The object that should be mocked, or + the constructor of an object to mock.
    opt_ignoreUnexpectedCalls: boolean=
    Whether to ignore unexpected + calls.
    opt_mockStaticMethods: boolean=
    An optional argument denoting that + a mock should be constructed from the static functions of a class.
    opt_createProxy: boolean=
    An optional argument denoting that + a proxy for the target mock should be created.
    Show:

    Instance Methods

    Defined in goog.testing.LooseMock

    code »$recordCall ( name, args )*
    Parameters
    name
    args

    A setter for the ignoreUnexpectedCalls field.

    Parameters
    ignoreUnexpectedCalls: boolean
    Whether to ignore unexpected calls.
    Returns
    This mock object.

    Defined in goog.testing.Mock

    Allows the expectation to be called any number of times.

    Returns
    This mock object.

    Render the provided argument array to a string to help + clients with debugging tests.

    Parameters
    args: ?Array
    The arguments passed to the mock.
    Returns
    Human-readable string.

    Allows the expectation to be called any number of times, as long as it's + called once.

    Returns
    This mock object.

    Allows the expectation to be called 0 or 1 times.

    Returns
    This mock object.
    code »$do ( expectation, args )*

    If this expectation defines a function to be called, + it will be called and its result will be returned. + Otherwise, if the expectation expects to throw, it will throw. + Otherwise, this method will return defined value.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    args: Array
    The arguments to the method.
    Returns
    The return value expected by the mock.

    Specifies a function to call for currently pending expectation. + Note, that using this method overrides declarations made + using $returns() and $throws() methods.

    Parameters
    func: Function
    The function to call.
    Returns
    This mock object.

    Initializes the functions on the mock object.

    Parameters
    objectToMock: Object
    The object being mocked.
    code »$maybeThrow ( expectation )

    If the expectation expects to throw, this method will throw.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    code »$mockMethod ( name )*

    The function that replaces all methods on the mock object.

    Parameters
    name: string
    The name of the method being mocked.
    Returns
    In record mode, returns the mock object. In replay mode, returns + whatever the creator of the mock set as the return value.

    Disallows the expectation from being called.

    Returns
    This mock object.

    Allows the expectation to be called exactly once.

    Returns
    This mock object.

    Throws an exception and records that an exception was thrown.

    Parameters
    ex: Object
    Exception.
    Throws
    Object
    #ex.

    Registers a verfifier function to use when verifying method argument lists.

    Parameters
    methodName: string
    The name of the method for which the verifierFn + should be used.
    fn: Function
    Argument list verifier function. Should take 2 argument + arrays as arguments, and return true if they are considered equivalent.
    Returns
    This mock object.

    Specifies a return value for the currently pending expectation.

    Parameters
    val: *
    The return value.
    Returns
    This mock object.
    code »$throwCallException ( name, args, opt_expectation )

    Throw an exception based on an incorrect method call.

    Parameters
    name: string
    Name of method called.
    args: ?Array
    Arguments passed to the mock.
    opt_expectation: goog.testing.MockExpectation=
    Expected next call, + if any.
    code »$throwException ( comment, opt_message )

    Throws an exception and records that an exception was thrown.

    Parameters
    comment: string
    A short comment about the exception.
    opt_message: ?string=
    A longer message about the exception.
    Throws
    Object
    JsUnitException object.

    Specifies a value for the currently pending expectation to throw.

    Parameters
    val: *
    The value to throw.
    Returns
    This mock object.

    Specifies the number of times the expectation should be called.

    Parameters
    times: number
    The number of times this method will be called.
    Returns
    This mock object.
    code »$verifyCall ( expectation, name, args )boolean

    Verifies that a method call matches an expectation.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation to check.
    name: string
    The name of the called method.
    args: ?Array
    The arguments passed to the mock.
    Returns
    Whether the call matches the expectation.

    Instance Properties

    Defined in goog.testing.LooseMock

    The calls that have been made; we cache them to verify at the end. Each + element is an array where the first element is the name, and the second + element is the arguments.

    A map of method names to a LooseExpectationCollection for that method.

    Whether to ignore unexpected calls.

    Defined in goog.testing.Mock

    Map of argument name to optional argument list verifier function.

    The expectation currently being created. All methods that modify the + current expectation return the Mock object for easy chaining, so this is + where we keep track of the expectation that's currently being modified.

    A proxy for the mock. This can be used for dependency injection in lieu of + the mock if the test requires a strict instanceof check.

    Whether or not we are in recording mode.

    First exception thrown by this mock; used in $verify.

    Static Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_Mock.html b/docs/class_goog_testing_Mock.html new file mode 100644 index 0000000..6cfe20d --- /dev/null +++ b/docs/class_goog_testing_Mock.html @@ -0,0 +1,29 @@ +goog.testing.Mock

    Class goog.testing.Mock

    code »
    All implemented interfaces:
    goog.testing.MockInterface

    The base class for a mock object.

    Constructor

    goog.testing.Mock ( objectToMock, opt_mockStaticMethods, opt_createProxy )
    Parameters
    objectToMock: (Object|Function)
    The object that should be mocked, or + the constructor of an object to mock.
    opt_mockStaticMethods: boolean=
    An optional argument denoting that + a mock should be constructed from the static functions of a class.
    opt_createProxy: boolean=
    An optional argument denoting that + a proxy for the target mock should be created.
    Show:

    Instance Methods

    Allows the expectation to be called any number of times.

    Returns
    This mock object.

    Render the provided argument array to a string to help + clients with debugging tests.

    Parameters
    args: ?Array
    The arguments passed to the mock.
    Returns
    Human-readable string.

    Allows the expectation to be called any number of times, as long as it's + called once.

    Returns
    This mock object.

    Allows the expectation to be called 0 or 1 times.

    Returns
    This mock object.
    code »$do ( expectation, args )*

    If this expectation defines a function to be called, + it will be called and its result will be returned. + Otherwise, if the expectation expects to throw, it will throw. + Otherwise, this method will return defined value.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    args: Array
    The arguments to the method.
    Returns
    The return value expected by the mock.

    Specifies a function to call for currently pending expectation. + Note, that using this method overrides declarations made + using $returns() and $throws() methods.

    Parameters
    func: Function
    The function to call.
    Returns
    This mock object.

    Initializes the functions on the mock object.

    Parameters
    objectToMock: Object
    The object being mocked.
    code »$maybeThrow ( expectation )

    If the expectation expects to throw, this method will throw.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    code »$mockMethod ( name )*

    The function that replaces all methods on the mock object.

    Parameters
    name: string
    The name of the method being mocked.
    Returns
    In record mode, returns the mock object. In replay mode, returns + whatever the creator of the mock set as the return value.

    Disallows the expectation from being called.

    Returns
    This mock object.

    Allows the expectation to be called exactly once.

    Returns
    This mock object.

    Throws an exception and records that an exception was thrown.

    Parameters
    ex: Object
    Exception.
    Throws
    Object
    #ex.
    code »$recordCall ( name, args )*

    Records an actual method call, intended to be overridden by a + subclass. The subclass must find the pending expectation and return the + correct value.

    Parameters
    name: string
    The name of the method being called.
    args: Array
    The arguments to the method.
    Returns
    The return expected by the mock.

    Records the currently pending expectation, intended to be overridden by a + subclass.

    Registers a verfifier function to use when verifying method argument lists.

    Parameters
    methodName: string
    The name of the method for which the verifierFn + should be used.
    fn: Function
    Argument list verifier function. Should take 2 argument + arrays as arguments, and return true if they are considered equivalent.
    Returns
    This mock object.

    Switches from recording to replay mode.

    Resets the state of this mock object. This clears all pending expectations + without verifying, and puts the mock in recording mode.

    Specifies a return value for the currently pending expectation.

    Parameters
    val: *
    The return value.
    Returns
    This mock object.
    code »$throwCallException ( name, args, opt_expectation )

    Throw an exception based on an incorrect method call.

    Parameters
    name: string
    Name of method called.
    args: ?Array
    Arguments passed to the mock.
    opt_expectation: goog.testing.MockExpectation=
    Expected next call, + if any.
    code »$throwException ( comment, opt_message )

    Throws an exception and records that an exception was thrown.

    Parameters
    comment: string
    A short comment about the exception.
    opt_message: ?string=
    A longer message about the exception.
    Throws
    Object
    JsUnitException object.

    Specifies a value for the currently pending expectation to throw.

    Parameters
    val: *
    The value to throw.
    Returns
    This mock object.

    Specifies the number of times the expectation should be called.

    Parameters
    times: number
    The number of times this method will be called.
    Returns
    This mock object.

    Verify that all of the expectations were met. Should be overridden by + subclasses.

    code »$verifyCall ( expectation, name, args )boolean

    Verifies that a method call matches an expectation.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation to check.
    name: string
    The name of the called method.
    args: ?Array
    The arguments passed to the mock.
    Returns
    Whether the call matches the expectation.

    Instance Properties

    Map of argument name to optional argument list verifier function.

    The expectation currently being created. All methods that modify the + current expectation return the Mock object for easy chaining, so this is + where we keep track of the expectation that's currently being modified.

    A proxy for the mock. This can be used for dependency injection in lieu of + the mock if the test requires a strict instanceof check.

    Whether or not we are in recording mode.

    First exception thrown by this mock; used in $verify.

    Static Properties

    Option that may be passed when constructing function, method, and + constructor mocks. Indicates that the expected calls should be accepted in + any order.

    This array contains the name of the functions that are part of the base + Object prototype. + Basically a copy of goog.object.PROTOTYPE_FIELDS_.

    Option that may be passed when constructing function, method, and + constructor mocks. Indicates that the expected calls should be accepted in + the recorded order only.

    \ No newline at end of file diff --git a/docs/class_goog_testing_MockClock.html b/docs/class_goog_testing_MockClock.html new file mode 100644 index 0000000..894cd55 --- /dev/null +++ b/docs/class_goog_testing_MockClock.html @@ -0,0 +1,66 @@ +goog.testing.MockClock

    Class goog.testing.MockClock

    code »
    goog.Disposable
    +  └ goog.testing.MockClock
    All implemented interfaces:
    goog.disposable.IDisposable

    Class for unit testing code that uses setTimeout and clearTimeout. + + NOTE: If you are using MockClock to test code that makes use of + goog.fx.Animation, then you must either: + + 1. Install and dispose of the MockClock in setUpPage() and tearDownPage() + respectively (rather than setUp()/tearDown()). + + or + + 2. Ensure that every test clears the animation queue by calling + mockClock.tick(x) at the end of each test function (where `x` is large + enough to complete all animations). + + Otherwise, if any animation is left pending at the time that + MockClock.dispose() is called, that will permanently prevent any future + animations from playing on the page.

    Constructor

    goog.testing.MockClock ( opt_autoInstall )
    Parameters
    opt_autoInstall: boolean=
    Install the MockClock at construction time.
    Show:

    Instance Methods

    Defined in goog.testing.MockClock

    Clears a requestAnimationFrame. + Mock implementation for cancelRequestAnimationFrame.

    Parameters
    timeoutKey: number
    The requestAnimationFrame key to clear.
    code »clearInterval_ ( timeoutKey )

    Clears an interval. + Mock implementation for clearInterval.

    Parameters
    timeoutKey: number
    The interval key to clear.
    code »clearTimeout_ ( timeoutKey )

    Clears a timeout. + Mock implementation for clearTimeout.

    Parameters
    timeoutKey: number
    The timeout key to clear.

    Signals that the mock clock has been reset, allowing objects that + maintain their own internal state to reset.

    Returns
    The MockClock's current time in milliseconds.
    Returns
    delay The amount of time between when a timeout is + scheduled to fire and when it actually fires, in milliseconds. May + be negative.
    Returns
    The number of timeouts that have been scheduled.

    Installs the MockClock by overriding the global object's implementation of + setTimeout, setInterval, clearTimeout and clearInterval.

    code »isTimeoutSet ( timeoutKey )boolean
    Parameters
    timeoutKey: number
    The timeout key.
    Returns
    Whether the timer has been set and not cleared, + independent of the timeout's expiration. In other words, the timeout + could have passed or could be scheduled for the future. Either way, + this function returns true or false depending only on whether the + provided timeoutKey represents a timeout that has been set and not + cleared.

    Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame.

    Schedules a function to be called when an animation frame is triggered. + Mock implementation for requestAnimationFrame.

    Parameters
    funcToCall: Function
    The function to call.
    Returns
    The number of timeouts created.

    Resets the MockClock, removing all timeouts that are scheduled and resets + the fake timer count.

    Runs any function that is scheduled before a certain time. Timeouts can + be made to fire early or late if timeoutDelay_ is non-0.

    Parameters
    endTime: number
    The latest time in the range, in milliseconds.
    code »scheduleFunction_ ( timeoutKey, funcToCall, millis, recurring )

    Schedules a function to be run at a certain time.

    Parameters
    timeoutKey: number
    The timeout key.
    funcToCall: Function
    The function to call.
    millis: number
    The number of milliseconds to call it in.
    recurring: boolean
    Whether to function call should recur.
    code »setImmediate_ ( funcToCall )number

    Schedules a function to be called immediately after the current JS + execution. + Mock implementation for setImmediate.

    Parameters
    funcToCall: Function
    The function to call.
    Returns
    The number of timeouts created.
    code »setInterval_ ( funcToCall, millis )number

    Schedules a function to be called every millis milliseconds. + Mock implementation for setInterval.

    Parameters
    funcToCall: Function
    The function to call.
    millis: number
    The number of milliseconds between calls.
    Returns
    The number of timeouts created.

    Sets the amount of time between when a timeout is scheduled to fire and when + it actually fires.

    Parameters
    delay: number
    The delay in milliseconds. May be negative.
    code »setTimeout_ ( funcToCall, millis )number

    Schedules a function to be called after millis milliseconds. + Mock implementation for setTimeout.

    Parameters
    funcToCall: Function
    The function to call.
    millis: number
    The number of milliseconds to call it after.
    Returns
    The number of timeouts created.
    code »tick ( opt_millis )number

    Increments the MockClock's time by a given number of milliseconds, running + any functions that are now overdue.

    Parameters
    opt_millis: number=
    Number of milliseconds to increment the counter. + If not specified, clock ticks 1 millisecond.
    Returns
    Current mock time in milliseconds.

    Removes the MockClock's hooks into the global object's functions and revert + to their original values.

    Defined in goog.Disposable

    code »<T> addOnDisposeCallback ( callback, opt_scope )

    Invokes a callback function when this object is disposed. Callbacks are + invoked in the order in which they were added.

    Parameters
    callback: function(this: T): ?
    The callback function.
    opt_scope: T=
    An optional scope to call the callback in.
    code »dispose ( )void

    Disposes of the object. If the object hasn't already been disposed of, calls + #disposeInternal. Classes that extend goog.Disposable should + override #disposeInternal in order to delete references to COM + objects, DOM nodes, and other disposable objects. Reentrant.

    Returns
    Nothing.
    Deprecated: Use #isDisposed instead.
    Returns
    Whether the object has been disposed of.
    Returns
    Whether the object has been disposed of.

    Associates a disposable object with this object so that they will be disposed + together.

    Parameters
    disposable: goog.disposable.IDisposable
    that will be disposed when + this object is disposed.

    Instance Properties

    Defined in goog.testing.MockClock

    Map of deleted keys. These keys represents keys that were deleted in a + clearInterval, timeoutid -> object.

    The current simulated time in milliseconds.

    Reverse-order queue of timers to fire. + + The last item of the queue is popped off. Insertion happens from the + right. For example, the expiration times for each element of the queue + might be in the order 300, 200, 200.

    PropertyReplacer instance which overwrites and resets setTimeout, + setInterval, etc. or null if the MockClock is not installed.

    Additional delay between the time a timeout was set to fire, and the time + it actually fires. Useful for testing workarounds for this Firefox 2 bug: + https://bugzilla.mozilla.org/show_bug.cgi?id=291386 + May be negative.

    Count of the number of timeouts made.

    Defined in goog.Disposable

    If monitoring the goog.Disposable instances is enabled, stores the creation + stack trace of the Disposable instance.

    Whether the object has been disposed of.

    Callbacks to invoke when this object is disposed.

    Static Functions

    Inserts a timer descriptor into a descending-order queue. + + Later-inserted duplicates appear at lower indices. For example, the + asterisk in (5,4,*,3,2,1) would be the insertion point for 3.

    Parameters
    timeout: Object
    The timeout to insert, with numerical runAtMillis + property.
    queue: Array.<Object>
    The queue to insert into, with each element + having a numerical runAtMillis property.

    Static Properties

    Maximum 32-bit signed integer. + + Timeouts over this time return immediately in many browsers, due to integer + overflow. Such known browsers include Firefox, Chrome, and Safari, but not + IE.

    Default wait timeout for mocking requestAnimationFrame (in milliseconds).

    \ No newline at end of file diff --git a/docs/class_goog_testing_MockControl.html b/docs/class_goog_testing_MockControl.html new file mode 100644 index 0000000..661a96c --- /dev/null +++ b/docs/class_goog_testing_MockControl.html @@ -0,0 +1,22 @@ +goog.testing.MockControl

    Class goog.testing.MockControl

    code »

    Controls a set of mocks. Controlled mocks are replayed, verified, and + cleaned-up at the same time.

    Constructor

    goog.testing.MockControl ( )
    Show:

    Instance Methods

    Calls replay on each controlled mock.

    Calls reset on each controlled mock.

    Calls tearDown on each controlled mock, if necesssary.

    Calls verify on each controlled mock.

    Takes control of this mock.

    Parameters
    mock: goog.testing.MockInterface
    Mock to be controlled.
    Returns
    The same mock passed in, + for convenience.
    code »createConstructorMock ( scope, constructorName, opt_strictness )!goog.testing.MockInterface

    Creates a controlled MethodMock for a constructor. Passes its arguments + through to the MethodMock constructor. See + goog.testing.createConstructorMock for details.

    Parameters
    scope: Object
    The scope of the constructor to be mocked out.
    constructorName: string
    The name of the function we're going to mock.
    opt_strictness: number=
    One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
    Returns
    The mocked method.
    code »createFunctionMock ( opt_functionName, opt_strictness )goog.testing.MockInterface

    Creates a controlled FunctionMock. Passes its arguments through to the + FunctionMock constructor.

    Parameters
    opt_functionName: string=
    The optional name of the function to mock + set to '[anonymous mocked function]' if not passed in.
    opt_strictness: number=
    One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
    Returns
    The mocked function.
    code »createGlobalFunctionMock ( functionName, opt_strictness )goog.testing.MockInterface

    Creates a controlled GlobalFunctionMock. Passes its arguments through to the + GlobalFunctionMock constructor.

    Parameters
    functionName: string
    The name of the function we're going to mock.
    opt_strictness: number=
    One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
    Returns
    The mocked function.
    code »createLooseMock ( objectToMock, opt_ignoreUnexpectedCalls, opt_mockStaticMethods, opt_createProxy )!goog.testing.LooseMock

    Creates a controlled LooseMock. Passes its arguments through to the + LooseMock constructor.

    Parameters
    objectToMock: (Object|Function)
    The object that should be mocked, or + the constructor of an object to mock.
    opt_ignoreUnexpectedCalls: boolean=
    Whether to ignore unexpected + calls.
    opt_mockStaticMethods: boolean=
    An optional argument denoting that + a mock should be constructed from the static functions of a class.
    opt_createProxy: boolean=
    An optional argument denoting that + a proxy for the target mock should be created.
    Returns
    The mock object.
    code »createMethodMock ( scope, functionName, opt_strictness )!goog.testing.MockInterface

    Creates a controlled MethodMock. Passes its arguments through to the + MethodMock constructor.

    Parameters
    scope: Object
    The scope of the method to be mocked out.
    functionName: string
    The name of the function we're going to mock.
    opt_strictness: number=
    One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
    Returns
    The mocked method.
    code »createStrictMock ( objectToMock, opt_mockStaticMethods, opt_createProxy )!goog.testing.StrictMock

    Creates a controlled StrictMock. Passes its arguments through to the + StrictMock constructor.

    Parameters
    objectToMock: (Object|Function)
    The object that should be mocked, or + the constructor of an object to mock.
    opt_mockStaticMethods: boolean=
    An optional argument denoting that + a mock should be constructed from the static functions of a class.
    opt_createProxy: boolean=
    An optional argument denoting that + a proxy for the target mock should be created.
    Returns
    The mock object.

    Instance Properties

    The list of mocks being controlled.

    \ No newline at end of file diff --git a/docs/class_goog_testing_MockExpectation.html b/docs/class_goog_testing_MockExpectation.html new file mode 100644 index 0000000..2619180 --- /dev/null +++ b/docs/class_goog_testing_MockExpectation.html @@ -0,0 +1,3 @@ +goog.testing.MockExpectation

    Class goog.testing.MockExpectation

    code »

    This is a class that represents an expectation.

    Constructor

    goog.testing.MockExpectation ( name )
    Parameters
    name: string
    The name of the method for this expectation.
    Show:

    Instance Methods

    Allow expectation failures to include messages.

    Parameters
    message: string
    The failure message.

    Get the error messages seen so far.

    Returns
    Error messages separated by \n.

    Get how many error messages have been seen so far.

    Returns
    Count of error messages.

    Instance Properties

    The number of times this method is called by real code.

    The arguments that are expected to be passed to this function

    An array of error messages for expectations not met.

    The value that will be thrown when the method is called

    The maximum number of times this method should be called.

    The minimum number of times this method should be called.

    The name of the method that is expected to be called.

    The value that this method should return.

    The function which will be executed when this method is called. + Method arguments will be passed to this function, and return value + of this function will be returned by the method.

    The number of times this method is called during the verification phase.

    \ No newline at end of file diff --git a/docs/class_goog_testing_ObjectPropertyString.html b/docs/class_goog_testing_ObjectPropertyString.html new file mode 100644 index 0000000..4195cc7 --- /dev/null +++ b/docs/class_goog_testing_ObjectPropertyString.html @@ -0,0 +1,3 @@ +goog.testing.ObjectPropertyString

    Class goog.testing.ObjectPropertyString

    code »

    Object to pass a property name as a string literal and its containing object + when the JSCompiler is rewriting these names. This should only be used in + test code.

    Constructor

    goog.testing.ObjectPropertyString ( object, propertyString )
    Parameters
    object: Object
    The containing object.
    propertyString: (Object|string)
    Property name as a string literal.
    Show:

    Instance Methods

    Returns
    The object.
    Returns
    The property string.

    Instance Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_PropertyReplacer.html b/docs/class_goog_testing_PropertyReplacer.html new file mode 100644 index 0000000..14dd913 --- /dev/null +++ b/docs/class_goog_testing_PropertyReplacer.html @@ -0,0 +1,46 @@ +goog.testing.PropertyReplacer

    Class goog.testing.PropertyReplacer

    code »

    Helper class for stubbing out variables and object properties for unit tests. + This class can change the value of some variables before running the test + cases, and to reset them in the tearDown phase. + See googletest.StubOutForTesting as an analogy in Python: + http://protobuf.googlecode.com/svn/trunk/python/stubout.py + + Example usage: +

    var stubs = new goog.testing.PropertyReplacer();
    +
    + function setUp() {
    +   // Mock functions used in all test cases.
    +   stubs.set(Math, 'random', function() {
    +     return 4;  // Chosen by fair dice roll. Guaranteed to be random.
    +   });
    + }
    +
    + function tearDown() {
    +   stubs.reset();
    + }
    +
    + function testThreeDice() {
    +   // Mock a constant used only in this test case.
    +   stubs.set(goog.global, 'DICE_COUNT', 3);
    +   assertEquals(12, rollAllDice());
    + }
    + + Constraints on altered objects: +
      +
    • DOM subclasses aren't supported. +
    • The value of the objects' constructor property must either be equal to + the real constructor or kept untouched. +

    Constructor

    goog.testing.PropertyReplacer ( )
    Show:

    Instance Methods

    code »remove ( obj, key )

    Deletes the key from the object while saving its original value.

    Parameters
    obj: (Object|Function)
    The JavaScript or native object or function to + alter. See the constraints in the class description.
    key: string
    The key to delete.
    code »replace ( obj, key, value )

    Changes an existing value in an object to another one of the same type while + saving its original state. The advantage of replace over #set + is that replace protects against typos and erroneously passing tests + after some members have been renamed during a refactoring.

    Parameters
    obj: (Object|Function)
    The JavaScript or native object or function to + alter. See the constraints in the class description.
    key: string
    The key to change the value for. It has to be present + either in obj or in its prototype chain.
    value: *
    The new value to set. It has to have the same type as the + original value. The types are compared with goog.typeOf.
    Throws
    Error
    In case of missing key or type mismatch.

    Resets all changes made by goog.testing.PropertyReplacer.prototype.set.

    code »set ( obj, key, value )

    Adds or changes a value in an object while saving its original state.

    Parameters
    obj: (Object|Function)
    The JavaScript or native object or function to + alter. See the constraints in the class description.
    key: string
    The key to change the value for.
    value: *
    The new value to set.
    code »setPath ( path, value )

    Builds an object structure for the provided namespace path. Doesn't + overwrite those prefixes of the path that are already objects or functions.

    Parameters
    path: string
    The path to create or alter, e.g. 'goog.ui.Menu'.
    value: *
    The value to set.

    Instance Properties

    Stores the values changed by the set() method in chronological order. + Its items are objects with 3 fields: 'object', 'key', 'value'. The + original value for the given key in the given object is stored under the + 'value' key.

    Static Functions

    Deletes a key from an object. Sets it to undefined or empty string if the + delete failed.

    Parameters
    obj: (Object|Function)
    The object or function to delete a key from.
    key: string
    The key to delete.

    Tells if the given key exists in the object. Ignores inherited fields.

    Parameters
    obj: (Object|Function)
    The JavaScript or native object or function + whose key is to be checked.
    key: string
    The key to check.
    Returns
    Whether the object has the key as own key.

    Static Properties

    Indicates that a key didn't exist before having been set by the set() method.

    \ No newline at end of file diff --git a/docs/class_goog_testing_StrictMock.html b/docs/class_goog_testing_StrictMock.html new file mode 100644 index 0000000..aae2814 --- /dev/null +++ b/docs/class_goog_testing_StrictMock.html @@ -0,0 +1,23 @@ +goog.testing.StrictMock

    Class goog.testing.StrictMock

    code »
    goog.testing.Mock
    +  └ goog.testing.StrictMock
    All implemented interfaces:
    goog.testing.MockInterface

    This is a mock that verifies that methods are called in the order that they + are specified during the recording phase. Since it verifies order, it + follows 'fail fast' semantics. If it detects a deviation from the + expectations, it will throw an exception and not wait for verify to be + called.

    Constructor

    goog.testing.StrictMock ( objectToMock, opt_mockStaticMethods, opt_createProxy )
    Parameters
    objectToMock: (Object|Function)
    The object that should be mocked, or + the constructor of an object to mock.
    opt_mockStaticMethods: boolean=
    An optional argument denoting that + a mock should be constructed from the static functions of a class.
    opt_createProxy: boolean=
    An optional argument denoting that + a proxy for the target mock should be created.
    Show:

    Instance Methods

    Defined in goog.testing.StrictMock

    code »$recordCall ( name, args )*
    Parameters
    name
    args

    Defined in goog.testing.Mock

    Allows the expectation to be called any number of times.

    Returns
    This mock object.

    Render the provided argument array to a string to help + clients with debugging tests.

    Parameters
    args: ?Array
    The arguments passed to the mock.
    Returns
    Human-readable string.

    Allows the expectation to be called any number of times, as long as it's + called once.

    Returns
    This mock object.

    Allows the expectation to be called 0 or 1 times.

    Returns
    This mock object.
    code »$do ( expectation, args )*

    If this expectation defines a function to be called, + it will be called and its result will be returned. + Otherwise, if the expectation expects to throw, it will throw. + Otherwise, this method will return defined value.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    args: Array
    The arguments to the method.
    Returns
    The return value expected by the mock.

    Specifies a function to call for currently pending expectation. + Note, that using this method overrides declarations made + using $returns() and $throws() methods.

    Parameters
    func: Function
    The function to call.
    Returns
    This mock object.

    Initializes the functions on the mock object.

    Parameters
    objectToMock: Object
    The object being mocked.
    code »$maybeThrow ( expectation )

    If the expectation expects to throw, this method will throw.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation.
    code »$mockMethod ( name )*

    The function that replaces all methods on the mock object.

    Parameters
    name: string
    The name of the method being mocked.
    Returns
    In record mode, returns the mock object. In replay mode, returns + whatever the creator of the mock set as the return value.

    Disallows the expectation from being called.

    Returns
    This mock object.

    Allows the expectation to be called exactly once.

    Returns
    This mock object.

    Throws an exception and records that an exception was thrown.

    Parameters
    ex: Object
    Exception.
    Throws
    Object
    #ex.

    Registers a verfifier function to use when verifying method argument lists.

    Parameters
    methodName: string
    The name of the method for which the verifierFn + should be used.
    fn: Function
    Argument list verifier function. Should take 2 argument + arrays as arguments, and return true if they are considered equivalent.
    Returns
    This mock object.

    Switches from recording to replay mode.

    Specifies a return value for the currently pending expectation.

    Parameters
    val: *
    The return value.
    Returns
    This mock object.
    code »$throwCallException ( name, args, opt_expectation )

    Throw an exception based on an incorrect method call.

    Parameters
    name: string
    Name of method called.
    args: ?Array
    Arguments passed to the mock.
    opt_expectation: goog.testing.MockExpectation=
    Expected next call, + if any.
    code »$throwException ( comment, opt_message )

    Throws an exception and records that an exception was thrown.

    Parameters
    comment: string
    A short comment about the exception.
    opt_message: ?string=
    A longer message about the exception.
    Throws
    Object
    JsUnitException object.

    Specifies a value for the currently pending expectation to throw.

    Parameters
    val: *
    The value to throw.
    Returns
    This mock object.

    Specifies the number of times the expectation should be called.

    Parameters
    times: number
    The number of times this method will be called.
    Returns
    This mock object.
    code »$verifyCall ( expectation, name, args )boolean

    Verifies that a method call matches an expectation.

    Parameters
    expectation: goog.testing.MockExpectation
    The expectation to check.
    name: string
    The name of the called method.
    args: ?Array
    The arguments passed to the mock.
    Returns
    Whether the call matches the expectation.

    Instance Properties

    Defined in goog.testing.StrictMock

    Defined in goog.testing.Mock

    Map of argument name to optional argument list verifier function.

    The expectation currently being created. All methods that modify the + current expectation return the Mock object for easy chaining, so this is + where we keep track of the expectation that's currently being modified.

    A proxy for the mock. This can be used for dependency injection in lieu of + the mock if the test requires a strict instanceof check.

    Whether or not we are in recording mode.

    First exception thrown by this mock; used in $verify.

    Static Properties

    \ No newline at end of file diff --git a/docs/class_goog_testing_TestCase.html b/docs/class_goog_testing_TestCase.html new file mode 100644 index 0000000..cd35ab8 --- /dev/null +++ b/docs/class_goog_testing_TestCase.html @@ -0,0 +1,85 @@ +goog.testing.TestCase

    Class goog.testing.TestCase

    code »

    A class representing a JsUnit test case. A TestCase is made up of a number + of test functions which can be run. Individual test cases can override the + following functions to set up their test environment: + - runTests - completely override the test's runner + - setUpPage - called before any of the test functions are run + - tearDownPage - called after all tests are finished + - setUp - called before each of the test functions + - tearDown - called after each of the test functions + - shouldRunTests - called before a test run, all tests are skipped if it + returns false. Can be used to disable tests on browsers + where they aren't expected to pass. + + Use #autoDiscoverLifecycle and #autoDiscoverTests

    Constructor

    goog.testing.TestCase ( opt_name )
    Parameters
    opt_name: string=
    The name of the test case, defaults to + 'Untitled Test Case'.

    Classes

    goog.testing.TestCase.Error
    A class representing an error thrown by the test
    goog.testing.TestCase.Result
    A class for representing test results.
    goog.testing.TestCase.Test
    A class representing a single test function.
    goog.testing.TestCase.protectedDate_
    No Description.

    Enumerations

    goog.testing.TestCase.Order
    The order to run the auto-discovered tests.
    Show:

    Instance Methods

    code »add ( test )

    Adds a new test to the test case.

    Parameters
    test: goog.testing.TestCase.Test
    The test to add.
    code »addNewTest ( name, ref, opt_scope )

    Creates and adds a new test. + + Convenience function to make syntax less awkward when not using automatic + test discovery.

    Parameters
    name: string
    The test name.
    ref: !Function
    Reference to the test function.
    opt_scope: !Object=
    Optional scope that the test function should be + called in.

    Adds any functions defined in the global scope that correspond to + lifecycle events for the test case. Overrides setUp, tearDown, setUpPage, + tearDownPage and runTests if they are defined.

    Adds any functions defined in the global scope that are prefixed with "test" + to the test case.

    Clears a timeout created by this.timeout().

    Parameters
    id: number
    A timeout id.

    Counts the number of files that were loaded for dependencies that are + required to run the test.

    Returns
    The number of files loaded.

    Creates a goog.testing.TestCase.Test from an auto-discovered + function.

    Parameters
    name: string
    The name of the function.
    ref: function(): void
    The auto-discovered function.
    Returns
    The newly created test.

    Cycles through the tests, breaking out using a setTimeout if the execution + time has execeeded #maxRunTime.

    code »doError ( test, opt_e )

    Handles a test that failed.

    Parameters
    test: goog.testing.TestCase.Test
    The test that failed.
    opt_e: *=
    The exception object associated with the + failure or a string.

    Handles a test that passed.

    Parameters
    test: goog.testing.TestCase.Test
    The test that passed.

    Executes each of the tests.

    Finalizes the test case, called when the tests have finished executing.

    Returns the number of tests actually run in the test case, i.e. subtracting + any which are skipped.

    Returns
    The number of un-ignored tests.
    Returns
    The function name prefix used to auto-discover tests.
    Returns
    Time since the last batch of tests was started.

    Returns the number of tests contained in the test case.

    Returns
    The number of tests.
    code »getGlobals ( opt_prefix )!Array

    Gets list of objects that potentially contain test cases. For IE 8 and below, + this is the global "this" (for properties set directly on the global this or + window) and the RuntimeObject (for global variables and functions). For all + other browsers, the array simply contains the global this.

    Parameters
    opt_prefix: string=
    An optional prefix. If specified, only get things + under this prefix. Note that the prefix is only honored in IE, since it + supports the RuntimeObject: + http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx + TODO: Remove this option.
    Returns
    A list of objects that should be inspected.
    Returns
    The name of the test.

    Returns the number of script files that were loaded in order to run the test.

    Returns
    The number of script files.
    code »getReport ( opt_verbose )string

    Returns a string detailing the results from the test.

    Parameters
    opt_verbose: boolean=
    If true results will include data about all + tests, not just what failed.
    Returns
    The results from the test.

    Returns the amount of time it took for the test to run.

    Returns
    The run time, in milliseconds.

    Returns the test results object: a map from test names to a list of test + failures (if any exist).

    Returns
    Tests results object.

    Gets the tests.

    Returns
    The test array.

    Returns the current time.

    Returns
    HH:MM:SS.
    Returns
    Whether the test case is running inside the multi test + runner.
    Returns
    Whether the test was a success.
    code »log ( val )

    Logs an object to the console, if available.

    Parameters
    val: *
    The value to log. Will be ToString'd.
    Parameters
    name: string
    Failed test name.
    opt_e: *=
    The exception object associated with the + failure or a string.
    Returns
    Error object.

    Checks to see if the test should be marked as failed before it is run. + + If there was an error in setUpPage, we treat that as a failure for all tests + and mark them all as having failed.

    Parameters
    testCase: goog.testing.TestCase.Test
    The current test case.
    Returns
    Whether the test was marked as failed.

    Returns the current test and increments the pointer.

    Returns
    The current test case.
    Returns
    The current time in milliseconds, don't use goog.now as some + tests override it.

    Reorders the tests depending on the order field.

    Parameters
    tests: Array.<goog.testing.TestCase.Test>
    An array of tests to + reorder.
    code »pad_ ( number )string

    Pads a number to make it have a leading zero if it's less than 10.

    Parameters
    number: number
    The number to pad.
    Returns
    The resulting string.

    Resets the test case pointer, so that next returns the first test.

    Executes each of the tests. + Overridable by the individual test case. This allows test cases to defer + when the test is actually started. If overridden, finalize must be called + by the test to indicate it has finished.

    code »saveMessage ( message )

    Saves a message to the result set.

    Parameters
    message: string
    The message to save.
    code »setBatchTime ( batchTime )
    Parameters
    batchTime: number
    Time since the last batch of tests was started.

    Sets the callback function that should be executed when the tests have + completed.

    Parameters
    fn: Function
    The callback function.
    code »setTests ( tests )

    Sets the tests.

    Parameters
    tests: !Array.<goog.testing.TestCase.Test>
    A new test array.

    Gets called before every goog.testing.TestCase.Test is been executed. Can be + overridden to add set up functionality to each test.

    Gets called before any tests are executed. Can be overridden to set up the + environment for the whole test case.

    Can be overridden in test classes to indicate whether the tests in a case + should be run in that particular situation. For example, this could be used + to stop tests running in a particular browser, where browser support for + the class under test was absent.

    Returns
    Whether any of the tests in the case should be run.

    Gets called after every goog.testing.TestCase.Test has been executed. Can be + overriden to add tear down functionality to each test.

    Gets called after all tests have been executed. Can be overridden to tear + down the entire test case.

    code »timeout ( fn, time )number

    Calls a function after a delay, using the protected timeout.

    Parameters
    fn: Function
    The function to call.
    time: number
    Delay in milliseconds.
    Returns
    The timeout id.

    Trims a path to be only that after google3.

    Parameters
    path: string
    The path to trim.
    Returns
    The resulting string.

    Instance Properties

    Time since the last batch of tests was started, if batchTime exceeds + #maxRunTime a timeout will be used to stop the tests blocking the + browser and a new batch will be started.

    Pointer to the current test.

    Exception object that was detected before a test runs.

    A name for the test case.

    Optional callback that will be executed when the test has finalized.

    The order to run the auto-discovered tests in.

    Object used to encapsulate the test results.

    Whether the test case is running.

    Timestamp for when the test was started.

    Whether the test case has ever tried to execute.

    Set of test names and/or indices to execute, or null if all tests should + be executed. + + Indices are included to allow automation tools to run a subset of the + tests without knowing the exact contents of the test file. + + Indices should only be used with SORTED ordering. + + Example valid values: +

      +
    • [testName] +
    • [testName1, testName2] +
    • [2] - will run the 3rd test in the order specified +
    • [1,3,5] +
    • [testName1, testName2, 3, 5] - will work +

      Array of test functions that can be executed.

      Static Functions

      Gets list of objects that potentially contain test cases. For IE 8 and below, + this is the global "this" (for properties set directly on the global this or + window) and the RuntimeObject (for global variables and functions). For all + other browsers, the array simply contains the global this.

      Parameters
      opt_prefix: string=
      An optional prefix. If specified, only get things + under this prefix. Note that the prefix is only honored in IE, since it + supports the RuntimeObject: + http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx + TODO: Remove this option.
      Returns
      A list of objects that should be inspected.

      Initializes the given test case with the global test runner 'G_testRunner'.

      Parameters
      testCase: goog.testing.TestCase
      The test case to install.

      Save a reference to window.clearTimeout, so any code that overrides + the default behavior (e.g. MockClock) doesn't affect our runner.

      Save a reference to window.setTimeout, so any code that overrides the + default behavior (the MockClock, for example) doesn't affect our runner.

      Static Properties

      Avoid a dependency on goog.userAgent and keep our own reference of whether + the browser is IE.

      TODO(user) replace this with prototype.currentTest. + Name of the current test that is running, or null if none is running.

      The maximum amount of time that the test can run before we force it to be + async. This prevents the test runner from blocking the browser and + potentially hurting the Selenium test harness.

      Saved string referencing goog.global.setTimeout's string serialization. IE + sometimes fails to uphold equality for setTimeout, but the string version + stays the same.

      \ No newline at end of file diff --git a/docs/class_goog_testing_TestCase_Error.html b/docs/class_goog_testing_TestCase_Error.html new file mode 100644 index 0000000..dbe77b2 --- /dev/null +++ b/docs/class_goog_testing_TestCase_Error.html @@ -0,0 +1 @@ +goog.testing.TestCase.Error

      Class goog.testing.TestCase.Error

      code »

      A class representing an error thrown by the test

      Constructor

      goog.testing.TestCase.Error ( source, message, opt_stack )
      Parameters
      source: string
      The name of the test which threw the error.
      message: string
      The error message.
      opt_stack: string=
      A string showing the execution stack.
      Show:

      Instance Methods

      Returns a string representing the error object.

      Returns
      A string representation of the error.

      Instance Properties

      Reference to the test function.

      The name of the test which threw the error.

      Scope that the test function should be called in.

      \ No newline at end of file diff --git a/docs/class_goog_testing_TestCase_Result.html b/docs/class_goog_testing_TestCase_Result.html new file mode 100644 index 0000000..094cf67 --- /dev/null +++ b/docs/class_goog_testing_TestCase_Result.html @@ -0,0 +1,5 @@ +goog.testing.TestCase.Result

      Class goog.testing.TestCase.Result

      code »

      A class for representing test results. A bag of public properties.

      Constructor

      goog.testing.TestCase.Result ( testCase )
      Parameters
      testCase: goog.testing.TestCase
      The test case that owns this result.
      Show:

      Instance Methods

      Returns
      A summary of the tests, including total number of tests that + passed, failed, and the time taken.
      Returns
      Whether the test was successful.

      Instance Properties

      Whether the tests have completed.

      Errors encountered while running the test.

      Messages to show the user after running the test.

      The number of files loaded to run this test.

      Test results for each test that was run. The test name is always added + as the key in the map, and the array of strings is an optional list + of failure messages. If the array is empty, the test passed. Otherwise, + the test failed.

      Total number of tests that were actually run.

      The amount of time the tests took to run.

      Number of successful tests.

      The test case that owns this result.

      Whether this test case was suppressed by shouldRunTests() returning false.

      Total number of tests that should have been run.

      \ No newline at end of file diff --git a/docs/class_goog_testing_TestCase_Test.html b/docs/class_goog_testing_TestCase_Test.html new file mode 100644 index 0000000..16aac26 --- /dev/null +++ b/docs/class_goog_testing_TestCase_Test.html @@ -0,0 +1,2 @@ +goog.testing.TestCase.Test

      Class goog.testing.TestCase.Test

      code »

      A class representing a single test function.

      Constructor

      goog.testing.TestCase.Test ( name, ref, opt_scope )
      Parameters
      name: string
      The test name.
      ref: Function
      Reference to the test function.
      opt_scope: Object=
      Optional scope that the test function should be + called in.
      Show:

      Instance Methods

      Executes the test function.

      Instance Properties

      The name of the test.

      Reference to the test function.

      Scope that the test function should be called in.

      \ No newline at end of file diff --git a/docs/class_goog_testing_TestCase_protectedDate_.html b/docs/class_goog_testing_TestCase_protectedDate_.html new file mode 100644 index 0000000..3087ec2 --- /dev/null +++ b/docs/class_goog_testing_TestCase_protectedDate_.html @@ -0,0 +1,3 @@ +goog.testing.TestCase.protectedDate_

      Class goog.testing.TestCase.protectedDate_

      code »

      Save a reference to window.Date, so any code that overrides + the default behavior doesn't affect our runner.

      Constructor

      goog.testing.TestCase.protectedDate_ ( )
      Show:

      Instance Methods

      code »setDate ( dayValue )

      Sets the day of the month for a specified date according to local time.

      Parameters
      dayValue
      code »setFullYear ( yearValue, opt_monthValue, opt_dayValue )

      Sets the full year for a specified date according to local time.

      Parameters
      yearValue
      opt_monthValue
      opt_dayValue
      code »setHours ( hoursValue, opt_minutesValue, opt_secondsValue, opt_msValue )

      Sets the hours for a specified date according to local time.

      Parameters
      hoursValue
      opt_minutesValue
      opt_secondsValue
      opt_msValue
      code »setMilliseconds ( millisecondsValue )

      Sets the milliseconds for a specified date according to local time.

      Parameters
      millisecondsValue
      code »setMinutes ( minutesValue, opt_secondsValue, opt_msValue )

      Sets the minutes for a specified date according to local time.

      Parameters
      minutesValue
      opt_secondsValue
      opt_msValue
      code »setMonth ( monthValue, opt_dayValue )

      Set the month for a specified date according to local time.

      Parameters
      monthValue
      opt_dayValue
      code »setSeconds ( secondsValue, opt_msValue )

      Sets the seconds for a specified date according to local time.

      Parameters
      secondsValue
      opt_msValue
      code »setTime ( timeValue )

      Sets the Date object to the time represented by a number of milliseconds + since January 1, 1970, 00:00:00 UTC.

      Parameters
      timeValue
      code »setUTCDate ( dayValue )

      Sets the day of the month for a specified date according to universal time.

      Parameters
      dayValue
      code »setUTCFullYear ( yearValue, opt_monthValue, opt_dayValue )

      Sets the full year for a specified date according to universal time.

      Parameters
      yearValue
      opt_monthValue
      opt_dayValue
      code »setUTCHours ( hoursValue, opt_minutesValue, opt_secondsValue, opt_msValue )

      Sets the hour for a specified date according to universal time.

      Parameters
      hoursValue
      opt_minutesValue
      opt_secondsValue
      opt_msValue
      code »setUTCMilliseconds ( millisecondsValue )

      Sets the milliseconds for a specified date according to universal time.

      Parameters
      millisecondsValue
      code »setUTCMinutes ( minutesValue, opt_secondsValue, opt_msValue )

      Sets the minutes for a specified date according to universal time.

      Parameters
      minutesValue
      opt_secondsValue
      opt_msValue
      code »setUTCMonth ( monthValue, opt_dayValue )

      Sets the month for a specified date according to universal time.

      Parameters
      monthValue
      opt_dayValue
      code »setUTCSeconds ( secondsValue, opt_msValue )

      Sets the seconds for a specified date according to universal time.

      Parameters
      secondsValue
      opt_msValue
      code »setYear ( yearValue )

      Sets the year for a specified date according to local time.

      Parameters
      yearValue
      code »toJSON ( opt_ignoredKey )string
      Parameters
      opt_ignoredKey
      code »toLocaleDateString ( opt_locales, opt_options )string
      Parameters
      opt_locales
      opt_options
      code »toLocaleFormat ( formatString )string
      Parameters
      formatString
      code »toLocaleTimeString ( opt_locales, opt_options )string
      Parameters
      opt_locales
      opt_options
      \ No newline at end of file diff --git a/docs/class_goog_testing_TestRunner.html b/docs/class_goog_testing_TestRunner.html new file mode 100644 index 0000000..c3cde33 --- /dev/null +++ b/docs/class_goog_testing_TestRunner.html @@ -0,0 +1,17 @@ +goog.testing.TestRunner

      Class goog.testing.TestRunner

      code »

      Construct a test runner. + + NOTE(user): This is currently pretty weird, I'm essentially trying to + create a wrapper that the Selenium test can hook into to query the state of + the running test case, while making goog.testing.TestCase general.

      Constructor

      goog.testing.TestRunner ( )
      Show:

      Instance Methods

      Executes a test case and prints the results to the window.

      Returns the number of script files that were loaded in order to run the test.

      Returns
      The number of script files.
      code »getReport ( opt_verbose )string

      Returns a report of the test case that ran. + Used by Selenium Hooks.

      Parameters
      opt_verbose: boolean=
      If true results will include data about all + tests, not just what failed.
      Returns
      A report summary of the test.

      Returns the amount of time it took for the test to run. + Used by Selenium Hooks.

      Returns
      The run time, in milliseconds.
      Returns
      A map of test names to a list of + test failures (if any) to provide formatted data for the test runner.

      Returns true if the test case runner has errors that were caught outside of + the test case.

      Returns
      Whether there were JS errors.
      code »initialize ( testCase )

      Initializes the test runner.

      Parameters
      testCase: goog.testing.TestCase
      The test case to initialize with.

      Returns true if the test runner is finished. + Used by Selenium Hooks.

      Returns
      Whether the test runner is active.

      Returns true if the test runner is initialized. + Used by Selenium Hooks.

      Returns
      Whether the test runner is active.
      Returns
      Whether the test runner should fail on an empty + test case.

      Returns true if the test case didn't fail. + Used by Selenium Hooks.

      Returns
      Whether the current test returned successfully.

      Logs a message to the current test case.

      Parameters
      s: string
      The text to output to the log.

      Logs an error that occurred. Used in the case of environment setting up + an onerror handler.

      Parameters
      msg: string
      Error message.

      Log failure in current running test.

      Parameters
      ex: Error
      Exception.

      Writes the results to the document when the test case completes.

      Sets a function to use as a filter for errors.

      Parameters
      fn: function(string)
      Filter function.
      code »setStrict ( strict )

      By default, the test runner is strict, and fails if it runs an empty + test case.

      Parameters
      strict: boolean
      Whether the test runner should fail on an empty + test case.

      Writes a nicely formatted log out to the document.

      Parameters
      log: string
      The string to write.

      Instance Properties

      Function to use when filtering errors.

      Errors that occurred in the window.

      Whether the test runner has been initialized yet.

      Element created in the document to add test results to.

      Whether an empty test case counts as an error.

      Reference to the active test case.

      \ No newline at end of file diff --git a/docs/class_goog_testing_events_Event.html b/docs/class_goog_testing_events_Event.html new file mode 100644 index 0000000..4c0df8e --- /dev/null +++ b/docs/class_goog_testing_events_Event.html @@ -0,0 +1,6 @@ +goog.testing.events.Event

      Class goog.testing.events.Event

      code »
      Event
      +  └ goog.testing.events.Event

      goog.events.BrowserEvent expects an Event so we provide one for JSCompiler. + + This clones a lot of the functionality of goog.events.Event. This used to + use a mixin, but the mixin results in confusing the two types when compiled.

      Constructor

      goog.testing.events.Event ( type, opt_target )
      Parameters
      type: string
      Event Type.
      opt_target: Object=
      Reference to the object that is the target of + this event.
      Show:

      Instance Methods

      Defined in goog.testing.events.Event

      Defined in Event

      code »initEvent ( eventTypeArg, canBubbleArg, cancelableArg )undefined
      Parameters
      eventTypeArg
      canBubbleArg
      cancelableArg

      Instance Properties

      Defined in goog.testing.events.Event

      Whether to cancel the event in internal capture/bubble processing for IE.

      Return value for in internal capture/bubble processing for IE.

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_ArgumentMatcher.html b/docs/class_goog_testing_mockmatchers_ArgumentMatcher.html new file mode 100644 index 0000000..ffc68fd --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_ArgumentMatcher.html @@ -0,0 +1,9 @@ +goog.testing.mockmatchers.ArgumentMatcher

      Class goog.testing.mockmatchers.ArgumentMatcher

      code »

      A simple interface for executing argument matching. A match in this case is + testing to see if a supplied object fits a given criteria. True is returned + if the given criteria is met.

      Constructor

      goog.testing.mockmatchers.ArgumentMatcher ( opt_matchFn, opt_matchName )
      Parameters
      opt_matchFn: Function=
      A function that evaluates a given argument + and returns true if it meets a given criteria.
      opt_matchName: ?string=
      The name expressing intent as part of + an error message for when a match fails.
      Show:

      Instance Methods

      code »matches ( toVerify, opt_expectation )boolean

      A function that takes a match argument and an optional MockExpectation + which (if provided) will get error information and returns whether or + not it matches.

      Parameters
      toVerify: *
      The argument that should be verified.
      opt_expectation: ?goog.testing.MockExpectation=
      The expectation + for this match.
      Returns
      Whether or not a given argument passes verification.

      Instance Properties

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_IgnoreArgument.html b/docs/class_goog_testing_mockmatchers_IgnoreArgument.html new file mode 100644 index 0000000..4d325c4 --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_IgnoreArgument.html @@ -0,0 +1,8 @@ +goog.testing.mockmatchers.IgnoreArgument

      Class goog.testing.mockmatchers.IgnoreArgument

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.IgnoreArgument

      A matcher that always returns true. It is useful when the user does not care + for some arguments. + For example: mockFunction('username', 'password', IgnoreArgument);

      Constructor

      goog.testing.mockmatchers.IgnoreArgument ( )
      Show:

      Instance Methods

      code »matches ( toVerify, opt_expectation )boolean

      A function that takes a match argument and an optional MockExpectation + which (if provided) will get error information and returns whether or + not it matches.

      Parameters
      toVerify: *
      The argument that should be verified.
      opt_expectation: ?goog.testing.MockExpectation=
      The expectation + for this match.
      Returns
      Whether or not a given argument passes verification.

      Instance Properties

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_InstanceOf.html b/docs/class_goog_testing_mockmatchers_InstanceOf.html new file mode 100644 index 0000000..a5f073b --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_InstanceOf.html @@ -0,0 +1,6 @@ +goog.testing.mockmatchers.InstanceOf

      Class goog.testing.mockmatchers.InstanceOf

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.InstanceOf

      A matcher that verifies that an argument is an instance of a given class.

      Constructor

      goog.testing.mockmatchers.InstanceOf ( ctor )
      Parameters
      ctor: Function
      The class that will be used for verification.
      Show:

      Instance Methods

      code »matches ( toVerify, opt_expectation )boolean

      A function that takes a match argument and an optional MockExpectation + which (if provided) will get error information and returns whether or + not it matches.

      Parameters
      toVerify: *
      The argument that should be verified.
      opt_expectation: ?goog.testing.MockExpectation=
      The expectation + for this match.
      Returns
      Whether or not a given argument passes verification.

      Instance Properties

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_ObjectEquals.html b/docs/class_goog_testing_mockmatchers_ObjectEquals.html new file mode 100644 index 0000000..967af04 --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_ObjectEquals.html @@ -0,0 +1,5 @@ +goog.testing.mockmatchers.ObjectEquals

      Class goog.testing.mockmatchers.ObjectEquals

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.ObjectEquals

      A matcher that verifies that the argument is an object that equals the given + expected object, using a deep comparison.

      Constructor

      goog.testing.mockmatchers.ObjectEquals ( expectedObject )
      Parameters
      expectedObject: Object
      An object to match against when + verifying the argument.
      Show:

      Instance Methods

      Defined in goog.testing.mockmatchers.ObjectEquals

      code »matches ( toVerify, opt_expectation )boolean
      Parameters
      toVerify
      opt_expectation

      Instance Properties

      Defined in goog.testing.mockmatchers.ArgumentMatcher

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_RegexpMatch.html b/docs/class_goog_testing_mockmatchers_RegexpMatch.html new file mode 100644 index 0000000..641c947 --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_RegexpMatch.html @@ -0,0 +1,6 @@ +goog.testing.mockmatchers.RegexpMatch

      Class goog.testing.mockmatchers.RegexpMatch

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.RegexpMatch

      A matcher that verifies that an argument matches a given RegExp.

      Constructor

      goog.testing.mockmatchers.RegexpMatch ( regexp )
      Parameters
      regexp: RegExp
      The regular expression that the argument must match.
      Show:

      Instance Methods

      code »matches ( toVerify, opt_expectation )boolean

      A function that takes a match argument and an optional MockExpectation + which (if provided) will get error information and returns whether or + not it matches.

      Parameters
      toVerify: *
      The argument that should be verified.
      opt_expectation: ?goog.testing.MockExpectation=
      The expectation + for this match.
      Returns
      Whether or not a given argument passes verification.

      Instance Properties

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_SaveArgument.html b/docs/class_goog_testing_mockmatchers_SaveArgument.html new file mode 100644 index 0000000..54da271 --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_SaveArgument.html @@ -0,0 +1,8 @@ +goog.testing.mockmatchers.SaveArgument

      Class goog.testing.mockmatchers.SaveArgument

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.SaveArgument

      A matcher that saves the argument that it is verifying so that your unit test + can perform extra tests with this argument later. For example, if the + argument is a callback method, the unit test can then later call this + callback to test the asynchronous portion of the call.

      Constructor

      goog.testing.mockmatchers.SaveArgument ( opt_matcher, opt_matchName )
      Parameters
      opt_matcher: (goog.testing.mockmatchers.ArgumentMatcher|Function)=
      Argument matcher or matching function that will be used to validate the + argument. By default, argument will always be valid.
      opt_matchName: ?string=
      The name expressing intent as part of + an error message for when a match fails.
      Show:

      Instance Methods

      Defined in goog.testing.mockmatchers.SaveArgument

      code »matches ( toVerify, opt_expectation )boolean
      Parameters
      toVerify
      opt_expectation

      Instance Properties

      Defined in goog.testing.mockmatchers.SaveArgument

      Saved argument that was verified.

      Delegate match requests to this matcher.

      Defined in goog.testing.mockmatchers.ArgumentMatcher

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_mockmatchers_TypeOf.html b/docs/class_goog_testing_mockmatchers_TypeOf.html new file mode 100644 index 0000000..8835d33 --- /dev/null +++ b/docs/class_goog_testing_mockmatchers_TypeOf.html @@ -0,0 +1,6 @@ +goog.testing.mockmatchers.TypeOf

      Class goog.testing.mockmatchers.TypeOf

      code »
      goog.testing.mockmatchers.ArgumentMatcher
      +  └ goog.testing.mockmatchers.TypeOf

      A matcher that verifies that an argument is of a given type (e.g. "object").

      Constructor

      goog.testing.mockmatchers.TypeOf ( type )
      Parameters
      type: string
      The type that a given argument must have.
      Show:

      Instance Methods

      code »matches ( toVerify, opt_expectation )boolean

      A function that takes a match argument and an optional MockExpectation + which (if provided) will get error information and returns whether or + not it matches.

      Parameters
      toVerify: *
      The argument that should be verified.
      opt_expectation: ?goog.testing.MockExpectation=
      The expectation + for this match.
      Returns
      Whether or not a given argument passes verification.

      Instance Properties

      A function that evaluates a given argument and returns true if it meets a + given criteria.

      A string indicating the match intent (e.g. isBoolean or isString).

      Static Properties

      \ No newline at end of file diff --git a/docs/class_goog_testing_stacktrace_Frame.html b/docs/class_goog_testing_stacktrace_Frame.html new file mode 100644 index 0000000..460cb26 --- /dev/null +++ b/docs/class_goog_testing_stacktrace_Frame.html @@ -0,0 +1,6 @@ +goog.testing.stacktrace.Frame

      Class goog.testing.stacktrace.Frame

      code »

      Class representing one stack frame.

      Constructor

      goog.testing.stacktrace.Frame ( context, name, alias, args, path )
      Parameters
      context: string
      Context object, empty in case of global functions or + if the browser doesn't provide this information.
      name: string
      Function name, empty in case of anonymous functions.
      alias: string
      Alias of the function if available. For example the + function name will be 'c' and the alias will be 'b' if the function is + defined as a.b = function c() {};.
      args: string
      Arguments of the function in parentheses if available.
      path: string
      File path or URL including line number and optionally + column number separated by colons.
      Show:

      Instance Methods

      Returns
      The function name or empty string if the function is + anonymous and the object field which it's assigned to is unknown.
      Returns
      Whether the stack frame contains an anonymous function.

      Brings one frame of the stack trace into a common format across browsers.

      Returns
      Pretty printed stack frame.

      Instance Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_AbstractBuilder.html b/docs/class_webdriver_AbstractBuilder.html index a8dc544..1b1363e 100644 --- a/docs/class_webdriver_AbstractBuilder.html +++ b/docs/class_webdriver_AbstractBuilder.html @@ -5,10 +5,12 @@
      Defines the remote WebDriver server that should be used for command command execution; may be overridden using webdriver.AbstractBuilder.prototype.usingServer.
      -

      Constructor

      webdriver.AbstractBuilder ( )
      Show:

      Instance Methods

      Builds a new webdriver.WebDriver instance using this builder's +

      Constructor

      webdriver.AbstractBuilder ( )
      Show:

      Instance Methods

      Builds a new webdriver.WebDriver instance using this builder's current configuration.

      Returns
      A new WebDriver client.
      Returns
      The current desired capabilities for this builder.
      Returns
      The URL of the WebDriver server this instance is configured - to use.

      Configures which WebDriver server should be used for new sessions. Overrides + to use.

      Sets the logging preferences for the created session. Preferences may be + changed by repeated calls, or by calling #withCapabilities.

      Parameters
      prefs: !(webdriver.logging.Preferences|Object.<string, string>)
      The + desired logging preferences.
      Returns
      This Builder instance for chain calling.

      Configures which WebDriver server should be used for new sessions. Overrides the value loaded from the webdriver.AbstractBuilder.SERVER_URL_ENV upon creation of this instance.

      Parameters
      url: string
      URL of the server to use.
      Returns
      This Builder instance for chain calling.

      Sets the desired capabilities when requesting a new session. This will overwrite any previously set desired capabilities.

      Parameters
      capabilities: !(Object|webdriver.Capabilities)
      The desired @@ -18,4 +20,4 @@ webdriver.AbstractBuilder#usingServer.

      Static Properties

      The default URL of the WebDriver server to use if webdriver.AbstractBuilder.SERVER_URL_ENV is not set.

      Environment variable that defines the URL of the WebDriver server that should be used for all new WebDriver clients. This setting may be overridden - using #usingServer(url).

      \ No newline at end of file + using #usingServer(url). \ No newline at end of file diff --git a/docs/class_webdriver_ActionSequence.html b/docs/class_webdriver_ActionSequence.html index 86ec373..aaf62c0 100644 --- a/docs/class_webdriver_ActionSequence.html +++ b/docs/class_webdriver_ActionSequence.html @@ -1,87 +1,115 @@ -webdriver.ActionSequence

      Class webdriver.ActionSequence

      code »

      Class for defining sequences of complex user interactions. Each sequence - will not be executed until #perform is called. - -

      Example:

      
      -   new webdriver.ActionSequence(driver).
      -       keyDown(webdriver.Key.SHIFT).
      -       click(element1).
      -       click(element2).
      -       dragAndDrop(element3, element4).
      -       keyUp(webdriver.Key.SHIFT).
      -       perform();
      - 

      Constructor

      webdriver.ActionSequence ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The driver instance to use.
      Show:

      Instance Methods

      code »click ( opt_elementOrButton, opt_button )!webdriver.ActionSequence

      Clicks a mouse button. - -

      If an element is provided, the mouse will first be moved to the center - of that element. This is equivalent to: -

      sequence.mouseMove(element).click()
      Parameters
      opt_elementOrButton: (webdriver.WebElement|webdriver.Button)=
      Either - the element to interact with or the button to click with. - Defaults to webdriver.Button.LEFT if neither an element nor - button is specified.
      opt_button: webdriver.Button=
      The button to use. Defaults to - webdriver.Button.LEFT. Ignored if a button is provided as the - first argument.
      Returns
      A self reference.
      code »doubleClick ( opt_elementOrButton, opt_button )!webdriver.ActionSequence

      Double-clicks a mouse button. - -

      If an element is provided, the mouse will first be moved to the center of - that element. This is equivalent to: -

      sequence.mouseMove(element).doubleClick()
      - -

      Warning: this method currently only supports the left mouse button. See - http://code.google.com/p/selenium/issues/detail?id=4047

      Parameters
      opt_elementOrButton: (webdriver.WebElement|webdriver.Button)=
      Either - the element to interact with or the button to click with. - Defaults to webdriver.Button.LEFT if neither an element nor - button is specified.
      opt_button: webdriver.Button=
      The button to use. Defaults to - webdriver.Button.LEFT. Ignored if a button is provided as the - first argument.
      Returns
      A self reference.
      code »dragAndDrop ( element, location )!webdriver.ActionSequence

      Convenience function for performing a "drag and drop" manuever. The target - element may be moved to the location of another element, or by an offset (in - pixels).

      Parameters
      element: !webdriver.WebElement
      The element to drag.
      location: (!webdriver.WebElement|{x: number, y: number})
      The - location to drag to, either as another WebElement or an offset in pixels.
      Returns
      A self reference.

      Performs a modifier key press. The modifier key is not released - until #keyUp or #sendKeys is called. The key press will be - targetted at the currently focused element.

      Parameters
      key: !webdriver.Key
      The modifier key to push. Must be one of - {ALT, CONTROL, SHIFT, COMMAND, META}.
      Returns
      A self reference.
      Throws
      Error
      If the key is not a valid modifier key.

      Performs a modifier key release. The release is targetted at the currently - focused element.

      Parameters
      key: !webdriver.Key
      The modifier key to release. Must be one of - {ALT, CONTROL, SHIFT, COMMAND, META}.
      Returns
      A self reference.
      Throws
      Error
      If the key is not a valid modifier key.
      code »mouseDown ( opt_elementOrButton, opt_button )!webdriver.ActionSequence

      Presses a mouse button. The mouse button will not be released until - #mouseUp is called, regardless of whether that call is made in this - sequence or another. The behavior for out-of-order events (e.g. mouseDown, - click) is undefined. - -

      If an element is provided, the mouse will first be moved to the center - of that element. This is equivalent to: -

      sequence.mouseMove(element).mouseDown()
      - -

      Warning: this method currently only supports the left mouse button. See - http://code.google.com/p/selenium/issues/detail?id=4047

      Parameters
      opt_elementOrButton: (webdriver.WebElement|webdriver.Button)=
      Either - the element to interact with or the button to click with. - Defaults to webdriver.Button.LEFT if neither an element nor - button is specified.
      opt_button: webdriver.Button=
      The button to use. Defaults to - webdriver.Button.LEFT. Ignored if a button is provided as the - first argument.
      Returns
      A self reference.
      code »mouseMove ( location, opt_offset )!webdriver.ActionSequence

      Moves the mouse. The location to move to may be specified in terms of the - mouse's current location, an offset relative to the top-left corner of an - element, or an element (in which case the middle of the element is used).

      Parameters
      location: (!webdriver.WebElement|{x: number, y: number})
      The - location to drag to, as either another WebElement or an offset in pixels.
      opt_offset: {x: number, y: number}=
      If the target location - is defined as a webdriver.WebElement, this parameter defines an - offset within that element. The offset should be specified in pixels - relative to the top-left corner of the element's bounding box. If - omitted, the element's center will be used as the target offset.
      Returns
      A self reference.
      code »mouseUp ( opt_elementOrButton, opt_button )!webdriver.ActionSequence

      Releases a mouse button. Behavior is undefined for calling this function - without a previous call to #mouseDown. - -

      If an element is provided, the mouse will first be moved to the center - of that element. This is equivalent to: -

      sequence.mouseMove(element).mouseUp()
      - -

      Warning: this method currently only supports the left mouse button. See - http://code.google.com/p/selenium/issues/detail?id=4047

      Parameters
      opt_elementOrButton: (webdriver.WebElement|webdriver.Button)=
      Either - the element to interact with or the button to click with. - Defaults to webdriver.Button.LEFT if neither an element nor - button is specified.
      opt_button: webdriver.Button=
      The button to use. Defaults to - webdriver.Button.LEFT. Ignored if a button is provided as the - first argument.
      Returns
      A self reference.

      Executes this action sequence.

      Returns
      A promise that will be resolved once - this sequence has completed.

      Schedules a keyboard action.

      Parameters
      description: string
      A simple descriptive label for the scheduled - action.
      keys: !Array
      The keys to send.
      Returns
      A self reference.
      code »scheduleMouseAction_ ( description, commandName, opt_elementOrButton, opt_button )!webdriver.ActionSequence

      Schedules a mouse action.

      Parameters
      description: string
      A simple descriptive label for the scheduled - action.
      commandName: !webdriver.CommandName
      The name of the command.
      opt_elementOrButton: (webdriver.WebElement|webdriver.Button)=
      Either - the element to interact with or the button to click with. - Defaults to webdriver.Button.LEFT if neither an element nor - button is specified.
      opt_button: webdriver.Button=
      The button to use. Defaults to - webdriver.Button.LEFT. Ignored if the previous argument is - provided as a button.
      Returns
      A self reference.
      code »schedule_ ( description, command )

      Schedules an action to be executed each time #perform is called on - this instance.

      Parameters
      description: string
      A description of the command.
      command: !webdriver.Command
      The command.

      Simulates typing multiple keys. Each modifier key encountered in the - sequence will not be released until it is encountered again. All key events - will be targetted at the currently focused element.

      Parameters
      var_args: ...(string|!webdriver.Key|!Array)
      The keys to type.
      Returns
      A self reference.
      Throws
      Error
      If the key is not a valid modifier key.

      Instance Properties

      Static Functions

      Checks that a key is a modifier key.

      Parameters
      key: !webdriver.Key
      The key to check.
      Throws
      Error
      If the key is not a modifier key.
      \ No newline at end of file +ActionSequence

      class ActionSequence

      Class for defining sequences of complex user interactions. Each sequence +will not be executed until #perform is called.

      +

      Example:

      +
      new webdriver.ActionSequence(driver).
      +    keyDown(webdriver.Key.SHIFT).
      +    click(element1).
      +    click(element2).
      +    dragAndDrop(element3, element4).
      +    keyUp(webdriver.Key.SHIFT).
      +    perform();
      +
      +

      new ActionSequence(driver)

      Parameters
      driverwebdriver.WebDriver

      The driver instance to use.

      +

      Instance Methods

      click(opt_elementOrButton, opt_button)code »

      Clicks a mouse button.

      +

      If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

      +
      sequence.mouseMove(element).click()
      +
      +
      Parameters
      opt_elementOrButton?(webdriver.WebElement|number)=

      Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

      +
      opt_buttonnumber=

      The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      doubleClick(opt_elementOrButton, opt_button)code »

      Double-clicks a mouse button.

      +

      If an element is provided, the mouse will first be moved to the center of +that element. This is equivalent to:

      +
      sequence.mouseMove(element).doubleClick()
      +
      +

      Warning: this method currently only supports the left mouse button. See +issue 4047.

      +
      Parameters
      opt_elementOrButton?(webdriver.WebElement|number)=

      Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

      +
      opt_buttonnumber=

      The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      dragAndDrop(element, location)code »

      Convenience function for performing a "drag and drop" manuever. The target +element may be moved to the location of another element, or by an offset (in +pixels).

      +
      Parameters
      elementwebdriver.WebElement

      The element to drag.

      +
      location(webdriver.WebElement|{x: number, y: number})

      The +location to drag to, either as another WebElement or an offset in pixels.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      keyDown(key)code »

      Performs a modifier key press. The modifier key is not released +until #keyUp or #sendKeys is called. The key press will be +targetted at the currently focused element.

      +
      Parameters
      keystring

      The modifier key to push. Must be one of +{ALT, CONTROL, SHIFT, COMMAND, META}.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +
      Throws
      Error

      If the key is not a valid modifier key.

      +

      keyUp(key)code »

      Performs a modifier key release. The release is targetted at the currently +focused element.

      +
      Parameters
      keystring

      The modifier key to release. Must be one of +{ALT, CONTROL, SHIFT, COMMAND, META}.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +
      Throws
      Error

      If the key is not a valid modifier key.

      +

      mouseDown(opt_elementOrButton, opt_button)code »

      Presses a mouse button. The mouse button will not be released until +#mouseUp is called, regardless of whether that call is made in this +sequence or another. The behavior for out-of-order events (e.g. mouseDown, +click) is undefined.

      +

      If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

      +
      sequence.mouseMove(element).mouseDown()
      +
      +

      Warning: this method currently only supports the left mouse button. See +issue 4047.

      +
      Parameters
      opt_elementOrButton?(webdriver.WebElement|number)=

      Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

      +
      opt_buttonnumber=

      The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      mouseMove(location, opt_offset)code »

      Moves the mouse. The location to move to may be specified in terms of the +mouse's current location, an offset relative to the top-left corner of an +element, or an element (in which case the middle of the element is used).

      +
      Parameters
      location(webdriver.WebElement|{x: number, y: number})

      The +location to drag to, as either another WebElement or an offset in pixels.

      +
      opt_offset{x: number, y: number}=

      If the target location +is defined as a webdriver.WebElement, this parameter defines an +offset within that element. The offset should be specified in pixels +relative to the top-left corner of the element's bounding box. If +omitted, the element's center will be used as the target offset.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      mouseUp(opt_elementOrButton, opt_button)code »

      Releases a mouse button. Behavior is undefined for calling this function +without a previous call to #mouseDown.

      +

      If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

      +
      sequence.mouseMove(element).mouseUp()
      +
      +

      Warning: this method currently only supports the left mouse button. See +issue 4047.

      +
      Parameters
      opt_elementOrButton?(webdriver.WebElement|number)=

      Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

      +
      opt_buttonnumber=

      The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +

      perform()code »

      Executes this action sequence.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be resolved once +this sequence has completed.

      +

      sendKeys(var_args)code »

      Simulates typing multiple keys. Each modifier key encountered in the +sequence will not be released until it is encountered again. All key events +will be targetted at the currently focused element.

      +
      Parameters
      var_args...(string|Array<string>)

      The keys to type.

      +
      Returns
      webdriver.ActionSequence

      A self reference.

      +
      Throws
      Error

      If the key is not a valid modifier key.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_Alert.html b/docs/class_webdriver_Alert.html index d40586f..398446e 100644 --- a/docs/class_webdriver_Alert.html +++ b/docs/class_webdriver_Alert.html @@ -1,109 +1,24 @@ -webdriver.Alert

      Class webdriver.Alert

      code »
      webdriver.promise.Promise
      -  └ webdriver.promise.Deferred
      -      └ webdriver.Alert

      Represents a modal dialog such as alert, confirm, or - prompt. Provides functions to retrieve the message displayed with - the alert, accept or dismiss the alert, and set the response text (in the - case of prompt).

      Constructor

      webdriver.Alert ( driver, text )
      Parameters
      driver: !webdriver.WebDriver
      The driver controlling the browser this - alert is attached to.
      text: !(string|webdriver.promise.Promise)
      Either the message text - displayed with this alert, or a promise that will be resolved to said - text.
      Show:

      Instance Methods

      Defined in webdriver.Alert

      Accepts this alert.

      Returns
      A promise that will be resolved when - this command has completed.

      Dismisses this alert.

      Returns
      A promise that will be resolved when - this command has completed.

      Retrieves the message text displayed with this alert. For instance, if the - alert were opened with alert("hello"), then this would return "hello".

      Returns
      A promise that will be resolved to the - text displayed with this alert.

      Sets the response text on this alert. This command will return an error if - the underlying alert does not support response text (e.g. window.alert and - window.confirm).

      Parameters
      text: string
      The text to set.
      Returns
      A promise that will be resolved when - this command has completed.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a - promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.Alert

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be - in one of three states: pending, resolved, or rejected. Each promise starts - in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Properties

      \ No newline at end of file +Alert

      class Alert

      Represents a modal dialog such as alert, confirm, or +prompt. Provides functions to retrieve the message displayed with +the alert, accept or dismiss the alert, and set the response text (in the +case of prompt).

      +

      new Alert(driver, text)

      Parameters
      driverwebdriver.WebDriver

      The driver controlling the browser this +alert is attached to.

      +
      textstring

      The message text displayed with this alert.

      +

      Instance Methods

      accept()code »

      Accepts this alert.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      dismiss()code »

      Dismisses this alert.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      getText()code »

      Retrieves the message text displayed with this alert. For instance, if the +alert were opened with alert("hello"), then this would return "hello".

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved to the text displayed with this alert.

      +

      sendKeys(text)code »

      Sets the response text on this alert. This command will return an error if +the underlying alert does not support response text (e.g. window.alert and +window.confirm).

      +
      Parameters
      textstring

      The text to set.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_AlertPromise.html b/docs/class_webdriver_AlertPromise.html new file mode 100644 index 0000000..cc5e3f3 --- /dev/null +++ b/docs/class_webdriver_AlertPromise.html @@ -0,0 +1,91 @@ +AlertPromise

      class AlertPromise

      final
      webdriver.Alert
      +  └ webdriver.AlertPromise
      All implemented interfaces
      IThenable<T>
      webdriver.promise.Thenable<webdriver.Alert>

      AlertPromise is a promise that will be fulfilled with an Alert. This promise +serves as a forward proxy on an Alert, allowing calls to be scheduled +directly on this instance before the underlying Alert has been fulfilled. In +other words, the following two statements are equivalent:

      +
      driver.switchTo().alert().dismiss();
      +driver.switchTo().alert().then(function(alert) {
      +  return alert.dismiss();
      +});
      +
      +

      new AlertPromise(driver, alert)

      Parameters
      driverwebdriver.WebDriver

      The driver controlling the browser this +alert is attached to.

      +
      alertwebdriver.promise.Thenable<webdriver.Alert>

      A thenable +that will be fulfilled with the promised alert.

      +

      Instance Methods

      accept()code »

      Defers action until the alert has been located.

      +

      Overrides: webdriver.Alert

      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      cancel(opt_reason)code »

      Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

      +

      Specified by: webdriver.promise.Thenable

      Parameters
      opt_reason?(string|webdriver.promise.CancellationError)=

      The reason this +promise is being cancelled.

      +

      dismiss()code »

      Defers action until the alert has been located.

      +

      Overrides: webdriver.Alert

      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      getText()code »

      Defer returning text until the promised alert has been resolved.

      +

      Overrides: webdriver.Alert

      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved to the text displayed with this alert.

      +

      isPending()code »

      Specified by: webdriver.promise.Thenable

      Returns
      boolean

      Whether this promise's value is still being computed.

      +

      sendKeys(text)code »

      Defers action until the alert has been located.

      +

      Overrides: webdriver.Alert

      Parameters
      textstring

      The text to set.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      then(opt_callback, opt_errback)code »

      Registers listeners for when this instance is resolved.

      +

      Specified by: webdriver.promise.Thenable, IThenable

      Parameters
      opt_callback?function(T): (R|IThenable<R>)=

      The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

      +
      opt_errback?function(*): (R|IThenable<R>)=

      The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenCatch(errback)code »

      Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} catch (ex) {
      +  console.error(ex);
      +}
      +
      +// Asynchronous promise API:
      +doAsynchronousWork().thenCatch(function(ex) {
      +  console.error(ex);
      +});
      +
      +

      Specified by: webdriver.promise.Thenable

      Parameters
      errbackfunction(*): (R|IThenable<R>)

      The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenFinally(callback)code »

      Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} finally {
      +  cleanUp();
      +}
      +
      +// Asynchronous promise API:
      +doAsynchronousWork().thenFinally(cleanUp);
      +
      +

      Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

      +
      try {
      +  throw Error('one');
      +} finally {
      +  throw Error('two');  // Hides Error: one
      +}
      +
      +promise.rejected(Error('one'))
      +    .thenFinally(function() {
      +      throw Error('two');  // Hides Error: one
      +    });
      +
      +

      Specified by: webdriver.promise.Thenable

      Parameters
      callbackfunction(): (R|IThenable<R>)

      The function +to call when this promise is resolved.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be fulfilled +with the callback result.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_Builder.html b/docs/class_webdriver_Builder.html index f650658..a67f28c 100644 --- a/docs/class_webdriver_Builder.html +++ b/docs/class_webdriver_Builder.html @@ -3,7 +3,9 @@ to reuse.

      Configures the builder to create a client that will use an existing WebDriver session.

      Parameters
      id: string
      The existing session ID to use.
      Returns
      This Builder instance for chain calling.

      Defined in webdriver.AbstractBuilder

      Returns
      The current desired capabilities for this builder.
      Returns
      The URL of the WebDriver server this instance is configured - to use.

      Configures which WebDriver server should be used for new sessions. Overrides + to use.

      Sets the logging preferences for the created session. Preferences may be + changed by repeated calls, or by calling #withCapabilities.

      Parameters
      prefs: !(webdriver.logging.Preferences|Object.<string, string>)
      The + desired logging preferences.
      Returns
      This Builder instance for chain calling.

      Configures which WebDriver server should be used for new sessions. Overrides the value loaded from the webdriver.AbstractBuilder.SERVER_URL_ENV upon creation of this instance.

      Parameters
      url: string
      URL of the server to use.
      Returns
      This Builder instance for chain calling.

      Sets the desired capabilities when requesting a new session. This will overwrite any previously set desired capabilities.

      Parameters
      capabilities: !(Object|webdriver.Capabilities)
      The desired @@ -19,4 +21,4 @@ default to creating clients that use this session. To create a new session, use #useExistingSession(boolean). The use of this environment variable requires that webdriver.AbstractBuilder.SERVER_URL_ENV also - be set. \ No newline at end of file + be set. \ No newline at end of file diff --git a/docs/class_webdriver_Capabilities.html b/docs/class_webdriver_Capabilities.html index 12d3834..e301d52 100644 --- a/docs/class_webdriver_Capabilities.html +++ b/docs/class_webdriver_Capabilities.html @@ -1,9 +1,60 @@ -webdriver.Capabilities

      Class webdriver.Capabilities

      code »

      Constructor

      webdriver.Capabilities ( opt_other )
      Parameters
      opt_other: (webdriver.Capabilities|Object)=
      Another set of - capabilities to merge into this instance.
      Show:

      Instance Methods

      code »get ( key )*
      Parameters
      key: string
      The capability to return.
      Returns
      The capability with the given key, or null if it has - not been set.
      code »has ( key )boolean
      Parameters
      key: string
      The capability to check.
      Returns
      Whether the specified capability is set.

      Merges another set of capabilities into this instance. Any duplicates in - the provided set will override those already set on this instance.

      Parameters
      other: !(webdriver.Capabilities|Object)
      The capabilities to - merge into this instance.
      Returns
      A self reference.
      Parameters
      key: string
      The capability to set.
      value: *
      The capability value. Capability values must be JSON - serializable. Pass null to unset the capability.
      Returns
      A self reference.
      Returns
      The JSON representation of this instance.

      Instance Properties

      Static Functions

      Returns
      A basic set of capabilities for Android.
      Returns
      A basic set of capabilities for Chrome.
      Returns
      A basic set of capabilities for Firefox.
      Returns
      A basic set of capabilities for HTMLUnit.
      Returns
      A basic set of capabilities for HTMLUnit - with enabled Javascript.
      Returns
      A basic set of capabilities for - Internet Explorer.
      Returns
      A basic set of capabilities for iPad.
      Returns
      A basic set of capabilities for iPhone.
      Returns
      A basic set of capabilities for Opera.
      Returns
      A basic set of capabilities for - PhantomJS.
      Returns
      A basic set of capabilities for Safari.
      \ No newline at end of file +Capabilities

      class Capabilities

      webdriver.Serializable<Object<string, ?>>
      +  └ webdriver.Capabilities

      new Capabilities(opt_other)

      Parameters
      opt_other?Object=

      Another set of +capabilities to merge into this instance.

      +

      Instance Methods

      get(key)code »

      Parameters
      keystring

      The capability to return.

      +
      Returns

      The capability with the given key, or null if it has +not been set.

      +

      has(key)code »

      Parameters
      keystring

      The capability to check.

      +
      Returns
      boolean

      Whether the specified capability is set.

      +

      merge(other)code »

      Merges another set of capabilities into this instance. Any duplicates in +the provided set will override those already set on this instance.

      +
      Parameters
      otherObject

      The capabilities to +merge into this instance.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      serialize()code »

      Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

      +

      Overrides: webdriver.Serializable

      Returns
      Object<string, ?>

      The JSON representation of this instance. Note, +the returned object may contain nested promises that are promised values.

      +

      set(key, value)code »

      Parameters
      keystring

      The capability to set.

      +
      value*

      The capability value. Capability values must be JSON +serializable. Pass null to unset the capability.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      setAlertBehavior(behavior)code »

      Sets the default action to take with an unexpected alert before returning +an error.

      +
      Parameters
      behaviorstring

      The desired behavior; should be "accept", "dismiss", +or "ignore". Defaults to "dismiss".

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      setEnableNativeEvents(enabled)code »

      Sets whether native events should be used.

      +
      Parameters
      enabledboolean

      Whether to enable native events.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      setLoggingPrefs(prefs)code »

      Sets the logging preferences. Preferences may be specified as a +webdriver.logging.Preferences instance, or a as a map of log-type to +log-level.

      +
      Parameters
      prefs(webdriver.logging.Preferences|Object<string, string>)

      The +logging preferences.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      setProxy(proxy)code »

      Sets the proxy configuration for this instance.

      +
      Parameters
      proxy{proxyType: string}

      The desired proxy configuration.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      setScrollBehavior(behavior)code »

      Sets how elements should be scrolled into view for interaction.

      +
      Parameters
      behaviornumber

      The desired scroll behavior: either 0 to align with +the top of the viewport or 1 to align with the bottom.

      +
      Returns
      webdriver.Capabilities

      A self reference.

      +

      Static Functions

      Capabilities.android()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for Android.

      +

      Capabilities.chrome()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for Chrome.

      +

      Capabilities.firefox()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for Firefox.

      +

      Capabilities.htmlunit()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for HTMLUnit.

      +

      Capabilities.htmlunitwithjs()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for HTMLUnit +with enabled Javascript.

      +

      Capabilities.ie()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for +Internet Explorer.

      +

      Capabilities.ipad()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for iPad.

      +

      Capabilities.iphone()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for iPhone.

      +

      Capabilities.opera()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for Opera.

      +

      Capabilities.phantomjs()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for +PhantomJS.

      +

      Capabilities.safari()code »

      Returns
      webdriver.Capabilities

      A basic set of capabilities for Safari.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_Command.html b/docs/class_webdriver_Command.html index 77ec1da..b6f001a 100644 --- a/docs/class_webdriver_Command.html +++ b/docs/class_webdriver_Command.html @@ -1 +1,15 @@ -webdriver.Command

      Class webdriver.Command

      code »

      Describes a command to be executed by the WebDriverJS framework.

      Constructor

      webdriver.Command ( name )
      Parameters
      name: !webdriver.CommandName
      The name of this command.
      Show:

      Instance Methods

      Returns
      This command's name.
      code »getParameter ( key )*

      Returns a named command parameter.

      Parameters
      key: string
      The parameter key to look up.
      Returns
      The parameter value, or undefined if it has not been set.
      Returns
      The parameters to send with this command.

      Sets a parameter to send with this command.

      Parameters
      name: string
      The parameter name.
      value: *
      The parameter value.
      Returns
      A self reference.

      Sets the parameters for this command.

      Parameters
      parameters: !Object
      The command parameters.
      Returns
      A self reference.

      Instance Properties

      The parameters to this command.

      \ No newline at end of file +Command

      class Command

      Describes a command to be executed by the WebDriverJS framework.

      +

      new Command(name)

      Parameters
      namestring

      The name of this command.

      +

      Instance Methods

      getName()code »

      Returns
      string

      This command's name.

      +

      getParameter(key)code »

      Returns a named command parameter.

      +
      Parameters
      keystring

      The parameter key to look up.

      +
      Returns

      The parameter value, or undefined if it has not been set.

      +

      getParameters()code »

      Returns
      Object<?, *>

      The parameters to send with this command.

      +

      setParameter(name, value)code »

      Sets a parameter to send with this command.

      +
      Parameters
      namestring

      The parameter name.

      +
      value*

      The parameter value.

      +
      Returns
      webdriver.Command

      A self reference.

      +

      setParameters(parameters)code »

      Sets the parameters for this command.

      +
      Parameters
      parametersObject<?, *>

      The command parameters.

      +
      Returns
      webdriver.Command

      A self reference.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_EventEmitter.html b/docs/class_webdriver_EventEmitter.html index 0289852..0a627b2 100644 --- a/docs/class_webdriver_EventEmitter.html +++ b/docs/class_webdriver_EventEmitter.html @@ -1,7 +1,35 @@ -webdriver.EventEmitter

      Class webdriver.EventEmitter

      code »

      Object that can emit events for others to listen for. This is used instead - of Closure's event system because it is much more light weight. The API is - based on Node's EventEmitters.

      Constructor

      webdriver.EventEmitter ( )
      Show:

      Instance Methods

      code »addListener ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      Registers a listener.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.
      code »addListener_ ( type, listenerFn, opt_scope, opt_oneshot )!webdriver.EventEmitter

      Registers a listener.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      opt_oneshot: boolean=
      Whether the listener should be removed after - the first event is fired.
      Returns
      A self reference.
      code »emit ( type, var_args )

      Fires an event and calls all listeners.

      Parameters
      type: string
      The type of event to emit.
      var_args: ...*
      Any arguments to pass to each listener.
      code »listeners ( type )!Array

      Returns a mutable list of listeners for a specific type of event.

      Parameters
      type: string
      The type of event to retrieve the listeners for.
      Returns
      The registered listeners for - the given event type.
      code »on ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      An alias for #addListener().

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.
      code »once ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      Registers a one-time listener which will be called only the first time an - event is emitted, after which it will be removed.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.

      Removes all listeners for a specific type of event. If no event is - specified, all listeners across all types will be removed.

      Parameters
      opt_type: string=
      The type of event to remove listeners from.
      Returns
      A self reference.

      Removes a previously registered event listener.

      Parameters
      type: string
      The type of event to unregister.
      listenerFn: !Function
      The handler function to remove.
      Returns
      A self reference.

      Instance Properties

      Map of events to registered listeners.

      \ No newline at end of file +EventEmitter

      class EventEmitter

      Object that can emit events for others to listen for. This is used instead +of Closure's event system because it is much more light weight. The API is +based on Node's EventEmitters.

      +

      new EventEmitter()

      Parameters
      None.

      Instance Methods

      addListener(type, listenerFn, opt_scope)code »

      Registers a listener.

      +
      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      emit(type, var_args)code »

      Fires an event and calls all listeners.

      +
      Parameters
      typestring

      The type of event to emit.

      +
      var_args...*

      Any arguments to pass to each listener.

      +

      listeners(type)code »

      Returns a mutable list of listeners for a specific type of event.

      +
      Parameters
      typestring

      The type of event to retrieve the listeners for.

      +
      Returns
      Array<{fn: Function, oneshot: boolean, scope: ?(Object)}>

      The registered listeners for +the given event type.

      +

      on(type, listenerFn, opt_scope)code »

      An alias for #addListener().

      +
      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      once(type, listenerFn, opt_scope)code »

      Registers a one-time listener which will be called only the first time an +event is emitted, after which it will be removed.

      +
      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      removeAllListeners(opt_type)code »

      Removes all listeners for a specific type of event. If no event is +specified, all listeners across all types will be removed.

      +
      Parameters
      opt_typestring=

      The type of event to remove listeners from.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      removeListener(type, listenerFn)code »

      Removes a previously registered event listener.

      +
      Parameters
      typestring

      The type of event to unregister.

      +
      listenerFnFunction

      The handler function to remove.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_FileDetector.html b/docs/class_webdriver_FileDetector.html new file mode 100644 index 0000000..678ec82 --- /dev/null +++ b/docs/class_webdriver_FileDetector.html @@ -0,0 +1,21 @@ +FileDetector

      class FileDetector

      Used with WebElement#sendKeys on file +input elements (<input type="file">) to detect when the entered key +sequence defines the path to a file.

      +

      By default, WebElement's will enter all +key sequences exactly as entered. You may set a +file detector on the parent +WebDriver instance to define custom behavior for handling file elements. Of +particular note is the selenium-webdriver/remote.FileDetector, which +should be used when running against a remote +Selenium Server.

      +

      new FileDetector()

      Parameters
      None.

      Instance Methods

      handleFile(driver, path)code »

      Handles the file specified by the given path, preparing it for use with +the current browser. If the path does not refer to a valid file, it will +be returned unchanged, otherwisee a path suitable for use with the current +browser will be returned.

      +

      This default implementation is a no-op. Subtypes may override this +function for custom tailored file handling.

      +
      Parameters
      driverwebdriver.WebDriver

      The driver for the current browser.

      +
      pathstring

      The path to process.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise for the processed +file path.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_FirefoxDomExecutor.html b/docs/class_webdriver_FirefoxDomExecutor.html index c36dcb1..6057523 100644 --- a/docs/class_webdriver_FirefoxDomExecutor.html +++ b/docs/class_webdriver_FirefoxDomExecutor.html @@ -1,2 +1,2 @@ -webdriver.FirefoxDomExecutor

      Class webdriver.FirefoxDomExecutor

      code »
      All implemented interfaces:
      webdriver.CommandExecutor

      Constructor

      webdriver.FirefoxDomExecutor ( )

      Enumerations

      webdriver.FirefoxDomExecutor.Attribute_
      Attributes used to communicate with the FirefoxDriver extension.
      webdriver.FirefoxDomExecutor.EventType_
      Events used to communicate with the FirefoxDriver extension.
      Show:

      Instance Methods

      code »execute ( command, callback )
      Parameters
      command
      callback

      Instance Properties

      code »pendingCommand_ : ?{name: string, callback: !Function}

      The pending command, if any.

      Static Functions

      Returns
      Whether the current environment supports the - FirefoxDomExecutor.
      \ No newline at end of file +webdriver.FirefoxDomExecutor

      Class webdriver.FirefoxDomExecutor

      code »
      All implemented interfaces:
      webdriver.CommandExecutor

      Constructor

      webdriver.FirefoxDomExecutor ( )

      Enumerations

      webdriver.FirefoxDomExecutor.Attribute_
      Attributes used to communicate with the FirefoxDriver extension.
      webdriver.FirefoxDomExecutor.EventType_
      Events used to communicate with the FirefoxDriver extension.
      Show:

      Instance Methods

      code »execute ( command, callback )
      Parameters
      command
      callback

      Instance Properties

      code »pendingCommand_ : ?{name: string, callback: !Function}

      The pending command, if any.

      Static Functions

      Returns
      Whether the current environment supports the + FirefoxDomExecutor.
      \ No newline at end of file diff --git a/docs/class_webdriver_Locator.html b/docs/class_webdriver_Locator.html index c6876e3..8960051 100644 --- a/docs/class_webdriver_Locator.html +++ b/docs/class_webdriver_Locator.html @@ -1,2 +1,12 @@ -webdriver.Locator

      Class webdriver.Locator

      code »

      An element locator.

      Constructor

      webdriver.Locator ( using, value )
      Parameters
      using: string
      The type of strategy to use for this locator.
      value: string
      The search target of this locator.
      Show:

      Instance Methods

      code »toString ( )string

      Instance Properties

      The search strategy to use when searching for an element.

      The search target for this locator.

      Static Functions

      Verifies that a value is a valid locator to use for searching for - elements on the page.

      Parameters
      value: *
      The value to check is a valid locator.
      Returns
      A valid locator object or function.
      Throws
      TypeError
      If the given value is an invalid locator.

      Creates a factory function for a webdriver.Locator.

      Parameters
      type: string
      The type of locator for the factory.
      Returns
      The new factory function.
      \ No newline at end of file +Locator

      class Locator

      An element locator.

      +

      new Locator(using, value)

      Parameters
      usingstring

      The type of strategy to use for this locator.

      +
      valuestring

      The search target of this locator.

      +

      Instance Methods

      toString()code »

      Returns
      string

      Instance Properties

      usingstring

      The search strategy to use when searching for an element.

      +
      valuestring

      The search target for this locator.

      +

      Static Functions

      Locator.checkLocator(value)code »

      Verifies that a value is a valid locator to use for searching for +elements on the page.

      +
      Parameters
      value*

      The value to check is a valid locator.

      +
      Returns
      (webdriver.Locator|Function)

      A valid locator object or function.

      +
      Throws
      TypeError

      If the given value is an invalid locator.

      +

      Static Properties

      Locator.StrategyObject<string, function(string): (Function|webdriver.Locator)>

      Maps webdriver.By.Hash keys to the appropriate factory function.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_Serializable.html b/docs/class_webdriver_Serializable.html new file mode 100644 index 0000000..df789cf --- /dev/null +++ b/docs/class_webdriver_Serializable.html @@ -0,0 +1,9 @@ +Serializable

      class Serializable<T>

      Defines an object that can be asynchronously serialized to its WebDriver +wire representation.

      +

      new Serializable()

      Parameters
      None.

      Instance Methods

      serialize()code »

      Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

      +
      Returns
      (T|IThenable<T>)

      This instance's serialized wire format.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_Session.html b/docs/class_webdriver_Session.html index 5aa83a4..689fa4b 100644 --- a/docs/class_webdriver_Session.html +++ b/docs/class_webdriver_Session.html @@ -1,3 +1,13 @@ -webdriver.Session

      Class webdriver.Session

      code »

      Contains information about a WebDriver session.

      Constructor

      webdriver.Session ( id, capabilities )
      Parameters
      id: string
      The session ID.
      capabilities: !(Object|webdriver.Capabilities)
      The session - capabilities.
      Show:

      Instance Methods

      Returns
      This session's capabilities.
      code »getCapability ( key )*

      Retrieves the value of a specific capability.

      Parameters
      key: string
      The capability to retrieve.
      Returns
      The capability value.
      Returns
      This session's ID.

      Returns the JSON representation of this object, which is just the string - session ID.

      Returns
      The JSON representation of this Session.

      Instance Properties

      \ No newline at end of file +Session

      class Session

      Contains information about a WebDriver session.

      +

      new Session(id, capabilities)

      Parameters
      idstring

      The session ID.

      +
      capabilitiesObject

      The session +capabilities.

      +

      Instance Methods

      getCapabilities()code »

      Returns
      webdriver.Capabilities

      This session's capabilities.

      +

      getCapability(key)code »

      Retrieves the value of a specific capability.

      +
      Parameters
      keystring

      The capability to retrieve.

      +
      Returns

      The capability value.

      +

      getId()code »

      Returns
      string

      This session's ID.

      +

      toJSON(arg0)code »

      Returns the JSON representation of this object, which is just the string +session ID.

      +
      Parameters
      arg0string=
      Returns
      string

      The JSON representation of this Session.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_TouchSequence.html b/docs/class_webdriver_TouchSequence.html new file mode 100644 index 0000000..5ea0593 --- /dev/null +++ b/docs/class_webdriver_TouchSequence.html @@ -0,0 +1,49 @@ +TouchSequence

      class TouchSequence

      Class for defining sequences of user touch interactions. Each sequence +will not be executed until #perform is called.

      +

      Example:

      +
      new webdriver.TouchSequence(driver).
      +    tapAndHold({x: 0, y: 0}).
      +    move({x: 3, y: 4}).
      +    release({x: 10, y: 10}).
      +    perform();
      +
      +

      new TouchSequence(driver)

      Parameters
      driverwebdriver.WebDriver

      The driver instance to use.

      +

      Instance Methods

      doubleTap(elem)code »

      Double taps an element.

      +
      Parameters
      elemwebdriver.WebElement

      The element to double tap.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      flick(speed)code »

      Flick, starting anywhere on the screen, at speed xspeed and yspeed.

      +
      Parameters
      speed{xspeed: number, yspeed: number}

      The speed to flick in each +direction, in pixels per second.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      flickElement(elem, offset, speed)code »

      Flick starting at elem and moving by x and y at specified speed.

      +
      Parameters
      elemwebdriver.WebElement

      The element where flick starts.

      +
      offset{x: number, y: number}

      The offset to flick to.

      +
      speednumber

      The speed to flick at in pixels per second.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      longPress(elem)code »

      Long press on an element.

      +
      Parameters
      elemwebdriver.WebElement

      The element to long press.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      move(location)code »

      Move a held touch to the specified location.

      +
      Parameters
      location{x: number, y: number}

      The location to move to.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      perform()code »

      Executes this action sequence.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be resolved once +this sequence has completed.

      +

      release(location)code »

      Release a held touch at the specified location.

      +
      Parameters
      location{x: number, y: number}

      The location to release at.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      scroll(offset)code »

      Scrolls the touch screen by the given offset.

      +
      Parameters
      offset{x: number, y: number}

      The offset to scroll to.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      scrollFromElement(elem, offset)code »

      Scrolls the touch screen, starting on elem and moving by the specified +offset.

      +
      Parameters
      elemwebdriver.WebElement

      The element where scroll starts.

      +
      offset{x: number, y: number}

      The offset to scroll to.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      tap(elem)code »

      Taps an element.

      +
      Parameters
      elemwebdriver.WebElement

      The element to tap.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +

      tapAndHold(location)code »

      Touch down at the given location.

      +
      Parameters
      location{x: number, y: number}

      The location to touch down at.

      +
      Returns
      webdriver.TouchSequence

      A self reference.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_UnhandledAlertError.html b/docs/class_webdriver_UnhandledAlertError.html index c8c53b4..e2ea0ca 100644 --- a/docs/class_webdriver_UnhandledAlertError.html +++ b/docs/class_webdriver_UnhandledAlertError.html @@ -1,6 +1,18 @@ -webdriver.UnhandledAlertError

      Class webdriver.UnhandledAlertError

      code »
      Error
      +UnhandledAlertError

      An error returned to indicate that there is an unhandled modal dialog on the - current page.

      Constructor

      webdriver.UnhandledAlertError ( message, alert )
      Parameters
      message: string
      The error message.
      alert: !webdriver.Alert
      The alert handle.
      Show:

      Instance Methods

      Defined in webdriver.UnhandledAlertError

      Returns
      The open alert.

      Defined in bot.Error

      Returns
      he string representation of this error.

      Instance Properties

      Defined in webdriver.UnhandledAlertError

      Defined in bot.Error

      Flag used for duck-typing when this code is embedded in a Firefox extension. - This is required since an Error thrown in one component and then reported - to another will fail instanceof checks in the second component.

      Static Properties

      \ No newline at end of file + └ webdriver.UnhandledAlertError

      An error returned to indicate that there is an unhandled modal dialog on the +current page.

      +

      new UnhandledAlertError(message, text, alert)

      Parameters
      messagestring

      The error message.

      +
      textstring

      The text displayed with the unhandled alert.

      +
      alertwebdriver.Alert

      The alert handle.

      +

      Instance Methods

      getAlertText()code »

      Returns
      string

      The text displayed with the unhandled alert.

      +

      toString()code »

      Defined by: bot.Error

      Returns
      string

      The string representation of this error.

      +

      Instance Properties

      codenumber

      This error's status code.

      +
      descriptionstring

      IE-only.

      +
      fileNamestring

      Mozilla-only

      +
      isAutomationErrorboolean

      Flag used for duck-typing when this code is embedded in a Firefox extension. +This is required since an Error thrown in one component and then reported +to another will fail instanceof checks in the second component.

      +
      lineNumbernumber

      Mozilla-only.

      +
      messagestring
      No description.
      namestring
      No description.
      sourceURL?

      Doesn't seem to exist, but closure/debug.js references it.

      +
      stackstring
      No description.
      statestring
      No description.
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver.html b/docs/class_webdriver_WebDriver.html index 4607477..9d778c4 100644 --- a/docs/class_webdriver_WebDriver.html +++ b/docs/class_webdriver_WebDriver.html @@ -1,251 +1,313 @@ -webdriver.WebDriver

      Class webdriver.WebDriver

      code »

      Creates a new WebDriver client, which provides control over a browser. +WebDriver

      class WebDriver

      Creates a new WebDriver client, which provides control over a browser.

      +

      Every WebDriver command returns a webdriver.promise.Promise that +represents the result of that command. Callbacks may be registered on this +object to manipulate the command result or catch an expected error. Any +commands scheduled with a callback are considered sub-commands and will +execute before the next command in the current frame. For example:

      +
      var message = [];
      +driver.call(message.push, message, 'a').then(function() {
      +  driver.call(message.push, message, 'b');
      +});
      +driver.call(message.push, message, 'c');
      +driver.call(function() {
      +  alert('message is abc? ' + (message.join('') == 'abc'));
      +});
      +
      +

      new WebDriver(session, executor, opt_flow)

      Parameters
      session(webdriver.Session|webdriver.promise.Promise)

      Either a +known session or a promise that will be resolved to a session.

      +
      executorwebdriver.CommandExecutor

      The executor to use when +sending commands to the browser.

      +
      opt_flow?webdriver.promise.ControlFlow=

      The flow to +schedule commands through. Defaults to the active flow object.

      +

      Instance Methods

      actions()code »

      Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

      +
      driver.actions().
      +    mouseDown(element1).
      +    mouseMove(element2).
      +    mouseUp().
      +    perform();
      +
      +
      Returns
      webdriver.ActionSequence

      A new action sequence for this instance.

      +

      <T> call(fn, opt_scope, var_args)code »

      Schedules a command to execute a custom function.

      +
      Parameters
      fnfunction(...?): (T|webdriver.promise.Promise<T>)

      The function to +execute.

      +
      opt_scope?Object=

      The object in whose scope to execute the function.

      +
      var_args...*

      Any arguments to pass to the function.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will be resolved' +with the function's result.

      +

      close()code »

      Schedules a command to close the current window.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when this command has completed.

      +

      controlFlow()code »

      Returns
      webdriver.promise.ControlFlow

      The control flow used by this +instance.

      +

      <T> executeAsyncScript(script, var_args)code »

      Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

      +

      Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

      +

      Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

      +
      • For a HTML element, the value will resolve to a +webdriver.WebElement
      • Null and undefined return values will resolve to null
      • Booleans, numbers, and strings will resolve as is
      • Functions will resolve to their string representation
      • For arrays and objects, each member item will be converted according to +the rules above
      +

      Example #1: Performing a sleep that is synchronized with the currently +selected window:

      +
      var start = new Date().getTime();
      +driver.executeAsyncScript(
      +    'window.setTimeout(arguments[arguments.length - 1], 500);').
      +    then(function() {
      +      console.log(
      +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
      +    });
      +
      +

      Example #2: Synchronizing a test with an AJAX application:

      +
      var button = driver.findElement(By.id('compose-button'));
      +button.click();
      +driver.executeAsyncScript(
      +    'var callback = arguments[arguments.length - 1];' +
      +    'mailClient.getComposeWindowWidget().onload(callback);');
      +driver.switchTo().frame('composeWidget');
      +driver.findElement(By.id('to')).sendKeys('dog@example.com');
      +
      +

      Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

      +
      driver.executeAsyncScript(function() {
      +  var callback = arguments[arguments.length - 1];
      +  var xhr = new XMLHttpRequest();
      +  xhr.open("GET", "/resource/data.json", true);
      +  xhr.onreadystatechange = function() {
      +    if (xhr.readyState == 4) {
      +      callback(xhr.responseText);
      +    }
      +  }
      +  xhr.send('');
      +}).then(function(str) {
      +  console.log(JSON.parse(str)['food']);
      +});
      +
      +
      Parameters
      script(string|Function)

      The script to execute.

      +
      var_args...*

      The arguments to pass to the script.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will resolve to the +scripts return value.

      +

      <T> executeScript(script, var_args)code »

      Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

      +

      Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

      +

      The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

      +

      If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

      +
      • For a HTML element, the value will resolve to a +webdriver.WebElement
      • Null and undefined return values will resolve to null
      • Booleans, numbers, and strings will resolve as is
      • Functions will resolve to their string representation
      • For arrays and objects, each member item will be converted according to +the rules above
      +
      Parameters
      script(string|Function)

      The script to execute.

      +
      var_args...*

      The arguments to pass to the script.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will resolve to the +scripts return value.

      +

      findElement(locator)code »

      Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

      +

      The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

      +
      var e1 = driver.findElement(By.id('foo'));
      +var e2 = driver.findElement({id:'foo'});
      +
      +

      You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

      +
      var link = driver.findElement(firstVisibleLink);
       
      - Every WebDriver command returns a webdriver.promise.Promise that
      - represents the result of that command. Callbacks may be registered on this
      - object to manipulate the command result or catch an expected error. Any
      - commands scheduled with a callback are considered sub-commands and will
      - execute before the next command in the current frame. For example:
      - 
      
      -   var message = [];
      -   driver.call(message.push, message, 'a').then(function() {
      -     driver.call(message.push, message, 'b');
      -   });
      -   driver.call(message.push, message, 'c');
      -   driver.call(function() {
      -     alert('message is abc? ' + (message.join('') == 'abc'));
      -   });
      - 

      Constructor

      webdriver.WebDriver ( session, executor, opt_flow )
      Parameters
      session: !(webdriver.Session|webdriver.promise.Promise)
      Either a - known session or a promise that will be resolved to a session.
      executor: !webdriver.CommandExecutor
      The executor to use when - sending commands to the browser.
      opt_flow: webdriver.promise.ControlFlow=
      The flow to - schedule commands through. Defaults to the active flow object.

      Classes

      webdriver.WebDriver.Logs
      Interface for managing WebDriver log records.
      webdriver.WebDriver.Navigation
      Interface for navigating back and forth in the browser history.
      webdriver.WebDriver.Options
      Provides methods for managing browser and driver state.
      webdriver.WebDriver.TargetLocator
      An interface for changing the focus of the driver to another frame or window.
      webdriver.WebDriver.Timeouts
      An interface for managing timeout behavior for WebDriver instances.
      webdriver.WebDriver.Window
      An interface for managing the current window.
      Show:

      Instance Methods

      Creates a new action sequence using this driver. The sequence will not be - scheduled for execution until webdriver.ActionSequence#perform is - called. Example: -

      
      -   driver.actions().
      -       mouseDown(element1).
      -       mouseMove(element2).
      -       mouseUp().
      -       perform();
      - 
      Returns
      A new action sequence for this instance.
      code »call ( fn, opt_scope, var_args )!webdriver.promise.Promise

      Schedules a command to execute a custom function.

      Parameters
      fn: !Function
      The function to execute.
      opt_scope: Object=
      The object in whose scope to execute the function.
      var_args: ...*
      Any arguments to pass to the function.
      Returns
      A promise that will be resolved with the - function's result.

      Schedules a command to close the current window.

      Returns
      A promise that will be resolved when - this command has completed.
      Returns
      The control flow used by this - instance.

      Schedules a command to execute asynchronous JavaScript in the context of the - currently selected frame or window. The script fragment will be executed as - the body of an anonymous function. If the script is provided as a function - object, that function will be converted to a string for injection into the - target window. - - Any arguments provided in addition to the script will be included as script - arguments and may be referenced using the arguments object. - Arguments may be a boolean, number, string, or webdriver.WebElement. - Arrays and objects may also be used as script arguments as long as each item - adheres to the types previously mentioned. - - Unlike executing synchronous JavaScript with - webdriver.WebDriver.prototype.executeScript, scripts executed with - this function must explicitly signal they are finished by invoking the - provided callback. This callback will always be injected into the - executed function as the last argument, and thus may be referenced with - arguments[arguments.length - 1]. The following steps will be taken - for resolving this functions return value against the first argument to the - script's callback function: -

        -
      • For a HTML element, the value will resolve to a - webdriver.WebElement
      • -
      • Null and undefined return values will resolve to null
      • -
      • Booleans, numbers, and strings will resolve as is
      • -
      • Functions will resolve to their string representation
      • -
      • For arrays and objects, each member item will be converted according to - the rules above
      • -
      - - Example #1: Performing a sleep that is synchronized with the currently - selected window: -
      - var start = new Date().getTime();
      - driver.executeAsyncScript(
      -     'window.setTimeout(arguments[arguments.length - 1], 500);').
      -     then(function() {
      -       console.log('Elapsed time: ' + (new Date().getTime() - start) + ' ms');
      -     });
      - 
      - - Example #2: Synchronizing a test with an AJAX application: -
      - var button = driver.findElement(By.id('compose-button'));
      - button.click();
      - driver.executeAsyncScript(
      -     'var callback = arguments[arguments.length - 1];' +
      -     'mailClient.getComposeWindowWidget().onload(callback);');
      - driver.switchTo().frame('composeWidget');
      - driver.findElement(By.id('to')).sendKEys('dog@example.com');
      - 
      - - Example #3: Injecting a XMLHttpRequest and waiting for the result. In this - example, the inject script is specified with a function literal. When using - this format, the function is converted to a string for injection, so it - should not reference any symbols not defined in the scope of the page under - test. -
      - driver.executeAsyncScript(function() {
      -   var callback = arguments[arguments.length - 1];
      -   var xhr = new XMLHttpRequest();
      -   xhr.open("GET", "/resource/data.json", true);
      -   xhr.onreadystatechange = function() {
      -     if (xhr.readyState == 4) {
      -       callback(xhr.resposneText);
      -     }
      -   }
      -   xhr.send('');
      - }).then(function(str) {
      -   console.log(JSON.parse(str)['food']);
      - });
      - 
      Parameters
      script: !(string|Function)
      The script to execute.
      var_args: ...*
      The arguments to pass to the script.
      Returns
      A promise that will resolve to the - scripts return value.

      Schedules a command to execute JavaScript in the context of the currently - selected frame or window. The script fragment will be executed as the body - of an anonymous function. If the script is provided as a function object, - that function will be converted to a string for injection into the target - window. - - Any arguments provided in addition to the script will be included as script - arguments and may be referenced using the arguments object. - Arguments may be a boolean, number, string, or webdriver.WebElement. - Arrays and objects may also be used as script arguments as long as each item - adheres to the types previously mentioned. - - The script may refer to any variables accessible from the current window. - Furthermore, the script will execute in the window's context, thus - document may be used to refer to the current document. Any local - variables will not be available once the script has finished executing, - though global variables will persist. - - If the script has a return value (i.e. if the script contains a return - statement), then the following steps will be taken for resolving this - functions return value: -

        -
      • For a HTML element, the value will resolve to a - webdriver.WebElement
      • -
      • Null and undefined return values will resolve to null
      • -
      • Booleans, numbers, and strings will resolve as is
      • -
      • Functions will resolve to their string representation
      • -
      • For arrays and objects, each member item will be converted according to - the rules above
      • -
      Parameters
      script: !(string|Function)
      The script to execute.
      var_args: ...*
      The arguments to pass to the script.
      Returns
      A promise that will resolve to the - scripts return value.

      Locates a DOM element so that commands may be issued against it using the - webdriver.WebElement class. This is accomplished by storing a - reference to the element in an object on the element's ownerDocument. - #executeScript will then be used to create a WebElement from this - reference. This requires this driver to currently be focused on the - ownerDocument's window+frame.

      Parameters
      element: !Element
      The element to locate.
      Returns
      A promise that will be resolved - with the located WebElement.

      Schedule a command to find an element on the page. If the element cannot be - found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned - by the driver. Unlike other commands, this error cannot be suppressed. In - other words, scheduling a command to find an element doubles as an assert - that the element is present on the page. To test whether an element is - present on the page, use #isElementPresent instead. - -

      The search criteria for an element may be defined using one of the - factories in the webdriver.By namespace, or as a short-hand - webdriver.By.Hash object. For example, the following two statements - are equivalent: -

      - var e1 = driver.findElement(By.id('foo'));
      - var e2 = driver.findElement({id:'foo'});
      - 
      - -

      You may also provide a custom locator function, which takes as input - this WebDriver instance and returns a webdriver.WebElement, or a - promise that will resolve to a WebElement. For example, to find the first - visible link on a page, you could write: -

      - var link = driver.findElement(firstVisibleLink);
      -
      - function firstVisibleLink(driver) {
      -   var links = driver.findElements(By.tagName('a'));
      -   return webdriver.promise.filter(links, function(link) {
      -     return links.isDisplayed();
      -   }).then(function(visibleLinks) {
      -     return visibleLinks[0];
      -   });
      - }
      - 
      - -

      When running in the browser, a WebDriver cannot manipulate DOM elements - directly; it may do so only through a webdriver.WebElement reference. - This function may be used to generate a WebElement from a DOM element. A - reference to the DOM element will be stored in a known location and this - driver will attempt to retrieve it through #executeScript. If the - element cannot be found (eg, it belongs to a different document than the - one this instance is currently focused on), a - bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

      Parameters
      locator: !(webdriver.Locator|webdriver.By.Hash|Element|Function)
      The - locator to use.
      Returns
      A WebElement that can be used to issue - commands against the located element. If the element is not found, the - element will be invalidated and all scheduled commands aborted.
      Parameters
      locatorFn: !Function
      The locator function to use.
      context: !(webdriver.WebDriver|webdriver.WebElement)
      The search - context.
      Returns
      A - promise that will resolve to a list of WebElements.

      Schedule a command to search for multiple elements on the page.

      Parameters
      locator: !(webdriver.Locator|webdriver.By.Hash|Function)
      The locator - strategy to use when searching for the element.
      Returns
      A - promise that will resolve to an array of WebElements.
      Parameters
      locatorFn: !Function
      The locator function to use.
      context: !(webdriver.WebDriver|webdriver.WebElement)
      The search - context.
      Returns
      A - promise that will resolve to an array of WebElements.

      Schedules a command to navigate to the given URL.

      Parameters
      url: string
      The fully qualified URL to open.
      Returns
      A promise that will be resolved when the - document has finished loading.

      Schedules a command to retrieve the current list of available window handles.

      Returns
      A promise that will be resolved with an - array of window handles.
      Returns
      A promise that will resolve with the - this instance's capabilities.

      Schedules a command to retrieve the URL of the current page.

      Returns
      A promise that will be resolved with the - current URL.

      Schedules a command to retrieve the current page's source. The page source - returned is a representation of the underlying DOM: do not expect it to be - formatted or escaped in the same way as the response sent from the web - server.

      Returns
      A promise that will be resolved with the - current page source.
      Returns
      A promise for this client's session.

      Schedules a command to retrieve the current page's title.

      Returns
      A promise that will be resolved with the - current page's title.

      Schedules a command to retrieve they current window handle.

      Returns
      A promise that will be resolved with the - current window handle.

      Schedules a command to test if an element is present on the page. - -

      If given a DOM element, this function will check if it belongs to the - document the driver is currently focused on. Otherwise, the function will - test if at least one element can be found with the given search criteria.

      Parameters
      locatorOrElement: !(webdriver.Locator|webdriver.By.Hash|Element|Function)
      The locator to use, or the actual - DOM element to be located by the server.
      Returns
      A promise that will resolve - with whether the element is present on the page.
      Returns
      The options interface for this - instance.
      Returns
      The navigation interface for this - instance.

      Schedules a command to quit the current session. After calling quit, this - instance will be invalidated and may no longer be used to issue commands - against the browser.

      Returns
      A promise that will be resolved when - the command has completed.
      code »schedule ( command, description )!webdriver.promise.Promise

      Schedules a webdriver.Command to be executed by this driver's - webdriver.CommandExecutor.

      Parameters
      command: !webdriver.Command
      The command to schedule.
      description: string
      A description of the command for debugging.
      Returns
      A promise that will be resolved with - the command result.

      Schedules a command to make the driver sleep for the given amount of time.

      Parameters
      ms: number
      The amount of time, in milliseconds, to sleep.
      Returns
      A promise that will be resolved when the - sleep has finished.
      Returns
      The target locator interface for - this instance.

      Schedule a command to take a screenshot. The driver makes a best effort to - return a screenshot of the following, in order of preference: -

        -
      1. Entire page -
      2. Current window -
      3. Visible portion of the current frame -
      4. The screenshot of the entire display containing the browser -
      Returns
      A promise that will be resolved to the - screenshot as a base-64 encoded PNG.
      code »wait ( fn, timeout, opt_message )!webdriver.promise.Promise

      Schedules a command to wait for a condition to hold, as defined by some - user supplied function. If any errors occur while evaluating the wait, they - will be allowed to propagate.

      Parameters
      fn: function(): boolean
      The function to evaluate as a wait condition.
      timeout: number
      How long to wait for the condition to be true.
      opt_message: string=
      An optional message to use if the wait times - out.
      Returns
      A promise that will be resolved when the - wait condition has been satisfied.

      Instance Properties

      Static Functions

      code »webdriver.WebDriver.acquireSession_ ( executor, command, description )!webdriver.WebDriver

      Sends a command to the server that is expected to return the details for a - webdriver.Session. This may either be an existing session, or a - newly created one.

      Parameters
      executor: !webdriver.CommandExecutor
      Command executor to use when - querying for session details.
      command: !webdriver.Command
      The command to send to fetch the session - details.
      description: string
      A descriptive debug label for this action.
      Returns
      A new WebDriver client for the session.

      Creates a new WebDriver client for an existing session.

      Parameters
      executor: !webdriver.CommandExecutor
      Command executor to use when - querying for session details.
      sessionId: string
      ID of the session to attach to.
      Returns
      A new client for the specified session.
      code »webdriver.WebDriver.createSession ( executor, desiredCapabilities )!webdriver.WebDriver

      Creates a new WebDriver session.

      Parameters
      executor: !webdriver.CommandExecutor
      The executor to create the new - session with.
      desiredCapabilities: !webdriver.Capabilities
      The desired - capabilities for the new session.
      Returns
      The driver for the newly created session.

      Translates a command to its wire-protocol representation before passing it - to the given executor for execution.

      Parameters
      executor: !webdriver.CommandExecutor
      The executor to use.
      command: !webdriver.Command
      The command to execute.
      Returns
      A promise that will resolve with the - command response.

      Converts a value from its JSON representation according to the WebDriver wire - protocol. Any JSON object containing a - webdriver.WebElement.ELEMENT_KEY key will be decoded to a - webdriver.WebElement object. All other values will be passed through - as is.

      Parameters
      driver: !webdriver.WebDriver
      The driver instance to use as the - parent of any unwrapped webdriver.WebElement values.
      value: *
      The value to convert.
      Returns
      The converted value.

      Converts an object to its JSON representation in the WebDriver wire protocol. - When converting values of type object, the following steps will be taken: -

        -
      1. if the object provides a "toWireValue" function, the return value will - be returned in its fully resolved state (e.g. this function may return - promise values)
      2. -
      3. if the object provides a "toJSON" function, the return value of this - function will be returned
      4. -
      5. otherwise, the value of each key will be recursively converted according - to the rules above.
      6. -
      Parameters
      obj: *
      The object to convert.
      Returns
      A promise that will resolve to the - input value's JSON representation.
      \ No newline at end of file +function firstVisibleLink(driver) { + var links = driver.findElements(By.tagName('a')); + return webdriver.promise.filter(links, function(link) { + return links.isDisplayed(); + }).then(function(visibleLinks) { + return visibleLinks[0]; + }); +} + +

      When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

      +
      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator to use.

      +
      Returns
      webdriver.WebElement

      A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

      +

      findElements(locator)code »

      Schedule a command to search for multiple elements on the page.

      +
      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The locator +strategy to use when searching for the element.

      +
      Returns
      webdriver.promise.Promise<Array<webdriver.WebElement>>

      A +promise that will resolve to an array of WebElements.

      +

      get(url)code »

      Schedules a command to navigate to the given URL.

      +
      Parameters
      urlstring

      The fully qualified URL to open.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the document has finished loading.

      +

      getAllWindowHandles()code »

      Schedules a command to retrieve the current list of available window handles.

      +
      Returns
      webdriver.promise.Promise<Array<string>>

      A promise that will +be resolved with an array of window handles.

      +

      getCapabilities()code »

      Returns
      webdriver.promise.Promise<webdriver.Capabilities>

      A promise +that will resolve with the this instance's capabilities.

      +

      getCurrentUrl()code »

      Schedules a command to retrieve the URL of the current page.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the current URL.

      +

      getPageSource()code »

      Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the current page source.

      +

      getSession()code »

      Returns
      webdriver.promise.Promise<webdriver.Session>

      A promise for this +client's session.

      +

      getTitle()code »

      Schedules a command to retrieve the current page's title.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the current page's title.

      +

      getWindowHandle()code »

      Schedules a command to retrieve they current window handle.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the current window handle.

      +

      isElementPresent(locatorOrElement)code »

      Schedules a command to test if an element is present on the page.

      +

      If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

      +
      Parameters
      locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The locator to use, or the actual +DOM element to be located by the server.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will resolve +with whether the element is present on the page.

      +

      manage()code »

      Returns
      webdriver.WebDriver.Options

      The options interface for this +instance.

      +


      quit()code »

      Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the command has completed.

      +

      <T> schedule(command, description)code »

      Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

      +
      Parameters
      commandwebdriver.Command

      The command to schedule.

      +
      descriptionstring

      A description of the command for debugging.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will be resolved +with the command result.

      +

      setFileDetector(detector)code »

      Sets the file detector that should be +used with this instance.

      +
      Parameters
      detectorwebdriver.FileDetector

      The detector to use or null.

      +

      sleep(ms)code »

      Schedules a command to make the driver sleep for the given amount of time.

      +
      Parameters
      msnumber

      The amount of time, in milliseconds, to sleep.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the sleep has finished.

      +

      switchTo()code »

      Returns
      webdriver.WebDriver.TargetLocator

      The target locator interface for +this instance.

      +

      takeScreenshot()code »

      Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

      +
      1. Entire page +
      2. Current window +
      3. Visible portion of the current frame +
      4. The screenshot of the entire display containing the browser +
      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

      +

      touchActions()code »

      Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

      +
      driver.touchActions().
      +    tap(element1).
      +    doubleTap(element2).
      +    perform();
      +
      +
      Returns
      webdriver.TouchSequence

      A new touch sequence for this instance.

      +

      <T> wait(condition, opt_timeout, opt_message)code »

      Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

      +

      For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

      +

      Example: waiting up to 10 seconds for an element to be present and visible +on the page.

      +
      var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
      +button.click();
      +
      +

      This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

      +

      Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

      +
      var started = startTestServer();
      +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
      +driver.get(getServerUrl());
      +
      +
      Parameters
      condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

      The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

      +
      opt_timeoutnumber=

      How long to wait for the condition to be true.

      +
      opt_messagestring=

      An optional message to use if the wait times +out.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

      +

      Static Functions

      WebDriver.attachToSession(executor, sessionId, opt_flow)code »

      Creates a new WebDriver client for an existing session.

      +
      Parameters
      executorwebdriver.CommandExecutor

      Command executor to use when +querying for session details.

      +
      sessionIdstring

      ID of the session to attach to.

      +
      opt_flow?webdriver.promise.ControlFlow=

      The control flow all driver +commands should execute under. Defaults to the +currently active control flow.

      +
      Returns
      webdriver.WebDriver

      A new client for the specified session.

      +

      WebDriver.createSession(executor, desiredCapabilities, opt_flow)code »

      Creates a new WebDriver session.

      +
      Parameters
      executorwebdriver.CommandExecutor

      The executor to create the new +session with.

      +
      desiredCapabilitieswebdriver.Capabilities

      The desired +capabilities for the new session.

      +
      opt_flow?webdriver.promise.ControlFlow=

      The control flow all driver +commands should execute under, including the initial session creation. +Defaults to the currently active +control flow.

      +
      Returns
      webdriver.WebDriver

      The driver for the newly created session.

      +

      Types

      WebDriver.Logs

      Interface for managing WebDriver log records.

      +
      WebDriver.Navigation

      Interface for navigating back and forth in the browser history.

      +
      WebDriver.Options

      Provides methods for managing browser and driver state.

      +
      WebDriver.TargetLocator

      An interface for changing the focus of the driver to another frame or window.

      +
      WebDriver.Timeouts

      An interface for managing timeout behavior for WebDriver instances.

      +
      WebDriver.Window

      An interface for managing the current window.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_Logs.html b/docs/class_webdriver_WebDriver_Logs.html index 7530f25..aeb76be 100644 --- a/docs/class_webdriver_WebDriver_Logs.html +++ b/docs/class_webdriver_WebDriver_Logs.html @@ -1,10 +1,15 @@ -webdriver.WebDriver.Logs

      Class webdriver.WebDriver.Logs

      code »

      Interface for managing WebDriver log records.

      Constructor

      webdriver.WebDriver.Logs ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      Fetches available log entries for the given type. - -

      Note that log buffers are reset after each call, meaning that - available log entries correspond to those entries not yet returned for a - given log type. In practice, this means that this call will return the - available log entries since the last call, or from the start of the - session.

      Parameters
      type: !webdriver.logging.Type
      The desired log type.
      Returns
      A - promise that will resolve to a list of log entries for the specified - type.

      Retrieves the log types available to this driver.

      Returns
      A - promise that will resolve to a list of available log types.

      Instance Properties

      \ No newline at end of file +WebDriver.Logs

      class WebDriver.Logs

      Interface for managing WebDriver log records.

      +

      new WebDriver.Logs(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      get(type)code »

      Fetches available log entries for the given type.

      +

      Note that log buffers are reset after each call, meaning that available +log entries correspond to those entries not yet returned for a given log +type. In practice, this means that this call will return the available log +entries since the last call, or from the start of the session.

      +
      Parameters
      typewebdriver.logging.Type

      The desired log type.

      +
      Returns
      webdriver.promise.Promise<Array<webdriver.logging.Entry>>

      A +promise that will resolve to a list of log entries for the specified +type.

      +

      getAvailableLogTypes()code »

      Retrieves the log types available to this driver.

      +
      Returns
      webdriver.promise.Promise<Array<webdriver.logging.Type>>

      A +promise that will resolve to a list of available log types.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_Navigation.html b/docs/class_webdriver_WebDriver_Navigation.html index efe2114..16af66c 100644 --- a/docs/class_webdriver_WebDriver_Navigation.html +++ b/docs/class_webdriver_WebDriver_Navigation.html @@ -1,5 +1,16 @@ -webdriver.WebDriver.Navigation

      Class webdriver.WebDriver.Navigation

      code »

      Interface for navigating back and forth in the browser history.

      Constructor

      webdriver.WebDriver.Navigation ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      Schedules a command to move backwards in the browser history.

      Returns
      A promise that will be resolved when the - navigation event has completed.

      Schedules a command to move forwards in the browser history.

      Returns
      A promise that will be resolved when the - navigation event has completed.

      Schedules a command to refresh the current page.

      Returns
      A promise that will be resolved when the - navigation event has completed.

      Schedules a command to navigate to a new URL.

      Parameters
      url: string
      The URL to navigate to.
      Returns
      A promise that will be resolved when the - URL has been loaded.

      Instance Properties

      \ No newline at end of file +WebDriver.Navigation

      class WebDriver.Navigation

      Interface for navigating back and forth in the browser history.

      +

      new WebDriver.Navigation(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      back()code »

      Schedules a command to move backwards in the browser history.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the navigation event has completed.

      +

      forward()code »

      Schedules a command to move forwards in the browser history.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the navigation event has completed.

      +

      refresh()code »

      Schedules a command to refresh the current page.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the navigation event has completed.

      +

      to(url)code »

      Schedules a command to navigate to a new URL.

      +
      Parameters
      urlstring

      The URL to navigate to.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the URL has been loaded.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_Options.html b/docs/class_webdriver_WebDriver_Options.html index ee252eb..6e0cc55 100644 --- a/docs/class_webdriver_WebDriver_Options.html +++ b/docs/class_webdriver_WebDriver_Options.html @@ -1,16 +1,41 @@ -webdriver.WebDriver.Options

      Class webdriver.WebDriver.Options

      code »

      Provides methods for managing browser and driver state.

      Constructor

      webdriver.WebDriver.Options ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      code »addCookie ( name, value, opt_path, opt_domain, opt_isSecure, opt_expiry )!webdriver.promise.Promise

      Schedules a command to add a cookie.

      Parameters
      name: string
      The cookie name.
      value: string
      The cookie value.
      opt_path: string=
      The cookie path.
      opt_domain: string=
      The cookie domain.
      opt_isSecure: boolean=
      Whether the cookie is secure.
      opt_expiry: (number|!Date)=
      When the cookie expires. If specified as - a number, should be in milliseconds since midnight, January 1, 1970 UTC.
      Returns
      A promise that will be resolved when the - cookie has been added to the page.

      Schedules a command to delete all cookies visible to the current page.

      Returns
      A promise that will be resolved when all - cookies have been deleted.

      Schedules a command to delete the cookie with the given name. This command is - a no-op if there is no cookie with the given name visible to the current - page.

      Parameters
      name: string
      The name of the cookie to delete.
      Returns
      A promise that will be resolved when the - cookie has been deleted.

      Schedules a command to retrieve the cookie with the given name. Returns null - if there is no such cookie. The cookie will be returned as a JSON object as - described by the WebDriver wire protocol.

      Parameters
      name: string
      The name of the cookie to retrieve.
      Returns
      A promise that will be resolved with the - named cookie, or null if there is no such cookie.

      Schedules a command to retrieve all cookies visible to the current page. - Each cookie will be returned as a JSON object as described by the WebDriver - wire protocol.

      Returns
      A promise that will be resolved with the - cookies visible to the current page.
      Returns
      The interface for managing driver - logs.
      Returns
      The interface for managing driver - timeouts.
      Returns
      The interface for managing the - current window.

      Instance Properties

      \ No newline at end of file +WebDriver.Options

      class WebDriver.Options

      Provides methods for managing browser and driver state.

      +

      new WebDriver.Options(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      addCookie(name, value, opt_path, opt_domain, opt_isSecure, opt_expiry)code »

      Schedules a command to add a cookie.

      +
      Parameters
      namestring

      The cookie name.

      +
      valuestring

      The cookie value.

      +
      opt_pathstring=

      The cookie path.

      +
      opt_domainstring=

      The cookie domain.

      +
      opt_isSecureboolean=

      Whether the cookie is secure.

      +
      opt_expiry(number|Date)=

      When the cookie expires. If specified as +a number, should be in milliseconds since midnight, January 1, 1970 UTC.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the cookie has been added to the page.

      +

      deleteAllCookies()code »

      Schedules a command to delete all cookies visible to the current page.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when all cookies have been deleted.

      +

      deleteCookie(name)code »

      Schedules a command to delete the cookie with the given name. This command is +a no-op if there is no cookie with the given name visible to the current +page.

      +
      Parameters
      namestring

      The name of the cookie to delete.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the cookie has been deleted.

      +

      getCookie(name)code »

      Schedules a command to retrieve the cookie with the given name. Returns null +if there is no such cookie. The cookie will be returned as a JSON object as +described by the WebDriver wire protocol.

      +
      Parameters
      namestring

      The name of the cookie to retrieve.

      +
      Returns
      webdriver.promise.Promise<?{domain: (string|undefined), expiry: (number|undefined), name: string, path: (string|undefined), secure: (boolean|undefined), value: string}>

      A +promise that will be resolved with the named cookie, or null +if there is no such cookie.

      +

      getCookies()code »

      Schedules a command to retrieve all cookies visible to the current page. +Each cookie will be returned as a JSON object as described by the WebDriver +wire protocol.

      +
      Returns
      webdriver.promise.Promise<Array<{domain: (string|undefined), expiry: (number|undefined), name: string, path: (string|undefined), secure: (boolean|undefined), value: string}>>

      A promise that will be +resolved with the cookies visible to the current page.

      +

      logs()code »

      Returns
      webdriver.WebDriver.Logs

      The interface for managing driver +logs.

      +

      timeouts()code »

      Returns
      webdriver.WebDriver.Timeouts

      The interface for managing driver +timeouts.

      +

      window()code »

      Returns
      webdriver.WebDriver.Window

      The interface for managing the +current window.

      +

      Type Definitions

      Options.Cookie{domain: (string|undefined), expiry: (number|undefined), name: string, path: (string|undefined), secure: (boolean|undefined), value: string}

      A JSON description of a browser cookie.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_TargetLocator.html b/docs/class_webdriver_WebDriver_TargetLocator.html index 8ff60ef..fe6d1b0 100644 --- a/docs/class_webdriver_WebDriver_TargetLocator.html +++ b/docs/class_webdriver_WebDriver_TargetLocator.html @@ -1,27 +1,38 @@ -webdriver.WebDriver.TargetLocator

      Class webdriver.WebDriver.TargetLocator

      code »

      An interface for changing the focus of the driver to another frame or window.

      Constructor

      webdriver.WebDriver.TargetLocator ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      Schedules a command retrieve the document.activeElement element on - the current document, or document.body if activeElement is not - available.

      Returns
      The active element.

      Schedules a command to change focus to the active alert dialog. This command - will return a bot.ErrorCode.NO_MODAL_DIALOG_OPEN error if a modal - dialog is not currently open.

      Returns
      The open alert.

      Schedules a command to switch focus of all future commands to the first frame - on the page.

      Returns
      A promise that will be resolved when the - driver has changed focus to the default content.

      Schedules a command to switch the focus of all future commands to another - frame on the page. -

      - If the frame is specified by a number, the command will switch to the frame - by its (zero-based) index into the window.frames collection. -

      - If the frame is specified by a string, the command will select the frame by - its name or ID. To select sub-frames, simply separate the frame names/IDs by - dots. As an example, "main.child" will select the frame with the name "main" - and then its child "child". -

      - If the specified frame can not be found, the deferred result will errback - with a bot.ErrorCode.NO_SUCH_FRAME error.

      Parameters
      nameOrIndex: (string|number)
      The frame locator.
      Returns
      A promise that will be resolved when the - driver has changed focus to the specified frame.

      Schedules a command to switch the focus of all future commands to another - window. Windows may be specified by their window.name attribute or - by its handle (as returned by webdriver.WebDriver#getWindowHandles). -

      - If the specificed window can not be found, the deferred result will errback - with a bot.ErrorCode.NO_SUCH_WINDOW error.

      Parameters
      nameOrHandle: string
      The name or window handle of the window to - switch focus to.
      Returns
      A promise that will be resolved when the - driver has changed focus to the specified window.

      Instance Properties

      \ No newline at end of file +WebDriver.TargetLocator

      class WebDriver.TargetLocator

      An interface for changing the focus of the driver to another frame or window.

      +

      new WebDriver.TargetLocator(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      activeElement()code »

      Schedules a command retrieve the document.activeElement element on +the current document, or document.body if activeElement is not +available.

      +
      Returns
      webdriver.WebElementPromise

      The active element.

      +

      alert()code »

      Schedules a command to change focus to the active alert dialog. This command +will return a bot.ErrorCode.NO_SUCH_ALERT error if an alert dialog +is not currently open.

      +
      Returns
      webdriver.AlertPromise

      The open alert.

      +

      defaultContent()code »

      Schedules a command to switch focus of all future commands to the first frame +on the page.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the driver has changed focus to the default content.

      +

      frame(nameOrIndex)code »

      Schedules a command to switch the focus of all future commands to another +frame on the page.

      +

      If the frame is specified by a number, the command will switch to the frame +by its (zero-based) index into +window.frames.

      +

      If the frame is specified by a string, the command will select the frame by +its name or ID. To select sub-frames, simply separate the frame names/IDs by +dots. As an example, "main.child" will select the frame with the name "main" +and then its child "child".

      +

      If the specified frame can not be found, the deferred result will errback +with a bot.ErrorCode.NO_SUCH_FRAME error.

      +
      Parameters
      nameOrIndex(string|number)

      The frame locator.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the driver has changed focus to the specified frame.

      +

      window(nameOrHandle)code »

      Schedules a command to switch the focus of all future commands to another +window. Windows may be specified by their window.name attribute or +by its handle (as returned by webdriver.WebDriver#getWindowHandles).

      +

      If the specificed window can not be found, the deferred result will errback +with a bot.ErrorCode.NO_SUCH_WINDOW error.

      +
      Parameters
      nameOrHandlestring

      The name or window handle of the window to +switch focus to.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the driver has changed focus to the specified window.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_Timeouts.html b/docs/class_webdriver_WebDriver_Timeouts.html index 39080e1..85926ca 100644 --- a/docs/class_webdriver_WebDriver_Timeouts.html +++ b/docs/class_webdriver_WebDriver_Timeouts.html @@ -1,21 +1,29 @@ -webdriver.WebDriver.Timeouts

      Class webdriver.WebDriver.Timeouts

      code »

      An interface for managing timeout behavior for WebDriver instances.

      Constructor

      webdriver.WebDriver.Timeouts ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      Specifies the amount of time the driver should wait when searching for an - element if it is not immediately present. -

      - When searching for a single element, the driver should poll the page - until the element has been found, or this timeout expires before failing - with a bot.ErrorCode.NO_SUCH_ELEMENT error. When searching - for multiple elements, the driver should poll the page until at least one - element has been found or this timeout has expired. -

      - Setting the wait timeout to 0 (its default value), disables implicit - waiting. -

      - Increasing the implicit wait timeout should be used judiciously as it - will have an adverse effect on test run time, especially when used with - slower location strategies like XPath.

      Parameters
      ms: number
      The amount of time to wait, in milliseconds.
      Returns
      A promise that will be resolved when the - implicit wait timeout has been set.

      Sets the amount of time to wait for a page load to complete before returning - an error. If the timeout is negative, page loads may be indefinite.

      Parameters
      ms: number
      The amount of time to wait, in milliseconds.
      Returns
      A promise that will be resolved when - the timeout has been set.

      Sets the amount of time to wait, in milliseconds, for an asynchronous script - to finish execution before returning an error. If the timeout is less than or - equal to 0, the script will be allowed to run indefinitely.

      Parameters
      ms: number
      The amount of time to wait, in milliseconds.
      Returns
      A promise that will be resolved when the - script timeout has been set.

      Instance Properties

      \ No newline at end of file +WebDriver.Timeouts

      class WebDriver.Timeouts

      An interface for managing timeout behavior for WebDriver instances.

      +

      new WebDriver.Timeouts(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      implicitlyWait(ms)code »

      Specifies the amount of time the driver should wait when searching for an +element if it is not immediately present.

      +

      When searching for a single element, the driver should poll the page +until the element has been found, or this timeout expires before failing +with a bot.ErrorCode.NO_SUCH_ELEMENT error. When searching +for multiple elements, the driver should poll the page until at least one +element has been found or this timeout has expired.

      +

      Setting the wait timeout to 0 (its default value), disables implicit +waiting.

      +

      Increasing the implicit wait timeout should be used judiciously as it +will have an adverse effect on test run time, especially when used with +slower location strategies like XPath.

      +
      Parameters
      msnumber

      The amount of time to wait, in milliseconds.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the implicit wait timeout has been set.

      +

      pageLoadTimeout(ms)code »

      Sets the amount of time to wait for a page load to complete before returning +an error. If the timeout is negative, page loads may be indefinite.

      +
      Parameters
      msnumber

      The amount of time to wait, in milliseconds.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the timeout has been set.

      +

      setScriptTimeout(ms)code »

      Sets the amount of time to wait, in milliseconds, for an asynchronous script +to finish execution before returning an error. If the timeout is less than or +equal to 0, the script will be allowed to run indefinitely.

      +
      Parameters
      msnumber

      The amount of time to wait, in milliseconds.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the script timeout has been set.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebDriver_Window.html b/docs/class_webdriver_WebDriver_Window.html index 87e91b6..7d85985 100644 --- a/docs/class_webdriver_WebDriver_Window.html +++ b/docs/class_webdriver_WebDriver_Window.html @@ -1,10 +1,27 @@ -webdriver.WebDriver.Window

      Class webdriver.WebDriver.Window

      code »

      An interface for managing the current window.

      Constructor

      webdriver.WebDriver.Window ( driver )
      Parameters
      driver: !webdriver.WebDriver
      The parent driver.
      Show:

      Instance Methods

      Retrieves the window's current position, relative to the top left corner of - the screen.

      Returns
      A promise that will be resolved with the - window's position in the form of a {x:number, y:number} object literal.

      Retrieves the window's current size.

      Returns
      A promise that will be resolved with the - window's size in the form of a {width:number, height:number} object - literal.

      Maximizes the current window.

      Returns
      A promise that will be resolved when the - command has completed.

      Repositions the current window.

      Parameters
      x: number
      The desired horizontal position, relative to the left side - of the screen.
      y: number
      The desired vertical position, relative to the top of the - of the screen.
      Returns
      A promise that will be resolved when the - command has completed.

      Resizes the current window.

      Parameters
      width: number
      The desired window width.
      height: number
      The desired window height.
      Returns
      A promise that will be resolved when the - command has completed.

      Instance Properties

      \ No newline at end of file +WebDriver.Window

      class WebDriver.Window

      An interface for managing the current window.

      +

      new WebDriver.Window(driver)

      Parameters
      driverwebdriver.WebDriver

      The parent driver.

      +

      Instance Methods

      getPosition()code »

      Retrieves the window's current position, relative to the top left corner of +the screen.

      +
      Returns
      webdriver.promise.Promise<{x: number, y: number}>

      A promise that +will be resolved with the window's position in the form of a +{x:number, y:number} object literal.

      +

      getSize()code »

      Retrieves the window's current size.

      +
      Returns
      webdriver.promise.Promise<{height: number, width: number}>

      A +promise that will be resolved with the window's size in the form of a +{width:number, height:number} object literal.

      +

      maximize()code »

      Maximizes the current window.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the command has completed.

      +

      setPosition(x, y)code »

      Repositions the current window.

      +
      Parameters
      xnumber

      The desired horizontal position, relative to the left side +of the screen.

      +
      ynumber

      The desired vertical position, relative to the top of the +of the screen.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the command has completed.

      +

      setSize(width, height)code »

      Resizes the current window.

      +
      Parameters
      widthnumber

      The desired window width.

      +
      heightnumber

      The desired window height.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the command has completed.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebElement.html b/docs/class_webdriver_WebElement.html index e2522cb..283d113 100644 --- a/docs/class_webdriver_WebElement.html +++ b/docs/class_webdriver_WebElement.html @@ -1,252 +1,216 @@ -webdriver.WebElement

      Class webdriver.WebElement

      code »
      webdriver.promise.Promise
      -  └ webdriver.promise.Deferred
      -      └ webdriver.WebElement

      Represents a DOM element. WebElements can be found by searching from the - document root using a webdriver.WebDriver instance, or by searching - under another webdriver.WebElement: -

      
      -   driver.get('http://www.google.com');
      -   var searchForm = driver.findElement(By.tagName('form'));
      -   var searchBox = searchForm.findElement(By.name('q'));
      -   searchBox.sendKeys('webdriver');
      - 
      +WebElement

      class WebElement

      webdriver.Serializable<{ELEMENT: string}>
      +  └ webdriver.WebElement

      Represents a DOM element. WebElements can be found by searching from the +document root using a webdriver.WebDriver instance, or by searching +under another WebElement:

      +
      driver.get('http://www.google.com');
      +var searchForm = driver.findElement(By.tagName('form'));
      +var searchBox = searchForm.findElement(By.name('q'));
      +searchBox.sendKeys('webdriver');
      +
      +

      The WebElement is implemented as a promise for compatibility with the promise +API. It will always resolve itself when its internal state has been fully +resolved and commands may be issued against the element. This can be used to +catch errors when an element cannot be located on the page:

      +
      driver.findElement(By.id('not-there')).then(function(element) {
      +  alert('Found an element that was not expected to be there!');
      +}, function(error) {
      +  alert('The element was not found, as expected');
      +});
      +
      +

      new WebElement(driver, id)

      Parameters
      driverwebdriver.WebDriver

      The parent WebDriver instance for this +element.

      +
      id(webdriver.promise.Promise<{ELEMENT: string}>|{ELEMENT: string})

      The server-assigned opaque ID for the +underlying DOM element.

      +

      Instance Methods

      clear()code »

      Schedules a command to clear the value of this element. This command +has no effect if the underlying DOM element is neither a text INPUT element +nor a TEXTAREA element.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the element has been cleared.

      +

      click()code »

      Schedules a command to click on this element.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the click command has completed.

      +

      findElement(locator)code »

      Schedule a command to find a descendant of this element. If the element +cannot be found, a bot.ErrorCode.NO_SUCH_ELEMENT result will +be returned by the driver. Unlike other commands, this error cannot be +suppressed. In other words, scheduling a command to find an element doubles +as an assert that the element is present on the page. To test whether an +element is present on the page, use #isElementPresent instead.

      +

      The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

      +
      var e1 = element.findElement(By.id('foo'));
      +var e2 = element.findElement({id:'foo'});
      +
      +

      You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

      +
      var link = element.findElement(firstVisibleLink);
       
      - The WebElement is implemented as a promise for compatibility with the promise
      - API. It will always resolve itself when its internal state has been fully
      - resolved and commands may be issued against the element. This can be used to
      - catch errors when an element cannot be located on the page:
      - 
      
      -   driver.findElement(By.id('not-there')).then(function(element) {
      -     alert('Found an element that was not expected to be there!');
      -   }, function(error) {
      -     alert('The element was not found, as expected');
      -   });
      - 

      Constructor

      webdriver.WebElement ( driver, id )
      Parameters
      driver: !webdriver.WebDriver
      The parent WebDriver instance for this - element.
      id: !(string|webdriver.promise.Promise)
      Either the opaque ID for the - underlying DOM element assigned by the server, or a promise that will - resolve to that ID or another WebElement.
      Show:

      Instance Methods

      Defined in webdriver.WebElement

      Schedules a command to clear the value of this element. This command - has no effect if the underlying DOM element is neither a text INPUT element - nor a TEXTAREA element.

      Returns
      A promise that will be resolved when - the element has been cleared.

      Schedules a command to click on this element.

      Returns
      A promise that will be resolved when - the click command has completed.

      Schedule a command to find a descendant of this element. If the element - cannot be found, a bot.ErrorCode.NO_SUCH_ELEMENT result will - be returned by the driver. Unlike other commands, this error cannot be - suppressed. In other words, scheduling a command to find an element doubles - as an assert that the element is present on the page. To test whether an - element is present on the page, use #isElementPresent instead. - -

      The search criteria for an element may be defined using one of the - factories in the webdriver.By namespace, or as a short-hand - webdriver.By.Hash object. For example, the following two statements - are equivalent: -

      - var e1 = element.findElement(By.id('foo'));
      - var e2 = element.findElement({id:'foo'});
      - 
      - -

      You may also provide a custom locator function, which takes as input - this WebDriver instance and returns a webdriver.WebElement, or a - promise that will resolve to a WebElement. For example, to find the first - visible link on a page, you could write: -

      - var link = element.findElement(firstVisibleLink);
      -
      - function firstVisibleLink(element) {
      -   var links = element.findElements(By.tagName('a'));
      -   return webdriver.promise.filter(links, function(link) {
      -     return links.isDisplayed();
      -   }).then(function(visibleLinks) {
      -     return visibleLinks[0];
      -   });
      - }
      - 
      Parameters
      locator: !(webdriver.Locator|webdriver.By.Hash|Function)
      The - locator strategy to use when searching for the element.
      Returns
      A WebElement that can be used to issue - commands against the located element. If the element is not found, the - element will be invalidated and all scheduled commands aborted.

      Schedules a command to find all of the descendants of this element that - match the given search criteria.

      Parameters
      locator: !(webdriver.Locator|webdriver.By.Hash|Function)
      The - locator strategy to use when searching for the elements.
      Returns
      A - promise that will resolve to an array of WebElements.

      Schedules a command to query for the value of the given attribute of the - element. Will return the current value, even if it has been modified after - the page has been loaded. More exactly, this method will return the value of - the given attribute, unless that attribute is not present, in which case the - value of the property with the same name is returned. If neither value is - set, null is returned (for example, the "value" property of a textarea - element). The "style" attribute is converted as best can be to a - text representation with a trailing semi-colon. The following are deemed to - be "boolean" attributes and will return either "true" or null: - -

      async, autofocus, autoplay, checked, compact, complete, controls, declare, - defaultchecked, defaultselected, defer, disabled, draggable, ended, - formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, - loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, - paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, - selected, spellcheck, truespeed, willvalidate - -

      Finally, the following commonly mis-capitalized attribute/property names - are evaluated as expected: -

        -
      • "class" -
      • "readonly" -
      Parameters
      attributeName: string
      The name of the attribute to query.
      Returns
      A promise that will be resolved with the - attribute's value. The returned value will always be either a string or - null.

      Schedules a command to query for the computed style of the element - represented by this instance. If the element inherits the named style from - its parent, the parent will be queried for its value. Where possible, color - values will be converted to their hex representation (e.g. #00ff00 instead of - rgb(0, 255, 0)). -

      - Warning: the value returned will be as the browser interprets it, so - it may be tricky to form a proper assertion.

      Parameters
      cssStyleProperty: string
      The name of the CSS style property to look - up.
      Returns
      A promise that will be resolved with the - requested CSS value.
      Returns
      The parent driver for this instance.

      Schedules a command to retrieve the inner HTML of this element.

      Returns
      A promise that will be resolved with the - element's inner HTML.

      Schedules a command to compute the location of this element in page space.

      Returns
      A promise that will be resolved to the - element's location as a {x:number, y:number} object.

      Schedules a command to retrieve the outer HTML of this element.

      Returns
      A promise that will be resolved with - the element's outer HTML.

      Schedules a command to compute the size of this element's bounding box, in - pixels.

      Returns
      A promise that will be resolved with the - element's size as a {width:number, height:number} object.

      Schedules a command to query for the tag/node name of this element.

      Returns
      A promise that will be resolved with the - element's tag name.

      Get the visible (i.e. not hidden by CSS) innerText of this element, including - sub-elements, without any leading or trailing whitespace.

      Returns
      A promise that will be resolved with the - element's visible text.

      Schedules a command to test whether this element is currently displayed.

      Returns
      A promise that will be resolved with - whether this element is currently visible on the page.

      Schedules a command to test if there is at least one descendant of this - element that matches the given search criteria.

      Parameters
      locator: !(webdriver.Locator|webdriver.By.Hash|Function)
      The - locator strategy to use when searching for the element.
      Returns
      A promise that will be - resolved with whether an element could be located on the page.

      Schedules a command to query whether the DOM element represented by this - instance is enabled, as dicted by the disabled attribute.

      Returns
      A promise that will be resolved with - whether this element is currently enabled.

      Schedules a command to query whether this element is selected.

      Returns
      A promise that will be resolved with - whether this element is currently selected.
      code »schedule_ ( command, description )!webdriver.promise.Promise

      Schedules a command that targets this element with the parent WebDriver - instance. Will ensure this element's ID is included in the command parameters - under the "id" key.

      Parameters
      command: !webdriver.Command
      The command to schedule.
      description: string
      A description of the command for debugging.
      Returns
      A promise that will be resolved with - the command result.

      Schedules a command to type a sequence on the DOM element represented by this - instance. -

      - Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is - processed in the keysequence, that key state is toggled until one of the - following occurs: -

        -
      • The modifier key is encountered again in the sequence. At this point the - state of the key is toggled (along with the appropriate keyup/down events). -
      • -
      • The webdriver.Key.NULL key is encountered in the sequence. When - this key is encountered, all modifier keys current in the down state are - released (with accompanying keyup events). The NULL key can be used to - simulate common keyboard shortcuts: -
        -     element.sendKeys("text was",
        -                      webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
        -                      "now text is");
        -     // Alternatively:
        -     element.sendKeys("text was",
        -                      webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
        -                      "now text is");
        - 
      • -
      • The end of the keysequence is encountered. When there are no more keys - to type, all depressed modifier keys are released (with accompanying keyup - events). -
      • -
      - Note: On browsers where native keyboard events are not yet - supported (e.g. Firefox on OS X), key events will be synthesized. Special - punctionation keys will be synthesized according to a standard QWERTY en-us - keyboard layout.
      Parameters
      var_args: ...string
      The sequence of keys to - type. All arguments will be joined into a single sequence (var_args is - permitted for convenience).
      Returns
      A promise that will be resolved when all - keys have been typed.

      Schedules a command to submit the form containing this element (or this - element if it is a FORM element). This command is a no-op if the element is - not contained in a form.

      Returns
      A promise that will be resolved when - the form has been submitted.
      Returns
      A promise that resolves to this - element's JSON representation as defined by the WebDriver wire protocol.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a - promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.WebElement

      The parent WebDriver instance for this element.

      A promise that resolves to the JSON representation of this WebElement's - ID, as defined by the WebDriver wire protocol.

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be - in one of three states: pending, resolved, or rejected. Each promise starts - in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Functions

      Compares to WebElements for equality.

      Parameters
      a: !webdriver.WebElement
      A WebElement.
      b: !webdriver.WebElement
      A WebElement.
      Returns
      A promise that will be resolved to - whether the two WebElements are equal.

      Static Properties

      The property key used in the wire protocol to indicate that a JSON object - contains the ID of a WebElement.

      \ No newline at end of file +function firstVisibleLink(element) { + var links = element.findElements(By.tagName('a')); + return webdriver.promise.filter(links, function(link) { + return links.isDisplayed(); + }).then(function(visibleLinks) { + return visibleLinks[0]; + }); +} + +
      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the element.

      +
      Returns
      webdriver.WebElement

      A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

      +

      findElements(locator)code »

      Schedules a command to find all of the descendants of this element that +match the given search criteria.

      +
      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the elements.

      +
      Returns
      webdriver.promise.Promise<Array<webdriver.WebElement>>

      A +promise that will resolve to an array of WebElements.

      +

      getAttribute(attributeName)code »

      Schedules a command to query for the value of the given attribute of the +element. Will return the current value, even if it has been modified after +the page has been loaded. More exactly, this method will return the value of +the given attribute, unless that attribute is not present, in which case the +value of the property with the same name is returned. If neither value is +set, null is returned (for example, the "value" property of a textarea +element). The "style" attribute is converted as best can be to a +text representation with a trailing semi-colon. The following are deemed to +be "boolean" attributes and will return either "true" or null:

      +

      async, autofocus, autoplay, checked, compact, complete, controls, declare, +defaultchecked, defaultselected, defer, disabled, draggable, ended, +formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, +loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, +paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, +selected, spellcheck, truespeed, willvalidate

      +

      Finally, the following commonly mis-capitalized attribute/property names +are evaluated as expected:

      +
      • "class"
      • "readonly"
      +
      Parameters
      attributeNamestring

      The name of the attribute to query.

      +
      Returns
      webdriver.promise.Promise<?string>

      A promise that will be +resolved with the attribute's value. The returned value will always be +either a string or null.

      +

      getCssValue(cssStyleProperty)code »

      Schedules a command to query for the computed style of the element +represented by this instance. If the element inherits the named style from +its parent, the parent will be queried for its value. Where possible, color +values will be converted to their hex representation (e.g. #00ff00 instead of +rgb(0, 255, 0)).

      +

      Warning: the value returned will be as the browser interprets it, so +it may be tricky to form a proper assertion.

      +
      Parameters
      cssStylePropertystring

      The name of the CSS style property to look +up.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the requested CSS value.

      +

      getDriver()code »

      Returns
      webdriver.WebDriver

      The parent driver for this instance.

      +

      getId()code »

      Returns
      webdriver.promise.Promise<{ELEMENT: string}>

      A promise +that resolves to this element's JSON representation as defined by the +WebDriver wire protocol.

      +

      getInnerHtml()code »

      Schedules a command to retrieve the inner HTML of this element.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's inner HTML.

      +

      getLocation()code »

      Schedules a command to compute the location of this element in page space.

      +
      Returns
      webdriver.promise.Promise<{x: number, y: number}>

      A promise that +will be resolved to the element's location as a +{x:number, y:number} object.

      +

      getOuterHtml()code »

      Schedules a command to retrieve the outer HTML of this element.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's outer HTML.

      +

      getRawId()code »

      Returns the raw ID string ID for this element.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that resolves to this +element's raw ID as a string value.

      +

      getSize()code »

      Schedules a command to compute the size of this element's bounding box, in +pixels.

      +
      Returns
      webdriver.promise.Promise<{height: number, width: number}>

      A +promise that will be resolved with the element's size as a +{width:number, height:number} object.

      +

      getTagName()code »

      Schedules a command to query for the tag/node name of this element.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's tag name.

      +

      getText()code »

      Get the visible (i.e. not hidden by CSS) innerText of this element, including +sub-elements, without any leading or trailing whitespace.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's visible text.

      +

      isDisplayed()code »

      Schedules a command to test whether this element is currently displayed.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently visible on the page.

      +

      isElementPresent(locator)code »

      Schedules a command to test if there is at least one descendant of this +element that matches the given search criteria.

      +
      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the element.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether an element could be located on the page.

      +

      isEnabled()code »

      Schedules a command to query whether the DOM element represented by this +instance is enabled, as dicted by the disabled attribute.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently enabled.

      +

      isSelected()code »

      Schedules a command to query whether this element is selected.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently selected.

      +

      sendKeys(var_args)code »

      Schedules a command to type a sequence on the DOM element represented by this +instance.

      +

      Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is +processed in the keysequence, that key state is toggled until one of the +following occurs:

      +
      • +

        The modifier key is encountered again in the sequence. At this point the +state of the key is toggled (along with the appropriate keyup/down events).

        +
      • +

        The webdriver.Key.NULL key is encountered in the sequence. When +this key is encountered, all modifier keys current in the down state are +released (with accompanying keyup events). The NULL key can be used to +simulate common keyboard shortcuts:

        +
          element.sendKeys("text was",
        +                   webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
        +                   "now text is");
        +  // Alternatively:
        +  element.sendKeys("text was",
        +                   webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
        +                   "now text is");
        +
        +
      • +

        The end of the keysequence is encountered. When there are no more keys +to type, all depressed modifier keys are released (with accompanying keyup +events).

        +
      +

      If this element is a file input (<input type="file">), the +specified key sequence should specify the path to the file to attach to +the element. This is analgous to the user clicking "Browse..." and entering +the path into the file select dialog.

      +
      var form = driver.findElement(By.css('form'));
      +var element = form.findElement(By.css('input[type=file]'));
      +element.sendKeys('/path/to/file.txt');
      +form.submit();
      +
      +

      For uploads to function correctly, the entered path must reference a file +on the browser's machine, not the local machine running this script. When +running against a remote Selenium server, a webdriver.FileDetector +may be used to transparently copy files to the remote machine before +attempting to upload them in the browser.

      +

      Note: On browsers where native keyboard events are not supported +(e.g. Firefox on OS X), key events will be synthesized. Special +punctionation keys will be synthesized according to a standard QWERTY en-us +keyboard layout.

      +
      Parameters
      var_args...(string|webdriver.promise.Promise<string>)

      The sequence +of keys to type. All arguments will be joined into a single sequence.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when all keys have been typed.

      +

      serialize()code »

      Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

      +

      Overrides: webdriver.Serializable

      Returns
      (webdriver.WebElement.Id|IThenable<webdriver.WebElement.Id>)

      This instance's serialized wire format.

      +

      submit()code »

      Schedules a command to submit the form containing this element (or this +element if it is a FORM element). This command is a no-op if the element is +not contained in a form.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the form has been submitted.

      +

      Static Functions

      WebElement.equals(a, b)code »

      Compares to WebElements for equality.

      +
      Parameters
      awebdriver.WebElement

      A WebElement.

      +
      bwebdriver.WebElement

      A WebElement.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved to whether the two WebElements are equal.

      +

      Static Properties

      WebElement.ELEMENT_KEYstring

      The property key used in the wire protocol to indicate that a JSON object +contains the ID of a WebElement.

      +

      Type Definitions

      WebElement.Id{ELEMENT: string}

      Wire protocol definition of a WebElement ID.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_WebElementPromise.html b/docs/class_webdriver_WebElementPromise.html new file mode 100644 index 0000000..136de10 --- /dev/null +++ b/docs/class_webdriver_WebElementPromise.html @@ -0,0 +1,266 @@ +WebElementPromise

      class WebElementPromise

      final
      webdriver.Serializable<{ELEMENT: string}>
      +  └ webdriver.WebElement
      +      └ webdriver.WebElementPromise
      All implemented interfaces
      IThenable<T>
      webdriver.promise.Thenable<webdriver.WebElement>

      WebElementPromise is a promise that will be fulfilled with a WebElement. +This serves as a forward proxy on WebElement, allowing calls to be +scheduled without directly on this instance before the underlying +WebElement has been fulfilled. In other words, the following two statements +are equivalent:

      +
      driver.findElement({id: 'my-button'}).click();
      +driver.findElement({id: 'my-button'}).then(function(el) {
      +  return el.click();
      +});
      +
      +

      new WebElementPromise(driver, el)

      Parameters
      driverwebdriver.WebDriver

      The parent WebDriver instance for this +element.

      +
      elwebdriver.promise.Promise<webdriver.WebElement>

      A promise +that will resolve to the promised element.

      +

      Instance Methods

      cancel(opt_reason)code »

      Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

      +

      Specified by: webdriver.promise.Thenable

      Parameters
      opt_reason?(string|webdriver.promise.CancellationError)=

      The reason this +promise is being cancelled.

      +

      clear()code »

      Schedules a command to clear the value of this element. This command +has no effect if the underlying DOM element is neither a text INPUT element +nor a TEXTAREA element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the element has been cleared.

      +

      click()code »

      Schedules a command to click on this element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the click command has completed.

      +

      findElement(locator)code »

      Schedule a command to find a descendant of this element. If the element +cannot be found, a bot.ErrorCode.NO_SUCH_ELEMENT result will +be returned by the driver. Unlike other commands, this error cannot be +suppressed. In other words, scheduling a command to find an element doubles +as an assert that the element is present on the page. To test whether an +element is present on the page, use #isElementPresent instead.

      +

      The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

      +
      var e1 = element.findElement(By.id('foo'));
      +var e2 = element.findElement({id:'foo'});
      +
      +

      You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

      +
      var link = element.findElement(firstVisibleLink);
      +
      +function firstVisibleLink(element) {
      +  var links = element.findElements(By.tagName('a'));
      +  return webdriver.promise.filter(links, function(link) {
      +    return links.isDisplayed();
      +  }).then(function(visibleLinks) {
      +    return visibleLinks[0];
      +  });
      +}
      +
      +

      Defined by: webdriver.WebElement

      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the element.

      +
      Returns
      webdriver.WebElement

      A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

      +

      findElements(locator)code »

      Schedules a command to find all of the descendants of this element that +match the given search criteria.

      +

      Defined by: webdriver.WebElement

      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the elements.

      +
      Returns
      webdriver.promise.Promise<Array<webdriver.WebElement>>

      A +promise that will resolve to an array of WebElements.

      +

      getAttribute(attributeName)code »

      Schedules a command to query for the value of the given attribute of the +element. Will return the current value, even if it has been modified after +the page has been loaded. More exactly, this method will return the value of +the given attribute, unless that attribute is not present, in which case the +value of the property with the same name is returned. If neither value is +set, null is returned (for example, the "value" property of a textarea +element). The "style" attribute is converted as best can be to a +text representation with a trailing semi-colon. The following are deemed to +be "boolean" attributes and will return either "true" or null:

      +

      async, autofocus, autoplay, checked, compact, complete, controls, declare, +defaultchecked, defaultselected, defer, disabled, draggable, ended, +formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, +loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, +paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, +selected, spellcheck, truespeed, willvalidate

      +

      Finally, the following commonly mis-capitalized attribute/property names +are evaluated as expected:

      +
      • "class"
      • "readonly"
      +

      Defined by: webdriver.WebElement

      Parameters
      attributeNamestring

      The name of the attribute to query.

      +
      Returns
      webdriver.promise.Promise<?string>

      A promise that will be +resolved with the attribute's value. The returned value will always be +either a string or null.

      +

      getCssValue(cssStyleProperty)code »

      Schedules a command to query for the computed style of the element +represented by this instance. If the element inherits the named style from +its parent, the parent will be queried for its value. Where possible, color +values will be converted to their hex representation (e.g. #00ff00 instead of +rgb(0, 255, 0)).

      +

      Warning: the value returned will be as the browser interprets it, so +it may be tricky to form a proper assertion.

      +

      Defined by: webdriver.WebElement

      Parameters
      cssStylePropertystring

      The name of the CSS style property to look +up.

      +
      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the requested CSS value.

      +

      getDriver()code »

      Defined by: webdriver.WebElement

      Returns
      webdriver.WebDriver

      The parent driver for this instance.

      +

      getId()code »

      Defers returning the element ID until the wrapped WebElement has been +resolved.

      +

      Overrides: webdriver.WebElement

      Returns
      webdriver.promise.Promise<{ELEMENT: string}>

      A promise +that resolves to this element's JSON representation as defined by the +WebDriver wire protocol.

      +

      getInnerHtml()code »

      Schedules a command to retrieve the inner HTML of this element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's inner HTML.

      +

      getLocation()code »

      Schedules a command to compute the location of this element in page space.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<{x: number, y: number}>

      A promise that +will be resolved to the element's location as a +{x:number, y:number} object.

      +

      getOuterHtml()code »

      Schedules a command to retrieve the outer HTML of this element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's outer HTML.

      +

      getRawId()code »

      Returns the raw ID string ID for this element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<string>

      A promise that resolves to this +element's raw ID as a string value.

      +

      getSize()code »

      Schedules a command to compute the size of this element's bounding box, in +pixels.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<{height: number, width: number}>

      A +promise that will be resolved with the element's size as a +{width:number, height:number} object.

      +

      getTagName()code »

      Schedules a command to query for the tag/node name of this element.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's tag name.

      +

      getText()code »

      Get the visible (i.e. not hidden by CSS) innerText of this element, including +sub-elements, without any leading or trailing whitespace.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<string>

      A promise that will be +resolved with the element's visible text.

      +

      isDisplayed()code »

      Schedules a command to test whether this element is currently displayed.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently visible on the page.

      +

      isElementPresent(locator)code »

      Schedules a command to test if there is at least one descendant of this +element that matches the given search criteria.

      +

      Defined by: webdriver.WebElement

      Parameters
      locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

      The +locator strategy to use when searching for the element.

      +
      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether an element could be located on the page.

      +

      isEnabled()code »

      Schedules a command to query whether the DOM element represented by this +instance is enabled, as dicted by the disabled attribute.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently enabled.

      +

      isPending()code »

      Specified by: webdriver.promise.Thenable

      Returns
      boolean

      Whether this promise's value is still being computed.

      +

      isSelected()code »

      Schedules a command to query whether this element is selected.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<boolean>

      A promise that will be +resolved with whether this element is currently selected.

      +

      sendKeys(var_args)code »

      Schedules a command to type a sequence on the DOM element represented by this +instance.

      +

      Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is +processed in the keysequence, that key state is toggled until one of the +following occurs:

      +
      • +

        The modifier key is encountered again in the sequence. At this point the +state of the key is toggled (along with the appropriate keyup/down events).

        +
      • +

        The webdriver.Key.NULL key is encountered in the sequence. When +this key is encountered, all modifier keys current in the down state are +released (with accompanying keyup events). The NULL key can be used to +simulate common keyboard shortcuts:

        +
          element.sendKeys("text was",
        +                   webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
        +                   "now text is");
        +  // Alternatively:
        +  element.sendKeys("text was",
        +                   webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
        +                   "now text is");
        +
        +
      • +

        The end of the keysequence is encountered. When there are no more keys +to type, all depressed modifier keys are released (with accompanying keyup +events).

        +
      +

      If this element is a file input (<input type="file">), the +specified key sequence should specify the path to the file to attach to +the element. This is analgous to the user clicking "Browse..." and entering +the path into the file select dialog.

      +
      var form = driver.findElement(By.css('form'));
      +var element = form.findElement(By.css('input[type=file]'));
      +element.sendKeys('/path/to/file.txt');
      +form.submit();
      +
      +

      For uploads to function correctly, the entered path must reference a file +on the browser's machine, not the local machine running this script. When +running against a remote Selenium server, a webdriver.FileDetector +may be used to transparently copy files to the remote machine before +attempting to upload them in the browser.

      +

      Note: On browsers where native keyboard events are not supported +(e.g. Firefox on OS X), key events will be synthesized. Special +punctionation keys will be synthesized according to a standard QWERTY en-us +keyboard layout.

      +

      Defined by: webdriver.WebElement

      Parameters
      var_args...(string|webdriver.promise.Promise<string>)

      The sequence +of keys to type. All arguments will be joined into a single sequence.

      +
      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when all keys have been typed.

      +

      serialize()code »

      Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

      +

      Defined by: webdriver.WebElement
      Overrides: webdriver.Serializable

      Returns
      (webdriver.WebElement.Id|IThenable<webdriver.WebElement.Id>)

      This instance's serialized wire format.

      +

      submit()code »

      Schedules a command to submit the form containing this element (or this +element if it is a FORM element). This command is a no-op if the element is +not contained in a form.

      +

      Defined by: webdriver.WebElement

      Returns
      webdriver.promise.Promise<undefined>

      A promise that will be resolved +when the form has been submitted.

      +

      then(opt_callback, opt_errback)code »

      Registers listeners for when this instance is resolved.

      +

      Specified by: webdriver.promise.Thenable, IThenable

      Parameters
      opt_callback?function(T): (R|IThenable<R>)=

      The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

      +
      opt_errback?function(*): (R|IThenable<R>)=

      The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenCatch(errback)code »

      Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} catch (ex) {
      +  console.error(ex);
      +}
      +
      +// Asynchronous promise API:
      +doAsynchronousWork().thenCatch(function(ex) {
      +  console.error(ex);
      +});
      +
      +

      Specified by: webdriver.promise.Thenable

      Parameters
      errbackfunction(*): (R|IThenable<R>)

      The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenFinally(callback)code »

      Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} finally {
      +  cleanUp();
      +}
      +
      +// Asynchronous promise API:
      +doAsynchronousWork().thenFinally(cleanUp);
      +
      +

      Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

      +
      try {
      +  throw Error('one');
      +} finally {
      +  throw Error('two');  // Hides Error: one
      +}
      +
      +promise.rejected(Error('one'))
      +    .thenFinally(function() {
      +      throw Error('two');  // Hides Error: one
      +    });
      +
      +

      Specified by: webdriver.promise.Thenable

      Parameters
      callbackfunction(): (R|IThenable<R>)

      The function +to call when this promise is resolved.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be fulfilled +with the callback result.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_http_CorsClient.html b/docs/class_webdriver_http_CorsClient.html index 7a7b1f1..cbffd87 100644 --- a/docs/class_webdriver_http_CorsClient.html +++ b/docs/class_webdriver_http_CorsClient.html @@ -30,4 +30,4 @@ onerror handler, but without the corresponding response text returned by the server. This renders IE and Opera incapable of handling command failures in the standard JSON protocol. -

      Constructor

      webdriver.http.CorsClient ( url )
      Parameters
      url: string
      URL for the WebDriver server to send commands to.
      Show:

      Instance Methods

      code »send ( request, callback )
      Parameters
      request
      callback

      Instance Properties

      Static Functions

      Tests whether the current environment supports cross-origin resource sharing.

      Returns
      Whether cross-origin resource sharing is supported.

      Static Properties

      Resource URL to send commands to on the server.

      \ No newline at end of file +

      Constructor

      webdriver.http.CorsClient ( url )
      Parameters
      url: string
      URL for the WebDriver server to send commands to.
      Show:

      Instance Methods

      code »send ( request, callback )
      Parameters
      request
      callback

      Instance Properties

      Static Functions

      Tests whether the current environment supports cross-origin resource sharing.

      Returns
      Whether cross-origin resource sharing is supported.

      Static Properties

      Resource URL to send commands to on the server.

      \ No newline at end of file diff --git a/docs/class_webdriver_http_Executor.html b/docs/class_webdriver_http_Executor.html index 1e656a3..09b05c9 100644 --- a/docs/class_webdriver_http_Executor.html +++ b/docs/class_webdriver_http_Executor.html @@ -1,8 +1,22 @@ -webdriver.http.Executor

      Class webdriver.http.Executor

      code »
      All implemented interfaces:
      webdriver.CommandExecutor

      A command executor that communicates with a server using the WebDriver - command protocol.

      Constructor

      webdriver.http.Executor ( client )
      Parameters
      client: !webdriver.http.Client
      The client to use when sending - requests to the server.
      Show:

      Instance Methods

      code »execute ( command, callback )
      Parameters
      command
      callback

      Instance Properties

      Client used to communicate with the server.

      Static Functions

      Builds a fully qualified path using the given set of command parameters. Each - path segment prefixed with ':' will be replaced by the value of the - corresponding parameter. All parameters spliced into the path will be - removed from the parameter map.

      Parameters
      path: string
      The original resource path.
      parameters: !Object
      The parameters object to splice into - the path.
      Returns
      The modified path.

      Callback used to parse webdriver.http.Response objects from a - webdriver.http.Client.

      Parameters
      httpResponse: !webdriver.http.Response
      The HTTP response to parse.
      Returns
      The parsed response.

      Static Properties

      Maps command names to resource locator.

      \ No newline at end of file +Executor

      class Executor

      All implemented interfaces
      webdriver.CommandExecutor

      A command executor that communicates with a server using the WebDriver +command protocol.

      +

      new Executor(client)

      Parameters
      clientwebdriver.http.Client

      The client to use when sending +requests to the server.

      +

      Instance Methods

      defineCommand(name, method, path)code »

      Defines a new command for use with this executor. When a command is sent, +the path will be preprocessed using the command's parameters; any +path segments prefixed with ":" will be replaced by the parameter of the +same name. For example, given "/person/:name" and the parameters +"{name: 'Bob'}", the final command path will be "/person/Bob".

      +
      Parameters
      namestring

      The command name.

      +
      methodstring

      The HTTP method to use when sending this command.

      +
      pathstring

      The path to send the command to, relative to +the WebDriver server's command root and of the form +"/path/:variable/segment".

      +

      execute(command, callback)code »

      Executes the given command. If there is an error executing the +command, the provided callback will be invoked with the offending error. +Otherwise, the callback will be invoked with a null Error and non-null +bot.response.ResponseObject object.

      +

      Specified by: webdriver.CommandExecutor

      Parameters
      commandwebdriver.Command

      The command to execute.

      +
      callbackfunction(Error, {status: number, value: *}=): ?

      the function +to invoke when the command response is ready.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_http_Request.html b/docs/class_webdriver_http_Request.html index 2c4efb5..8f6392f 100644 --- a/docs/class_webdriver_http_Request.html +++ b/docs/class_webdriver_http_Request.html @@ -1,4 +1,12 @@ -webdriver.http.Request

      Class webdriver.http.Request

      code »

      Describes a partial HTTP request. This class is a "partial" request and only - defines the path on the server to send a request to. It is each - webdriver.http.Client's responsibility to build the full URL for the - final request.

      Constructor

      webdriver.http.Request ( method, path, opt_data )
      Parameters
      method: string
      The HTTP method to use for the request.
      path: string
      Path on the server to send the request to.
      opt_data: Object=
      This request's JSON data.
      Show:

      Instance Methods

      code »toString ( )string

      Instance Properties

      This request's body.

      The headers to send with the request.

      The HTTP method to use for the request.

      The path on the server to send the request to.

      \ No newline at end of file +Request

      class Request

      Describes a partial HTTP request. This class is a "partial" request and only +defines the path on the server to send a request to. It is each +webdriver.http.Client's responsibility to build the full URL for the +final request.

      +

      new Request(method, path, opt_data)

      Parameters
      methodstring

      The HTTP method to use for the request.

      +
      pathstring

      Path on the server to send the request to.

      +
      opt_data?Object=

      This request's JSON data.

      +

      Instance Methods

      toString()code »

      Returns
      string

      Instance Properties

      dataObject

      This request's body.

      +
      headersObject<?, (string|number)>

      The headers to send with the request.

      +
      methodstring

      The HTTP method to use for the request.

      +
      pathstring

      The path on the server to send the request to.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_http_Response.html b/docs/class_webdriver_http_Response.html index c39d270..785057a 100644 --- a/docs/class_webdriver_http_Response.html +++ b/docs/class_webdriver_http_Response.html @@ -1,3 +1,13 @@ -webdriver.http.Response

      Class webdriver.http.Response

      code »

      Represents a HTTP response.

      Constructor

      webdriver.http.Response ( status, headers, body )
      Parameters
      status: number
      The response code.
      headers: !Object.<string>
      The response headers. All header - names will be converted to lowercase strings for consistent lookups.
      body: string
      The response body.
      Show:

      Instance Methods

      code »toString ( )string

      Instance Properties

      The response body.

      The response body.

      The HTTP response code.

      Static Functions

      Builds a webdriver.http.Response from a XMLHttpRequest or - XDomainRequest response object.

      Parameters
      xhr: !(XDomainRequest|XMLHttpRequest)
      The request to parse.
      Returns
      The parsed response.
      \ No newline at end of file +Response

      class Response

      Represents a HTTP response.

      +

      new Response(status, headers, body)

      Parameters
      statusnumber

      The response code.

      +
      headersObject<?, string>

      The response headers. All header +names will be converted to lowercase strings for consistent lookups.

      +
      bodystring

      The response body.

      +

      Instance Methods

      toString()code »

      Returns
      string

      Instance Properties

      bodystring

      The response body.

      +
      headersObject<?, string>

      The response body.

      +
      statusnumber

      The HTTP response code.

      +

      Static Functions

      Response.fromXmlHttpRequest(xhr)code »

      Builds a webdriver.http.Response from a XMLHttpRequest or +XDomainRequest response object.

      +
      Parameters
      xhr(XDomainRequest|XMLHttpRequest)

      The request to parse.

      +
      Returns
      webdriver.http.Response

      The parsed response.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_http_XhrClient.html b/docs/class_webdriver_http_XhrClient.html index d53dfaa..1ed338f 100644 --- a/docs/class_webdriver_http_XhrClient.html +++ b/docs/class_webdriver_http_XhrClient.html @@ -1 +1 @@ -webdriver.http.XhrClient

      Class webdriver.http.XhrClient

      code »
      All implemented interfaces:
      webdriver.http.Client

      A HTTP client that sends requests using XMLHttpRequests.

      Constructor

      webdriver.http.XhrClient ( url )
      Parameters
      url: string
      URL for the WebDriver server to send commands to.
      Show:

      Instance Methods

      code »send ( request, callback )
      Parameters
      request
      callback

      Instance Properties

      \ No newline at end of file +webdriver.http.XhrClient

      Class webdriver.http.XhrClient

      code »
      All implemented interfaces:
      webdriver.http.Client

      A HTTP client that sends requests using XMLHttpRequests.

      Constructor

      webdriver.http.XhrClient ( url )
      Parameters
      url: string
      URL for the WebDriver server to send commands to.
      Show:

      Instance Methods

      code »send ( request, callback )
      Parameters
      request
      callback

      Instance Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_logging_Entry.html b/docs/class_webdriver_logging_Entry.html index c6f0822..acb8ad9 100644 --- a/docs/class_webdriver_logging_Entry.html +++ b/docs/class_webdriver_logging_Entry.html @@ -1,4 +1,15 @@ -webdriver.logging.Entry

      Class webdriver.logging.Entry

      code »

      A single log entry.

      Constructor

      webdriver.logging.Entry ( level, message, opt_timestamp, opt_type )
      Parameters
      level: (!webdriver.logging.Level|string)
      The entry level.
      message: string
      The log message.
      opt_timestamp: number=
      The time this entry was generated, in - milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the - current time will be used.
      opt_type: string=
      The log type, if known.
      Show:

      Instance Methods

      code »toJSON ( ){level: string, message: string, timestamp: number, type: string}
      Returns
      The JSON representation of this entry.

      Instance Properties

      Static Functions

      Converts a goog.debug.LogRecord into a - webdriver.logging.Entry.

      Parameters
      logRecord: !goog.debug.LogRecord
      The record to convert.
      opt_type: string=
      The log type.
      Returns
      The converted entry.
      \ No newline at end of file +Entry

      class Entry

      A single log entry recorded by a WebDriver component, such as a remote +WebDriver server.

      +

      new Entry(level, message, opt_timestamp, opt_type)

      Parameters
      level(webdriver.logging.Level|string)

      The entry level.

      +
      messagestring

      The log message.

      +
      opt_timestampnumber=

      The time this entry was generated, in +milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the +current time will be used.

      +
      opt_typestring=

      The log type, if known.

      +

      Instance Methods

      toJSON(arg0)code »

      Parameters
      arg0string=
      Returns
      {level: string, message: string, timestamp: number, type: string}

      The JSON representation of this entry.

      +

      Instance Properties

      messagestring
      No description.
      timestampnumber
      No description.
      typestring
      No description.

      Static Functions

      Entry.fromClosureLogRecord(logRecord, opt_type)code »

      Converts a goog.debug.LogRecord into a +webdriver.logging.Entry.

      +
      Parameters
      logRecordwebdriver.logging.LogRecord

      The record to convert.

      +
      opt_typestring=

      The log type.

      +
      Returns
      webdriver.logging.Entry

      The converted entry.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_logging_Level.html b/docs/class_webdriver_logging_Level.html new file mode 100644 index 0000000..1671f7e --- /dev/null +++ b/docs/class_webdriver_logging_Level.html @@ -0,0 +1,55 @@ +Level

      class Level

      The Level class defines a set of standard logging levels that +can be used to control logging output. The logging Level objects +are ordered and are specified by ordered integers. Enabling logging +at a given level also enables logging at all higher levels.

      +

      +Clients should normally use the predefined Level constants such +as Level.SEVERE. +

      +The levels in descending order are: +

      • SEVERE (highest value) +
      • WARNING +
      • INFO +
      • CONFIG +
      • FINE +
      • FINER +
      • FINEST (lowest value) +
      +In addition there is a level OFF that can be used to turn +off logging, and a level ALL that can be used to enable +logging of all messages. +

      new Level(name, value)

      Parameters
      namestring

      The name of the level.

      +
      valuenumber

      The numeric value of the level.

      +

      Instance Methods

      toString()code »

      Returns
      string

      String representation of the logger level.

      +

      Instance Properties

      namestring

      The name of the level

      +
      valuenumber

      The numeric value of the level

      +

      Static Functions

      Level.getPredefinedLevel(name)code »

      Gets the predefined level with the given name.

      +
      Parameters
      namestring

      The name of the level.

      +
      Returns
      webdriver.logging.Level

      The level, or null if none found.

      +

      Level.getPredefinedLevelByValue(value)code »

      Gets the highest predefined level <= #value.

      +
      Parameters
      valuenumber

      Level value.

      +
      Returns
      webdriver.logging.Level

      The level, or null if none found.

      +

      Static Properties

      Level.ALLwebdriver.logging.Level

      ALL indicates that all messages should be logged. +This level is initialized to 0.

      +
      Level.CONFIGwebdriver.logging.Level

      CONFIG is a message level for static configuration messages. +This level is initialized to 700.

      +
      Level.DEBUGwebdriver.logging.Level

      DEBUG is a message level for debugging messages and has the same log level +as the Logger.Level.CONFIG message level.

      +
      Level.FINEwebdriver.logging.Level

      FINE is a message level providing tracing information. +This level is initialized to 500.

      +
      Level.FINERwebdriver.logging.Level

      FINER indicates a fairly detailed tracing message. +This level is initialized to 400.

      +
      Level.FINESTwebdriver.logging.Level

      FINEST indicates a highly detailed tracing message. +This level is initialized to 300.

      +
      Level.INFOwebdriver.logging.Level

      INFO is a message level for informational messages. +This level is initialized to 800.

      +
      Level.OFFwebdriver.logging.Level

      OFF is a special level that can be used to turn off logging. +This level is initialized to Infinity.

      +
      Level.SEVEREwebdriver.logging.Level

      SEVERE is a message level indicating a serious failure. +This level is initialized to 1000.

      +
      Level.SHOUTwebdriver.logging.Level

      SHOUT is a message level for extra debugging loudness. +This level is initialized to 1200.

      +
      Level.WARNINGwebdriver.logging.Level

      WARNING is a message level indicating a potential problem. +This level is initialized to 900.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_logging_LogRecord.html b/docs/class_webdriver_logging_LogRecord.html new file mode 100644 index 0000000..37154d7 --- /dev/null +++ b/docs/class_webdriver_logging_LogRecord.html @@ -0,0 +1,45 @@ +LogRecord

      class LogRecord

      LogRecord objects are used to pass logging requests between +the logging framework and individual log Handlers.

      +

      new LogRecord(level, msg, loggerName, opt_time, opt_sequenceNumber)

      Parameters
      levelwebdriver.logging.Level

      One of the level identifiers.

      +
      msgstring

      The string message.

      +
      loggerNamestring

      The name of the source logger.

      +
      opt_timenumber=

      Time this log record was created if other than now. +If 0, we use #goog.now.

      +
      opt_sequenceNumbernumber=

      Sequence number of this log record. This +should only be passed in when restoring a log record from persistence.

      +

      Instance Methods

      getException()code »

      Get the exception that is part of the log record.

      +
      Returns
      Object

      the exception.

      +

      getLevel()code »

      Get the logging message level, for example Level.SEVERE.

      +
      Returns
      webdriver.logging.Level

      the logging message level.

      +

      getLoggerName()code »

      Get the source Logger's name.

      +
      Returns
      string

      source logger name (may be null).

      +

      getMessage()code »

      Get the "raw" log message, before localization or formatting.

      +
      Returns
      string

      the raw message string.

      +

      getMillis()code »

      Get event time in milliseconds since 1970.

      +
      Returns
      number

      event time in millis since 1970.

      +

      getSequenceNumber()code »

      Get the sequence number.

      +

      +Sequence numbers are normally assigned in the LogRecord +constructor, which assigns unique sequence numbers to +each new LogRecord in increasing order. +

      Returns
      number

      the sequence number.

      +

      reset(level, msg, loggerName, opt_time, opt_sequenceNumber)code »

      Sets all fields of the log record.

      +
      Parameters
      levelwebdriver.logging.Level

      One of the level identifiers.

      +
      msgstring

      The string message.

      +
      loggerNamestring

      The name of the source logger.

      +
      opt_timenumber=

      Time this log record was created if other than now. +If 0, we use #goog.now.

      +
      opt_sequenceNumbernumber=

      Sequence number of this log record. This +should only be passed in when restoring a log record from persistence.

      +

      setException(exception)code »

      Set the exception that is part of the log record.

      +
      Parameters
      exceptionObject

      the exception.

      +

      setLevel(level)code »

      Set the logging message level, for example Level.SEVERE.

      +
      Parameters
      levelwebdriver.logging.Level

      the logging message level.

      +

      setLoggerName(loggerName)code »

      Get the source Logger's name.

      +
      Parameters
      loggerNamestring

      source logger name (may be null).

      +

      setMessage(msg)code »

      Set the "raw" log message, before localization or formatting.

      +
      Parameters
      msgstring

      the raw message string.

      +

      setMillis(time)code »

      Set event time in milliseconds since 1970.

      +
      Parameters
      timenumber

      event time in millis since 1970.

      +

      Compiler Constants

      LogRecord.ENABLE_SEQUENCE_NUMBERSboolean

      Whether to enable log sequence numbers.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_logging_Logger.html b/docs/class_webdriver_logging_Logger.html new file mode 100644 index 0000000..f500dde --- /dev/null +++ b/docs/class_webdriver_logging_Logger.html @@ -0,0 +1,119 @@ +Logger

      class Logger

      The Logger is an object used for logging debug messages. Loggers are +normally named, using a hierarchical dot-separated namespace. Logger names +can be arbitrary strings, but they should normally be based on the package +name or class name of the logged component, such as goog.net.BrowserChannel.

      +

      The Logger object is loosely based on the java class +java.util.logging.Logger. It supports different levels of filtering for +different loggers.

      +

      The logger object should never be instantiated by application code. It +should always use the goog.debug.Logger.getLogger function.

      +

      new Logger(name)

      Parameters
      namestring

      The name of the Logger.

      +

      Instance Methods

      addHandler(handler)code »

      Adds a handler to the logger. This doesn't use the event system because +we want to be able to add logging to the event system.

      +
      Parameters
      handlerFunction

      Handler function to add.

      +

      config(msg, opt_exception)code »

      Logs a message at the Logger.Level.CONFIG level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      fine(msg, opt_exception)code »

      Logs a message at the Logger.Level.FINE level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      finer(msg, opt_exception)code »

      Logs a message at the Logger.Level.FINER level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      finest(msg, opt_exception)code »

      Logs a message at the Logger.Level.FINEST level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      getChildren()code »

      Returns the children of this logger as a map of the child name to the logger.

      +
      Returns
      Object

      The map where the keys are the child leaf names and the +values are the Logger objects.

      +

      getEffectiveLevel()code »

      Returns the effective level of the logger based on its ancestors' levels.

      +
      Returns
      webdriver.logging.Level

      The level.

      +

      getLevel()code »

      Gets the log level specifying which message levels will be logged by this +logger. Message levels lower than this value will be discarded. +The level value Level.OFF can be used to turn off logging. If the level +is null, it means that this node should inherit its level from its nearest +ancestor with a specific (non-null) level value.

      +
      Returns
      webdriver.logging.Level

      The level.

      +

      getLogRecord(level, msg, opt_exception)code »

      Creates a new log record and adds the exception (if present) to it.

      +
      Parameters
      levelwebdriver.logging.Level

      One of the level identifiers.

      +
      msgstring

      The string message.

      +
      opt_exception?Object=

      An exception associated with the +message.

      +
      Returns
      webdriver.logging.LogRecord

      A log record.

      +

      getName()code »

      Gets the name of this logger.

      +
      Returns
      string

      The name of this logger.

      +

      getParent()code »

      Returns the parent of this logger.

      +
      Returns
      webdriver.logging.Logger

      The parent logger or null if this is the root.

      +

      info(msg, opt_exception)code »

      Logs a message at the Logger.Level.INFO level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      isLoggable(level)code »

      Checks if a message of the given level would actually be logged by this +logger. This check is based on the Loggers effective level, which may be +inherited from its parent.

      +
      Parameters
      levelwebdriver.logging.Level

      The level to check.

      +
      Returns
      boolean

      Whether the message would be logged.

      +

      log(level, msg, opt_exception)code »

      Logs a message. If the logger is currently enabled for the +given message level then the given message is forwarded to all the +registered output Handler objects.

      +
      Parameters
      levelwebdriver.logging.Level

      One of the level identifiers.

      +
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Object=

      An exception associated with the +message.

      +

      logRecord(logRecord)code »

      Logs a LogRecord. If the logger is currently enabled for the +given message level then the given message is forwarded to all the +registered output Handler objects.

      +
      Parameters
      logRecordwebdriver.logging.LogRecord

      A log record to log.

      +

      removeHandler(handler)code »

      Removes a handler from the logger. This doesn't use the event system because +we want to be able to add logging to the event system.

      +
      Parameters
      handlerFunction

      Handler function to remove.

      +
      Returns
      boolean

      Whether the handler was removed.

      +

      setLevel(level)code »

      Set the log level specifying which message levels will be logged by this +logger. Message levels lower than this value will be discarded. +The level value Level.OFF can be used to turn off logging. If the new level +is null, it means that this node should inherit its level from its nearest +ancestor with a specific (non-null) level value.

      +
      Parameters
      levelwebdriver.logging.Level

      The new level.

      +

      severe(msg, opt_exception)code »

      Logs a message at the Logger.Level.SEVERE level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      shout(msg, opt_exception)code »

      Logs a message at the Logger.Level.SHOUT level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      warning(msg, opt_exception)code »

      Logs a message at the Logger.Level.WARNING level. +If the logger is currently enabled for the given message level then the +given message is forwarded to all the registered output Handler objects.

      +
      Parameters
      msg(string|function(): string)

      The message to log.

      +
      opt_exception?Error=

      An exception associated with the message.

      +

      Static Functions

      Logger.getLogger(name)code »

      deprecated

      Finds or creates a logger for a named subsystem. If a logger has already been +created with the given name it is returned. Otherwise a new logger is +created. If a new logger is created its log level will be configured based +on the LogManager configuration and it will configured to also send logging +output to its parent's handlers. It will be registered in the LogManager +global namespace.

      +
      Deprecated

      use goog.log instead. http://go/goog-debug-logger-deprecated

      +
      Parameters
      namestring

      A name for the logger. This should be a dot-separated +name and should normally be based on the package name or class name of the +subsystem, such as goog.net.BrowserChannel.

      +
      Returns
      webdriver.logging.Logger

      The named logger.

      +

      Logger.logToProfilers(msg)code »

      Logs a message to profiling tools, if available. +https://developers.google.com/web-toolkit/speedtracer/logging-api +http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx

      +
      Parameters
      msgstring

      The message to log.

      +

      Static Properties

      Compiler Constants

      Logger.ENABLE_HIERARCHYboolean

      Toggles whether loggers other than the root logger can have +log handlers attached to them and whether they can have their log level +set. Logging is a bit faster when this is set to false.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_logging_Preferences.html b/docs/class_webdriver_logging_Preferences.html new file mode 100644 index 0000000..baccf72 --- /dev/null +++ b/docs/class_webdriver_logging_Preferences.html @@ -0,0 +1,8 @@ +Preferences

      class Preferences

      Describes the log preferences for a WebDriver session.

      +

      new Preferences()

      Parameters
      None.

      Instance Methods

      setLevel(type, level)code »

      Sets the desired logging level for a particular log type.

      +
      Parameters
      typestring

      The log type.

      +
      levelwebdriver.logging.Level

      The desired log level.

      +

      toJSON(arg0)code »

      Converts this instance to its JSON representation.

      +
      Parameters
      arg0string=
      Returns
      Object<string, string>

      The JSON representation of this set of +preferences.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_promise_CanceledTaskError_.html b/docs/class_webdriver_promise_CanceledTaskError_.html index cd0697e..95c978b 100644 --- a/docs/class_webdriver_promise_CanceledTaskError_.html +++ b/docs/class_webdriver_promise_CanceledTaskError_.html @@ -1,4 +1,4 @@ -webdriver.promise.CanceledTaskError_

      Class webdriver.promise.CanceledTaskError_

      code »
      Error
      +webdriver.promise.CanceledTaskError_

      Class webdriver.promise.CanceledTaskError_

      code »
      Errorgoog.debug.Error
             └ webdriver.promise.CanceledTaskError_

      Special error used to signal when a task is canceled because a previous - task in the same frame failed.

      Constructor

      webdriver.promise.CanceledTaskError_ ( err )
      Parameters
      err: *
      The error that caused the task cancellation.
      Show:

      Static Properties

      \ No newline at end of file + task in the same frame failed.

      Constructor

      webdriver.promise.CanceledTaskError_ ( err )
      Parameters
      err: *
      The error that caused the task cancellation.
      Show:

      Static Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_promise_CancellationError.html b/docs/class_webdriver_promise_CancellationError.html new file mode 100644 index 0000000..535bc28 --- /dev/null +++ b/docs/class_webdriver_promise_CancellationError.html @@ -0,0 +1,18 @@ +CancellationError

      class CancellationError

      final
      Error
      +  └ goog.debug.Error
      +      └ webdriver.promise.CancellationError

      Error used when the computation of a promise is cancelled.

      +

      new CancellationError(opt_msg)

      Parameters
      opt_msgstring=

      The cancellation message.

      +

      Instance Properties

      descriptionstring

      IE-only.

      +
      fileNamestring

      Mozilla-only

      +
      lineNumbernumber

      Mozilla-only.

      +
      messagestring
      No description.
      namestring
      No description.
      reportErrorToServerboolean

      Whether to report this error to the server. Setting this to false will +cause the error reporter to not report the error back to the server, +which can be useful if the client knows that the error has already been +logged on the server.

      +
      sourceURL?

      Doesn't seem to exist, but closure/debug.js references it.

      +
      stackstring
      No description.

      Static Functions

      CancellationError.wrap(error, opt_msg)code »

      Wraps the given error in a CancellationError. Will trivially return +the error itself if it is an instanceof CancellationError.

      +
      Parameters
      error*

      The error to wrap.

      +
      opt_msgstring=

      The prefix message to use.

      +
      Returns
      webdriver.promise.CancellationError

      A cancellation error.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_promise_ControlFlow.html b/docs/class_webdriver_promise_ControlFlow.html index 1edb7cd..e8df80c 100644 --- a/docs/class_webdriver_promise_ControlFlow.html +++ b/docs/class_webdriver_promise_ControlFlow.html @@ -1,116 +1,109 @@ -webdriver.promise.ControlFlow

      Class webdriver.promise.ControlFlow

      code »
      webdriver.EventEmitter
      -  └ webdriver.promise.ControlFlow

      Handles the execution of scheduled tasks, each of which may be an - asynchronous operation. The control flow will ensure tasks are executed in - the ordered scheduled, starting each task only once those before it have - completed. - -

      Each task scheduled within this flow may return a - webdriver.promise.Promise to indicate it is an asynchronous - operation. The ControlFlow will wait for such promises to be resolved before - marking the task as completed. - -

      Tasks and each callback registered on a webdriver.promise.Deferred - will be run in their own ControlFlow frame. Any tasks scheduled within a - frame will have priority over previously scheduled tasks. Furthermore, if - any of the tasks in the frame fails, the remainder of the tasks in that frame - will be discarded and the failure will be propagated to the user through the - callback/task's promised result. - -

      Each time a ControlFlow empties its task queue, it will fire an - webdriver.promise.ControlFlow.EventType.IDLE event. Conversely, - whenever the flow terminates due to an unhandled error, it will remove all - remaining tasks in its queue and fire an - webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION event. If - there are no listeners registered with the flow, the error will be - rethrown to the global error handler.

      Constructor

      webdriver.promise.ControlFlow ( opt_timer )
      Parameters
      opt_timer: webdriver.promise.ControlFlow.Timer=
      The timer object - to use. Should only be set for testing.

      Enumerations

      Show:

      Type Definitions

      code »webdriver.promise.ControlFlow.Timer : {clearInterval: function(number), clearTimeout: function(number), setInterval: function(!Function, number): number, setTimeout: function(!Function, number): number}
      No description.

      Instance Methods

      Defined in webdriver.promise.ControlFlow

      Aborts the current frame. The frame, and all of the tasks scheduled within it - will be discarded. If this instance does not have an active frame, it will - immediately terminate all execution.

      Parameters
      error: *
      The reason the frame is being aborted; typically either - an Error or string.

      Aborts this flow, abandoning all remaining tasks. If there are - listeners registered, an UNCAUGHT_EXCEPTION will be emitted with the - offending error, otherwise, the error will be rethrown to the - global error handler.

      Parameters
      error: *
      Object describing the error that caused the flow to - abort; usually either an Error or string value.
      code »annotateError ( e )!(Error|goog.testing.JsUnitException)

      Appends a summary of this instance's recent task history to the given - error's stack trace. This function will also ensure the error's stack trace - is in canonical form.

      Parameters
      e: !(Error|goog.testing.JsUnitException)
      The error to annotate.
      Returns
      The annotated error.

      Schedules a task that will wait for another promise to resolve. The resolved - promise's value will be returned as the task result.

      Parameters
      promise: !webdriver.promise.Promise
      The promise to wait on.
      Returns
      A promise that will resolve when the - task has completed.

      Cancels the event loop, if necessary.

      Cancels the shutdown sequence if it is currently scheduled.

      Clears this instance's task history.

      Commences the shutdown sequence for this instance. After one turn of the - event loop, this object will emit the - webdriver.promise.ControlFlow.EventType.IDLE event to signal - listeners that it has completed. During this wait, if another task is - scheduled, the shutdown will be aborted.

      code »execute ( fn, opt_description )!webdriver.promise.Promise

      Schedules a task for execution. If there is nothing currently in the - queue, the task will be executed in the next turn of the event loop.

      Parameters
      fn: !Function
      The function to call to start the task. If the - function returns a webdriver.promise.Promise, this instance - will wait for it to be resolved before starting the next task.
      opt_description: string=
      A description of the task.
      Returns
      A promise that will be resolved with - the result of the action.

      Returns a summary of the recent task activity for this instance. This - includes the most recently completed task, as well as any parent tasks. In - the returned summary, the task at index N is considered a sub-task of the - task at index N+1.

      Returns
      A summary of this instance's recent task - activity.
      Returns
      The next task to execute, or - null if a frame was resolved.
      Returns
      The scheduled tasks still pending with this instance.

      Resets this instance, clearing its queue and removing all event listeners.

      Parameters
      frame: !webdriver.promise.Frame_
      The frame to resolve.

      Executes the next task for the current frame. If the current frame has no - more tasks, the frame's result will be resolved, returning control to the - frame's creator. This will terminate the flow if the completed frame was at - the top of the stack.

      code »runInNewFrame_ ( fn, callback, errback, opt_activate )

      Executes a function in a new frame. If the function does not schedule any new - tasks, the frame will be discarded and the function's result returned - immediately. Otherwise, a promise will be returned. This promise will be - resolved with the function's result once all of the tasks scheduled within - the function have been completed. If the function's frame is aborted, the - returned promise will be rejected.

      Parameters
      fn: !Function
      The function to execute.
      callback: function(*)
      The function to call with a successful result.
      errback: function(*)
      The function to call if there is an error.
      opt_activate: boolean=
      Whether the active frame should be updated to - the newly created frame so tasks are treated as sub-tasks.

      Schedules the interval for this instance's event loop, if necessary.

      code »timeout ( ms, opt_description )!webdriver.promise.Promise

      Inserts a setTimeout into the command queue. This is equivalent to - a thread sleep in a synchronous programming language.

      Parameters
      ms: number
      The timeout delay, in milliseconds.
      opt_description: string=
      A description to accompany the timeout.
      Returns
      A promise that will be resolved with - the result of the action.

      Removes a completed task from this instance's history record. If any - tasks remain from aborted frames, those will be removed as well.

      code »wait ( condition, timeout, opt_message )!webdriver.promise.Promise

      Schedules a task that shall wait for a condition to hold. Each condition - function may return any value, but it will always be evaluated as a boolean. - -

      Condition functions may schedule sub-tasks with this instance, however, - their execution time will be factored into whether a wait has timed out. - -

      In the event a condition returns a Promise, the polling loop will wait for - it to be resolved before evaluating whether the condition has been satisfied. - The resolution time for a promise is factored into whether a wait has timed - out. - -

      If the condition function throws, or returns a rejected promise, the - wait task will fail.

      Parameters
      condition: !Function
      The condition function to poll.
      timeout: number
      How long to wait, in milliseconds, for the condition - to hold before timing out.
      opt_message: string=
      An optional error message to include if the - wait times out; defaults to the empty string.
      Returns
      A promise that will be resolved when the - condition has been satisified. The promise shall be rejected if the wait - times out waiting for the condition.

      Defined in webdriver.EventEmitter

      code »addListener ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      Registers a listener.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.
      code »addListener_ ( type, listenerFn, opt_scope, opt_oneshot )!webdriver.EventEmitter

      Registers a listener.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      opt_oneshot: boolean=
      Whether the listener should be removed after - the first event is fired.
      Returns
      A self reference.
      code »emit ( type, var_args )

      Fires an event and calls all listeners.

      Parameters
      type: string
      The type of event to emit.
      var_args: ...*
      Any arguments to pass to each listener.
      code »listeners ( type )!Array

      Returns a mutable list of listeners for a specific type of event.

      Parameters
      type: string
      The type of event to retrieve the listeners for.
      Returns
      The registered listeners for - the given event type.
      code »on ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      An alias for #addListener().

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.
      code »once ( type, listenerFn, opt_scope )!webdriver.EventEmitter

      Registers a one-time listener which will be called only the first time an - event is emitted, after which it will be removed.

      Parameters
      type: string
      The type of event to listen for.
      listenerFn: !Function
      The function to invoke when the event is fired.
      opt_scope: Object=
      The object in whose scope to invoke the listener.
      Returns
      A self reference.

      Removes all listeners for a specific type of event. If no event is - specified, all listeners across all types will be removed.

      Parameters
      opt_type: string=
      The type of event to remove listeners from.
      Returns
      A self reference.

      Removes a previously registered event listener.

      Parameters
      type: string
      The type of event to unregister.
      listenerFn: !Function
      The handler function to remove.
      Returns
      A self reference.

      Instance Properties

      Defined in webdriver.promise.ControlFlow

      Tracks the active execution frame for this instance. Lazily initialized - when the first task is scheduled.

      Interval ID for this instance's event loop.

      A list of recent tasks. Each time a new task is started, or a frame is - completed, the previously recorded task is removed from this list. If - there are multiple tasks, task N+1 is considered a sub-task of task - N.

      The number of aborted frames since the last time a task was executed or a - frame completed successfully.

      The number of "pending" promise rejections. - -

      Each time a promise is rejected and is not handled by a listener, it will - schedule a 0-based timeout to check if it is still unrejected in the next - turn of the JS-event loop. This allows listeners to attach to, and handle, - the rejected promise at any point in same turn of the event loop that the - promise was rejected. - -

      When this flow's own event loop triggers, it will not run if there - are any outstanding promise rejections. This allows unhandled promises to - be reported before a new task is started, ensuring the error is reported to - the current task queue.

      A reference to the frame in which new tasks should be scheduled. If - null, tasks will be scheduled within the active frame. When forcing - a function to run in the context of a new frame, this pointer is used to - ensure tasks are scheduled within the newly created frame, even though it - won't be active yet.

      Timeout ID set when the flow is about to shutdown without any errors - being detected. Upon shutting down, the flow will emit an - webdriver.promise.ControlFlow.EventType.IDLE event. Idle events - always follow a brief timeout in order to catch latent errors from the last - completed task. If this task had a callback registered, but no errback, and - the task fails, the unhandled failure would not be reported by the promise - system until the next turn of the event loop: - - // Schedule 1 task that fails. - var result = webriver.promise.controlFlow().schedule('example', - function() { return webdriver.promise.rejected('failed'); }); - // Set a callback on the result. This delays reporting the unhandled - // failure for 1 turn of the event loop. - result.then(goog.nullFunction);

      The timer used by this instance.

      Defined in webdriver.EventEmitter

      Map of events to registered listeners.

      Static Properties

      How often, in milliseconds, the event loop should run.

      The default timer object, which uses the global timer functions.

      \ No newline at end of file +ControlFlow

      class ControlFlow

      final
      webdriver.EventEmitter
      +  └ webdriver.promise.ControlFlow

      Handles the execution of scheduled tasks, each of which may be an +asynchronous operation. The control flow will ensure tasks are executed in +the ordered scheduled, starting each task only once those before it have +completed.

      +

      Each task scheduled within this flow may return a +webdriver.promise.Promise to indicate it is an asynchronous +operation. The ControlFlow will wait for such promises to be resolved before +marking the task as completed.

      +

      Tasks and each callback registered on a webdriver.promise.Promise +will be run in their own ControlFlow frame. Any tasks scheduled within a +frame will take priority over previously scheduled tasks. Furthermore, if any +of the tasks in the frame fail, the remainder of the tasks in that frame will +be discarded and the failure will be propagated to the user through the +callback/task's promised result.

      +

      Each time a ControlFlow empties its task queue, it will fire an +IDLE event. Conversely, +whenever the flow terminates due to an unhandled error, it will remove all +remaining tasks in its queue and fire an +UNCAUGHT_EXCEPTION event. If there are no listeners registered with the +flow, the error will be rethrown to the global error handler.

      +

      new ControlFlow()

      Parameters
      None.

      Instance Methods

      addListener(type, listenerFn, opt_scope)code »

      Registers a listener.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      emit(type, var_args)code »

      Fires an event and calls all listeners.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to emit.

      +
      var_args...*

      Any arguments to pass to each listener.

      +

      <T> execute(fn, opt_description)code »

      Schedules a task for execution. If there is nothing currently in the +queue, the task will be executed in the next turn of the event loop. If +the task function is a generator, the task will be executed using +webdriver.promise.consume.

      +
      Parameters
      fnfunction(): (T|webdriver.promise.Promise<T>)

      The function to +call to start the task. If the function returns a +webdriver.promise.Promise, this instance will wait for it to be +resolved before starting the next task.

      +
      opt_descriptionstring=

      A description of the task.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will be resolved +with the result of the action.

      +

      getSchedule(opt_includeStackTraces)code »

      Generates an annotated string describing the internal state of this control +flow, including the currently executing as well as pending tasks. If +opt_includeStackTraces === true, the string will include the +stack trace from when each task was scheduled.

      +
      Parameters
      opt_includeStackTracesstring=

      Whether to include the stack traces +from when each task was scheduled. Defaults to false.

      +
      Returns
      string

      String representation of this flow's internal state.

      +

      listeners(type)code »

      Returns a mutable list of listeners for a specific type of event.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to retrieve the listeners for.

      +
      Returns
      Array<{fn: Function, oneshot: boolean, scope: ?(Object)}>

      The registered listeners for +the given event type.

      +

      on(type, listenerFn, opt_scope)code »

      An alias for #addListener().

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      once(type, listenerFn, opt_scope)code »

      Registers a one-time listener which will be called only the first time an +event is emitted, after which it will be removed.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to listen for.

      +
      listenerFnFunction

      The function to invoke when the event is fired.

      +
      opt_scope?Object=

      The object in whose scope to invoke the listener.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      removeAllListeners(opt_type)code »

      Removes all listeners for a specific type of event. If no event is +specified, all listeners across all types will be removed.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      opt_typestring=

      The type of event to remove listeners from.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      removeListener(type, listenerFn)code »

      Removes a previously registered event listener.

      +

      Defined by: webdriver.EventEmitter

      Parameters
      typestring

      The type of event to unregister.

      +
      listenerFnFunction

      The handler function to remove.

      +
      Returns
      webdriver.EventEmitter

      A self reference.

      +

      reset()code »

      Resets this instance, clearing its queue and removing all event listeners.

      +

      timeout(ms, opt_description)code »

      Inserts a setTimeout into the command queue. This is equivalent to +a thread sleep in a synchronous programming language.

      +
      Parameters
      msnumber

      The timeout delay, in milliseconds.

      +
      opt_descriptionstring=

      A description to accompany the timeout.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be resolved with +the result of the action.

      +

      toString()code »

      Returns a string representation of this control flow, which is its current +schedule, sans task stack traces.

      +
      Returns
      string

      The string representation of this contorl flow.

      +

      <T> wait(condition, opt_timeout, opt_message)code »

      Schedules a task that shall wait for a condition to hold. Each condition +function may return any value, but it will always be evaluated as a boolean.

      +

      Condition functions may schedule sub-tasks with this instance, however, +their execution time will be factored into whether a wait has timed out.

      +

      In the event a condition returns a Promise, the polling loop will wait for +it to be resolved before evaluating whether the condition has been satisfied. +The resolution time for a promise is factored into whether a wait has timed +out.

      +

      If the condition function throws, or returns a rejected promise, the +wait task will fail.

      +

      If the condition is defined as a promise, the flow will wait for it to +settle. If the timeout expires before the promise settles, the promise +returned by this function will be rejected.

      +

      If this function is invoked with timeout === 0, or the timeout is omitted, +the flow will wait indefinitely for the condition to be satisfied.

      +
      Parameters
      condition(webdriver.promise.Promise<T>|function(): ?)

      The condition to poll, +or a promise to wait on.

      +
      opt_timeoutnumber=

      How long to wait, in milliseconds, for the +condition to hold before timing out. If omitted, the flow will wait +indefinitely.

      +
      opt_messagestring=

      An optional error message to include if the +wait times out; defaults to the empty string.

      +
      Returns
      webdriver.promise.Promise<T>

      A promise that will be fulfilled +when the condition has been satisified. The promise shall be rejected if +the wait times out waiting for the condition.

      +
      Throws
      TypeError

      If condition is not a function or promise or if timeout +is not a number >= 0.

      +

      Types

      ControlFlow.EventType

      Events that may be emitted by an webdriver.promise.ControlFlow.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_promise_Deferred.html b/docs/class_webdriver_promise_Deferred.html index d98258e..ab17f12 100644 --- a/docs/class_webdriver_promise_Deferred.html +++ b/docs/class_webdriver_promise_Deferred.html @@ -1,110 +1,84 @@ -webdriver.promise.Deferred

      Class webdriver.promise.Deferred

      code »
      webdriver.promise.Promise
      -  └ webdriver.promise.Deferred

      Represents a value that will be resolved at some point in the future. This - class represents the protected "producer" half of a Promise - each Deferred - has a promise property that may be returned to consumers for - registering callbacks, reserving the ability to resolve the deferred to the - producer. +Deferred

      class Deferred<T>

      All implemented interfaces
      IThenable<T>
      webdriver.promise.Thenable<T>

      Represents a value that will be resolved at some point in the future. This +class represents the protected "producer" half of a Promise - each Deferred +has a promise property that may be returned to consumers for +registering callbacks, reserving the ability to resolve the deferred to the +producer.

      +

      If this Deferred is rejected and there are no listeners registered before +the next turn of the event loop, the rejection will be passed to the +webdriver.promise.ControlFlow as an unhandled failure.

      +

      new Deferred(opt_flow)

      Parameters
      opt_flow?webdriver.promise.ControlFlow=

      The control flow +this instance was created under. This should only be provided during +unit tests.

      +

      Instance Methods

      cancel(opt_reason)code »

      Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

      +

      Specified by: webdriver.promise.Thenable

      Parameters
      opt_reason?(string|webdriver.promise.CancellationError)=

      The reason this +promise is being cancelled.

      +

      fulfill(opt_value)code »

      Resolves this deferred with the given value. It is safe to call this as a +normal function (with no bound "this").

      +
      Parameters
      opt_value?(T|{then: ?})=

      The fulfilled value.

      +

      isPending()code »

      Specified by: webdriver.promise.Thenable

      Returns
      boolean

      Whether this promise's value is still being computed.

      +

      reject(opt_reason)code »

      Rejects this promise with the given reason. It is safe to call this as a +normal function (with no bound "this").

      +
      Parameters
      opt_reason*=

      The rejection reason.

      +

      then(opt_callback, opt_errback)code »

      deprecated

      Registers listeners for when this instance is resolved.

      +

      Specified by: webdriver.promise.Thenable, IThenable

      Deprecated

      Use then from the promise property directly.

      +
      Parameters
      opt_callback?function(T): (R|IThenable<R>)=

      The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

      +
      opt_errback?function(*): (R|IThenable<R>)=

      The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenCatch(errback)code »

      deprecated

      Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} catch (ex) {
      +  console.error(ex);
      +}
       
      - 

      If this Deferred is rejected and there are no listeners registered before - the next turn of the event loop, the rejection will be passed to the - webdriver.promise.ControlFlow as an unhandled failure. +// Asynchronous promise API: +doAsynchronousWork().thenCatch(function(ex) { + console.error(ex); +}); +

      +

      Specified by: webdriver.promise.Thenable

      Deprecated

      Use thenCatch from the promise property directly.

      +
      Parameters
      errbackfunction(*): (R|IThenable<R>)

      The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenFinally(callback)code »

      deprecated

      Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} finally {
      +  cleanUp();
      +}
       
      - 

      If this Deferred is cancelled, the cancellation reason will be forward to - the Deferred's canceller function (if provided). The canceller may return a - truth-y value to override the reason provided for rejection.

      Constructor

      webdriver.promise.Deferred ( opt_canceller, opt_flow )
      Parameters
      opt_canceller: Function=
      Function to call when cancelling the - computation of this instance's value.
      opt_flow: webdriver.promise.ControlFlow=
      The control flow - this instance was created under. This should only be provided during - unit tests.

      Enumerations

      Show:

      Type Definitions

      code »webdriver.promise.Deferred.Listener_ : {callback: (Function|undefined), errback: (Function|undefined), fulfill: function(*), reject: function(*)}
      Type definition for a listener registered on a Deferred object.

      Instance Methods

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a - promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will - be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      +// Asynchronous promise API:
      +doAsynchronousWork().thenFinally(cleanUp);
      +
      +

      Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

      +
      try {
      +  throw Error('one');
      +} finally {
      +  throw Error('two');  // Hides Error: one
      +}
       
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be - in one of three states: pending, resolved, or rejected. Each promise starts - in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Properties

      \ No newline at end of file +promise.rejected(Error('one')) + .thenFinally(function() { + throw Error('two'); // Hides Error: one + }); + +

      Specified by: webdriver.promise.Thenable

      Deprecated

      Use thenFinally from the promise property directly.

      +
      Parameters
      callbackfunction(): (R|IThenable<R>)

      The function +to call when this promise is resolved.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be fulfilled +with the callback result.

      +

      Instance Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_promise_Frame_.html b/docs/class_webdriver_promise_Frame_.html index 798287f..19134a6 100644 --- a/docs/class_webdriver_promise_Frame_.html +++ b/docs/class_webdriver_promise_Frame_.html @@ -1,14 +1,14 @@ -webdriver.promise.Frame_

      Class webdriver.promise.Frame_

      code »
      webdriver.promise.Promise
      +webdriver.promise.Frame_

      Class webdriver.promise.Frame_

      code »
      webdriver.promise.Promise.<(T|null)>
         └ webdriver.promise.Deferredwebdriver.promise.Node_
      -          └ webdriver.promise.Frame_

      An execution frame within a webdriver.promise.ControlFlow. Each + └ webdriver.promise.Frame_

      All implemented interfaces:
      webdriver.promise.Thenable.<(T|null)>

      An execution frame within a webdriver.promise.ControlFlow. Each frame represents the execution context for either a webdriver.promise.Task_ or a callback on a webdriver.promise.Deferred.

      Each frame may contain sub-frames. If child N is a sub-frame, then the items queued within it are given priority over child N+1.

      Constructor

      webdriver.promise.Frame_ ( flow )
      Parameters
      flow: !webdriver.promise.ControlFlow
      The flow this instance belongs - to.
      Show:

      Instance Methods

      Defined in webdriver.promise.Frame_

      Adds a new node to this frame.

      Parameters
      node: !(webdriver.promise.Frame_|webdriver.promise.Task_)
      The node to insert.

      Marks all of the tasks that are descendants of this frame in the execution + to.

      Show:

      Instance Methods

      Defined in webdriver.promise.Frame_

      Adds a new node to this frame.

      Parameters
      node: !(webdriver.promise.Frame_|webdriver.promise.Task_)
      The node to insert.

      Marks all of the tasks that are descendants of this frame in the execution tree as cancelled. This is necessary for callbacks scheduled asynchronous. For example: @@ -27,95 +27,14 @@ // flow failed: Error: boom // task failed! CanceledTaskError: Task discarded due to a previous // task failure: Error: boom

      Parameters
      error: !webdriver.promise.CanceledTaskError_
      The cancellation - error.
      Returns
      This frame's - fist child.
      Returns
      The task currently executing - within this frame, if any.

      Locks this frame.

      Removes a child from this frame.

      Parameters
      child: !(webdriver.promise.Frame_|webdriver.promise.Task_)
      The child to remove.
      Parameters
      task: webdriver.promise.Task_
      The task currently - executing within this frame, if any.
      code »toString ( )string

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will + error.

      Returns
      This frame's + fist child.
      Returns
      The task currently executing + within this frame, if any.

      Locks this frame.

      Removes a child from this frame.

      Parameters
      child: !(webdriver.promise.Frame_|webdriver.promise.Task_)
      The child to remove.
      Parameters
      task: webdriver.promise.Task_
      The task currently + executing within this frame, if any.
      code »toString ( )string

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a + Error or a string.

      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Frame_

      Whether this frame is active. A frame is considered active once one of its + it before resolving.

      Parameters
      opt_value: T=
      The fulfilled value.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise.<(T|null)>

      code »cancel ( reason )
      Parameters
      reason
      code »isPending ( )boolean
      code »then ( opt_callback, opt_errback )
      Parameters
      opt_callback
      opt_errback
      code »thenCatch ( errback )
      Parameters
      errback
      code »thenFinally ( callback )
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Frame_

      Whether this frame is active. A frame is considered active once one of its descendants has been removed for execution. Adding a sub-frame as a child to an active frame is an indication that @@ -128,15 +47,13 @@ flow.execute('this should execute 2nd', goog.nullFunction); }); flow.execute('this should execute last', goog.nullFunction); -

      Whether this frame is currently locked. A locked frame represents a callback +

      Whether this frame is currently locked. A locked frame represents a callback or task function which has run to completion and scheduled all of its tasks.

      Once a frame becomes active, any new frames which are added represent callbacks on a webdriver.promise.Deferred, whose - tasks must be given priority over previously scheduled tasks.

      A reference to the last node inserted in this frame.

      The task currently being executed within this frame.

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be + tasks must be given priority over previously scheduled tasks.

      A reference to the last node inserted in this frame.

      The task currently being executed within this frame.

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be in one of three states: pending, resolved, or rejected. Each promise starts in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Properties

      \ No newline at end of file + fulfilled or rejected state, at which point the promise is considered + resolved.

      Static Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_promise_MultipleUnhandledRejectionError.html b/docs/class_webdriver_promise_MultipleUnhandledRejectionError.html new file mode 100644 index 0000000..5f52808 --- /dev/null +++ b/docs/class_webdriver_promise_MultipleUnhandledRejectionError.html @@ -0,0 +1,14 @@ +MultipleUnhandledRejectionError

      class MultipleUnhandledRejectionError

      Error
      +  └ goog.debug.Error
      +      └ webdriver.promise.MultipleUnhandledRejectionError

      Error used when there are multiple unhandled promise rejections detected +within a task or callback.

      +

      new MultipleUnhandledRejectionError(errors)

      Parameters
      errorsSet<*>

      The errors to report.

      +

      Instance Properties

      descriptionstring

      IE-only.

      +
      errorsSet<*>
      No description.
      fileNamestring

      Mozilla-only

      +
      lineNumbernumber

      Mozilla-only.

      +
      messagestring
      No description.
      namestring
      No description.
      reportErrorToServerboolean

      Whether to report this error to the server. Setting this to false will +cause the error reporter to not report the error back to the server, +which can be useful if the client knows that the error has already been +logged on the server.

      +
      sourceURL?

      Doesn't seem to exist, but closure/debug.js references it.

      +
      stackstring
      No description.
      \ No newline at end of file diff --git a/docs/class_webdriver_promise_Node_.html b/docs/class_webdriver_promise_Node_.html index f815628..666e39a 100644 --- a/docs/class_webdriver_promise_Node_.html +++ b/docs/class_webdriver_promise_Node_.html @@ -1,97 +1,14 @@ -webdriver.promise.Node_

      Class webdriver.promise.Node_

      code »
      webdriver.promise.Promise
      +webdriver.promise.Node_

      Class webdriver.promise.Node_

      code »
      webdriver.promise.Promise.<(T|null)>
         └ webdriver.promise.Deferred
      -      └ webdriver.promise.Node_

      A single node in an webdriver.promise.ControlFlow's task tree.

      Constructor

      webdriver.promise.Node_ ( flow )
      Parameters
      flow: !webdriver.promise.ControlFlow
      The flow this instance belongs - to.
      Show:

      Instance Methods

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will + └ webdriver.promise.Node_

      All implemented interfaces:
      webdriver.promise.Thenable.<(T|null)>

      A single node in an webdriver.promise.ControlFlow's task tree.

      Constructor

      webdriver.promise.Node_ ( flow )
      Parameters
      flow: !webdriver.promise.ControlFlow
      The flow this instance belongs + to.
      Show:

      Instance Methods

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a + Error or a string.

      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will + it before resolving.

      Parameters
      opt_value: T=
      The fulfilled value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be + Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise.<(T|null)>

      code »cancel ( reason )
      Parameters
      reason
      code »isPending ( )boolean
      code »then ( opt_callback, opt_errback )
      Parameters
      opt_callback
      opt_errback
      code »thenCatch ( errback )
      Parameters
      errback
      code »thenFinally ( callback )
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be in one of three states: pending, resolved, or rejected. Each promise starts in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Properties

      \ No newline at end of file + fulfilled or rejected state, at which point the promise is considered + resolved.

      Static Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_promise_Promise.html b/docs/class_webdriver_promise_Promise.html index 0bab2ee..7a18e13 100644 --- a/docs/class_webdriver_promise_Promise.html +++ b/docs/class_webdriver_promise_Promise.html @@ -1,88 +1,74 @@ -webdriver.promise.Promise

      Class webdriver.promise.Promise

      code »

      Represents the eventual value of a completed operation. Each promise may be - in one of three states: pending, resolved, or rejected. Each promise starts - in the pending state and may make a single transition to either a - fulfilled or failed state. +Promise

      class Promise<T>

      All implemented interfaces
      IThenable<T>
      webdriver.promise.Thenable<T>

      Represents the eventual value of a completed operation. Each promise may be +in one of three states: pending, fulfilled, or rejected. Each promise starts +in the pending state and may make a single transition to either a +fulfilled or rejected state, at which point the promise is considered +resolved.

      +

      new Promise(resolver, opt_flow)

      Parameters
      resolverfunction(function(?(T|{then: ?})=): ?, function(*=): ?): ?

      Function that is invoked immediately to begin computation of this +promise's value. The function should accept a pair of callback functions, +one for fulfilling the promise and another for rejecting it.

      +
      opt_flow?webdriver.promise.ControlFlow=

      The control flow +this instance was created under. Defaults to the currently active flow.

      +

      Instance Methods

      cancel(opt_reason)code »

      Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

      +

      Specified by: webdriver.promise.Thenable

      Parameters
      opt_reason?(string|webdriver.promise.CancellationError)=

      The reason this +promise is being cancelled.

      +

      isPending()code »

      Specified by: webdriver.promise.Thenable

      Returns
      boolean

      Whether this promise's value is still being computed.

      +

      then(opt_callback, opt_errback)code »

      Registers listeners for when this instance is resolved.

      +

      Specified by: webdriver.promise.Thenable, IThenable

      Parameters
      opt_callback?function(T): (R|IThenable<R>)=

      The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

      +
      opt_errback?function(*): (R|IThenable<R>)=

      The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenCatch(errback)code »

      Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} catch (ex) {
      +  console.error(ex);
      +}
       
      - 

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Constructor

      webdriver.promise.Promise ( )
      Show:

      Instance Methods

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      +// Asynchronous promise API:
      +doAsynchronousWork().thenCatch(function(ex) {
      +  console.error(ex);
      +});
      +
      +

      Specified by: webdriver.promise.Thenable

      Parameters
      errbackfunction(*): (R|IThenable<R>)

      The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

      +
      Returns
      webdriver.promise.Promise

      A new promise which will be +resolved with the result of the invoked callback.

      +

      thenFinally(callback)code »

      Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

      +
      // Synchronous API:
      +try {
      +  doSynchronousWork();
      +} finally {
      +  cleanUp();
      +}
       
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      +// Asynchronous promise API:
      +doAsynchronousWork().thenFinally(cleanUp);
      +
      +

      Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

      +
      try {
      +  throw Error('one');
      +} finally {
      +  throw Error('two');  // Hides Error: one
      +}
       
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback
      \ No newline at end of file +promise.rejected(Error('one')) + .thenFinally(function() { + throw Error('two'); // Hides Error: one + }); + +

      Specified by: webdriver.promise.Thenable

      Parameters
      callbackfunction(): (R|IThenable<R>)

      The function +to call when this promise is resolved.

      +
      Returns
      webdriver.promise.Promise

      A promise that will be fulfilled +with the callback result.

      +

      toString()code »

      Returns
      string
      \ No newline at end of file diff --git a/docs/class_webdriver_promise_Task_.html b/docs/class_webdriver_promise_Task_.html index bc36581..f226db6 100644 --- a/docs/class_webdriver_promise_Task_.html +++ b/docs/class_webdriver_promise_Task_.html @@ -1,101 +1,18 @@ -webdriver.promise.Task_

      Class webdriver.promise.Task_

      code »
      webdriver.promise.Promise
      +webdriver.promise.Task_

      Class webdriver.promise.Task_

      code »
      webdriver.promise.Promise.<(T|null)>
         └ webdriver.promise.Deferredwebdriver.promise.Node_
      -          └ webdriver.promise.Task_

      A task to be executed by a webdriver.promise.ControlFlow.

      Constructor

      webdriver.promise.Task_ ( flow, fn, description, snapshot )
      Parameters
      flow: !webdriver.promise.ControlFlow
      The flow this instances belongs + └ webdriver.promise.Task_
      All implemented interfaces:
      webdriver.promise.Thenable.<(T|null)>

      A task to be executed by a webdriver.promise.ControlFlow.

      Constructor

      webdriver.promise.Task_ ( flow, fn, description, snapshot )
      Parameters
      flow: !webdriver.promise.ControlFlow
      The flow this instances belongs to.
      fn: !Function
      The function to call when the task executes. If it returns a webdriver.promise.Promise, the flow will wait for it to be resolved before starting the next task.
      description: string
      A description of the task for debugging.
      snapshot: !webdriver.stacktrace.Snapshot
      A snapshot of the stack - when this task was scheduled.
      Show:

      Instance Methods

      Defined in webdriver.promise.Task_

      Executes this task.

      Returns
      This task's description.
      code »toString ( )string

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will + when this task was scheduled.

      Show:

      Instance Methods

      Defined in webdriver.promise.Task_

      Executes this task.

      Returns
      This task's description.
      code »toString ( )string

      Defined in webdriver.promise.Node_

      Returns
      This node's parent.
      Returns
      The root of this node's tree.
      code »setParent ( parent )
      Parameters
      parent: webdriver.promise.Node_
      This node's new parent.

      Defined in webdriver.promise.Deferred

      code »errback ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.
      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a + Error or a string.

      code »fulfill ( opt_value )

      Resolves this promise with the given value. If the value is itself a promise and not a reference to this deferred, this instance will wait for - it before resolving.

      Parameters
      opt_value: *=
      The resolved value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will + it before resolving.

      Parameters
      opt_value: T=
      The fulfilled value.
      code »reject ( opt_error )

      Rejects this promise. If the error is itself a promise, this instance will be chained to it and be rejected with the error's resolved value.

      Parameters
      opt_error: *=
      The rejection reason, typically either a - Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise

      code »addBoth ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenFinally() instead.

      Registers a function to be invoked when this promise is either rejected or - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call when this promise is - either resolved or rejected. The function should expect a single - argument: the resolved value or rejection error.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallback ( callback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      Registers a function to be invoked when this promise is successfully - resolved. This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addCallbacks ( callback, errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #then() instead.

      An alias for webdriver.promise.Promise.prototype.then that permits - the scope of the invoked function to be specified. This function is provided - for backwards compatibility with the Dojo Deferred API.

      Parameters
      callback: Function
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »addErrback ( errback, opt_self )!webdriver.promise.Promise
      Deprecated: Use #thenCatch() instead.

      Registers a function to be invoked when this promise is rejected. - This function is provided for backwards compatibility with the - Dojo Deferred API.

      Parameters
      errback: Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      opt_self: !Object=
      The object which |this| should refer to when the - function is invoked.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.
      code »cancel ( reason )

      Cancels the computation of this promise's value, rejecting the promise in the - process.

      Parameters
      reason: *
      The reason this promise is being cancelled. If not an - Error, one will be created using the value's string - representation.
      Returns
      Whether this promise's value is still being computed.
      code »then ( opt_callback, opt_errback )!webdriver.promise.Promise

      Registers listeners for when this instance is resolved. This function most - overridden by subtypes.

      Parameters
      opt_callback: Function=
      The function to call if this promise is - successfully resolved. The function should expect a single argument: the - promise's resolved value.
      opt_errback: Function=
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener for when this promise is rejected. This is synonymous - with the catch clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } catch (ex) {
      -     console.error(ex);
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenCatch(function(ex) {
      -     console.error(ex);
      -   });
      - 
      Parameters
      errback: !Function
      The function to call if this promise is - rejected. The function should expect a single argument: the rejection - reason.
      Returns
      A new promise which will be resolved - with the result of the invoked callback.

      Registers a listener to invoke when this promise is resolved, regardless - of whether the promise's value was successfully computed. This function - is synonymous with the finally clause in a synchronous API: -

      
      -   // Synchronous API:
      -   try {
      -     doSynchronousWork();
      -   } finally {
      -     cleanUp();
      -   }
      -
      -   // Asynchronous promise API:
      -   doAsynchronousWork().thenFinally(cleanUp);
      - 
      - - Note: similar to the finally clause, if the registered - callback returns a rejected promise or throws an error, it will silently - replace the rejection error (if any) from this promise: -
      
      -   try {
      -     throw Error('one');
      -   } finally {
      -     throw Error('two');  // Hides Error: one
      -   }
      -
      -   webdriver.promise.rejected(Error('one'))
      -       .thenFinally(function() {
      -         throw Error('two');  // Hides Error: one
      -       });
      - 
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Task_

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be + Error or a string.

      Removes all of the listeners previously registered on this deferred.

      Throws
      Error
      If this deferred has already been resolved.

      Defined in webdriver.promise.Promise.<(T|null)>

      code »cancel ( reason )
      Parameters
      reason
      code »isPending ( )boolean
      code »then ( opt_callback, opt_errback )
      Parameters
      opt_callback
      opt_errback
      code »thenCatch ( errback )
      Parameters
      errback
      code »thenFinally ( callback )
      Parameters
      callback

      Instance Properties

      Defined in webdriver.promise.Task_

      Defined in webdriver.promise.Node_

      Defined in webdriver.promise.Deferred

      Represents the eventual value of a completed operation. Each promise may be in one of three states: pending, resolved, or rejected. Each promise starts in the pending state and may make a single transition to either a - fulfilled or failed state. - -

      This class is based on the Promise/A proposal from CommonJS. Additional - functions are provided for API compatibility with Dojo Deferred objects.

      Static Properties

      \ No newline at end of file + fulfilled or rejected state, at which point the promise is considered + resolved.

      Static Properties

      \ No newline at end of file diff --git a/docs/class_webdriver_stacktrace_Frame.html b/docs/class_webdriver_stacktrace_Frame.html index 68edecc..4b77d11 100644 --- a/docs/class_webdriver_stacktrace_Frame.html +++ b/docs/class_webdriver_stacktrace_Frame.html @@ -1,8 +1,20 @@ -webdriver.stacktrace.Frame

      Class webdriver.stacktrace.Frame

      code »

      Class representing one stack frame.

      Constructor

      webdriver.stacktrace.Frame ( context, name, alias, path )
      Parameters
      context: (string|undefined)
      Context object, empty in case of global - functions or if the browser doesn't provide this information.
      name: (string|undefined)
      Function name, empty in case of anonymous - functions.
      alias: (string|undefined)
      Alias of the function if available. For - example the function name will be 'c' and the alias will be 'b' if the - function is defined as a.b = function c() {};.
      path: (string|undefined)
      File path or URL including line number and - optionally column number separated by colons.
      Show:

      Instance Methods

      Returns
      The column number if known and -1 if it is unknown.
      Returns
      The line number if known or -1 if it is unknown.
      Returns
      The function name or empty string if the function is - anonymous and the object field which it's assigned to is unknown.
      Returns
      The url or empty string if it is unknown.
      Returns
      Whether the stack frame contains an anonymous function.

      Converts this frame to its string representation using V8's stack trace - format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi

      Returns
      The string representation of this frame.

      Instance Properties

      \ No newline at end of file +Frame

      class Frame

      Class representing one stack frame.

      +

      new Frame(context, name, alias, path)

      Parameters
      context(string|undefined)

      Context object, empty in case of global +functions or if the browser doesn't provide this information.

      +
      name(string|undefined)

      Function name, empty in case of anonymous +functions.

      +
      alias(string|undefined)

      Alias of the function if available. For +example the function name will be 'c' and the alias will be 'b' if the +function is defined as a.b = function c() {};.

      +
      path(string|undefined)

      File path or URL including line number and +optionally column number separated by colons.

      +

      Instance Methods

      getColumn()code »

      Returns
      number

      The column number if known and -1 if it is unknown.

      +

      getLine()code »

      Returns
      number

      The line number if known or -1 if it is unknown.

      +

      getName()code »

      Returns
      string

      The function name or empty string if the function is +anonymous and the object field which it's assigned to is unknown.

      +

      getUrl()code »

      Returns
      string

      The url or empty string if it is unknown.

      +

      isAnonymous()code »

      Returns
      boolean

      Whether the stack frame contains an anonymous function.

      +

      toString()code »

      Converts this frame to its string representation using V8's stack trace +format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi

      +
      Returns
      string

      The string representation of this frame.

      +

      Instance Properties

      columnnumber
      No description.
      \ No newline at end of file diff --git a/docs/class_webdriver_stacktrace_Snapshot.html b/docs/class_webdriver_stacktrace_Snapshot.html index 0484ea1..b623cdf 100644 --- a/docs/class_webdriver_stacktrace_Snapshot.html +++ b/docs/class_webdriver_stacktrace_Snapshot.html @@ -1,5 +1,6 @@ -webdriver.stacktrace.Snapshot

      Class webdriver.stacktrace.Snapshot

      code »

      Stores a snapshot of the stack trace at the time this instance was created. - The stack trace will always be adjusted to exclude this function call.

      Constructor

      webdriver.stacktrace.Snapshot ( opt_slice )
      Parameters
      opt_slice: number=
      The number of frames to remove from the top of - the generated stack trace.
      Show:

      Instance Methods

      Returns
      The parsed stack trace.

      Instance Properties

      The parsed stack trace. This list is lazily generated the first time it is - accessed.

      The error's stacktrace. This must be accessed immediately to ensure Opera - computes the context correctly.

      \ No newline at end of file +Snapshot

      class Snapshot

      Stores a snapshot of the stack trace at the time this instance was created. +The stack trace will always be adjusted to exclude this function call.

      +

      new Snapshot(opt_slice)

      Parameters
      opt_slicenumber=

      The number of frames to remove from the top of +the generated stack trace.

      +

      Instance Methods

      getStacktrace()code »

      Returns
      Array<webdriver.stacktrace.Frame>

      The parsed stack trace.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_testing_Assertion.html b/docs/class_webdriver_testing_Assertion.html index 8aee79e..b19a1b1 100644 --- a/docs/class_webdriver_testing_Assertion.html +++ b/docs/class_webdriver_testing_Assertion.html @@ -1,32 +1,99 @@ -webdriver.testing.Assertion

      Class webdriver.testing.Assertion

      code »

      Utility for performing assertions against a given value. If the - value is a webdriver.promise.Promise, this assertion will wait - for it to resolve before applying any matchers.

      Constructor

      webdriver.testing.Assertion ( value )
      Parameters
      value: *
      The value to wrap and apply matchers to.

      Classes

      webdriver.testing.Assertion.DelegatingMatcher_
      Wraps an object literal implementing the Matcher interface.
      Show:

      Instance Methods

      code »apply ( matcher, opt_message )webdriver.promise.Promise

      Asserts that the given matcher accepts the value wrapped by this - instance. If the wrapped value is a promise, this function will defer - applying the assertion until the value has been resolved. Otherwise, it - will be applied immediately.

      Parameters
      matcher: !goog.labs.testing.Matcher
      The matcher to apply
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The deferred assertion result, or - null if the assertion was immediately applied.
      code »closeTo ( value, range, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a number within a given distance of an - expected value.

      Parameters
      value: number
      The expected value.
      range: number
      The maximum amount the actual value is permitted to - differ from the expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »contains ( value, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string or array-like structure - containing the given value.

      Parameters
      value: *
      The expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »endsWith ( suffix, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string ending with the given suffix.

      Parameters
      suffix: string
      The expected suffix.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »equalTo ( value, opt_message )webdriver.promise.Promise

      Asserts that the value managed by this assertion is strictly equal to the - given value.

      Parameters
      value: *
      The expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number strictly - greater than value.

      Parameters
      value: number
      The minimum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number >= the given - value.

      Parameters
      value: number
      The minimum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the wrapped value is an instance of the given class.

      Parameters
      ctor: !Function
      The expected class constructor.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is strictly false.

      Returns
      The assertion result.

      Asserts that the wrapped value is null.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the wrapped value is null or undefined.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is strictly true.

      Returns
      The assertion result.

      Asserts that the wrapped value is undefined.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »lessThan ( value, opt_message )webdriver.promise.Promise

      Asserts that the value managed by this assertion is a number strictly less - than the given value.

      Parameters
      value: number
      The maximum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number <= the given - value.

      Parameters
      value: number
      The maximum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »matches ( regex, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string that matches the given RegExp.

      Parameters
      regex: !RegExp
      The regex to test.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »startsWith ( prefix, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string starting with the given prefix.

      Parameters
      prefix: string
      The expected prefix.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Instance Properties

      A self reference provided for writing fluent assertions: - webdriver.testing.assert(x).is.equalTo(y);

      Negates any matchers applied to this instance's value: - webdriver.testing.assert(x).not.equalTo(y);

      \ No newline at end of file +Assertion

      class Assertion

      Utility for performing assertions against a given value. If the +value is a webdriver.promise.Promise, this assertion will wait +for it to resolve before applying any matchers.

      +

      new Assertion(value)

      Parameters
      value*

      The value to wrap and apply matchers to.

      +

      Instance Methods

      apply(matcher, opt_message)code »

      Asserts that the given matcher accepts the value wrapped by this +instance. If the wrapped value is a promise, this function will defer +applying the assertion until the value has been resolved. Otherwise, it +will be applied immediately.

      +
      Parameters
      matchergoog.labs.testing.Matcher

      The matcher to apply

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The deferred assertion result, or +null if the assertion was immediately applied.

      +

      closeTo(value, range, opt_message)code »

      Asserts that the wrapped value is a number within a given distance of an +expected value.

      +
      Parameters
      valuenumber

      The expected value.

      +
      rangenumber

      The maximum amount the actual value is permitted to +differ from the expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      contains(value, opt_message)code »

      Asserts that the wrapped value is a string or array-like structure +containing the given value.

      +
      Parameters
      value*

      The expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      endsWith(suffix, opt_message)code »

      Asserts that the wrapped value is a string ending with the given suffix.

      +
      Parameters
      suffixstring

      The expected suffix.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      equalTo(value, opt_message)code »

      Asserts that the value managed by this assertion is strictly equal to the +given value.

      +
      Parameters
      value*

      The expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      greaterThan(value, opt_message)code »

      Asserts that the value managed by this assertion is a number strictly +greater than value.

      +
      Parameters
      valuenumber

      The minimum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      greaterThanEqualTo(value, opt_message)code »

      Asserts that the value managed by this assertion is a number >= the given +value.

      +
      Parameters
      valuenumber

      The minimum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      instanceOf(ctor, opt_message)code »

      Asserts that the wrapped value is an instance of the given class.

      +
      Parameters
      ctorFunction

      The expected class constructor.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isFalse()code »

      Asserts that the value managed by this assertion is strictly false.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isNull(opt_message)code »

      Asserts that the wrapped value is null.

      +
      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isNullOrUndefined(opt_message)code »

      Asserts that the wrapped value is null or undefined.

      +
      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isTrue()code »

      Asserts that the value managed by this assertion is strictly true.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isUndefined(opt_message)code »

      Asserts that the wrapped value is undefined.

      +
      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      lessThan(value, opt_message)code »

      Asserts that the value managed by this assertion is a number strictly less +than the given value.

      +
      Parameters
      valuenumber

      The maximum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      lessThanEqualTo(value, opt_message)code »

      Asserts that the value managed by this assertion is a number <= the given +value.

      +
      Parameters
      valuenumber

      The maximum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      matches(regex, opt_message)code »

      Asserts that the wrapped value is a string that matches the given RegExp.

      +
      Parameters
      regexRegExp

      The regex to test.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      startsWith(prefix, opt_message)code »

      Asserts that the wrapped value is a string starting with the given prefix.

      +
      Parameters
      prefixstring

      The expected prefix.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      Instance Properties

      iswebdriver.testing.Assertion

      A self reference provided for writing fluent assertions: +webdriver.testing.assert(x).is.equalTo(y);

      +
      notwebdriver.testing.NegatedAssertion

      Negates any matchers applied to this instance's value: +webdriver.testing.assert(x).not.equalTo(y);

      +

      Types

      Assertion.DelegatingMatcher_

      Wraps an object literal implementing the Matcher interface.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_testing_Assertion_DelegatingMatcher_.html b/docs/class_webdriver_testing_Assertion_DelegatingMatcher_.html index 22a3d65..1d6baea 100644 --- a/docs/class_webdriver_testing_Assertion_DelegatingMatcher_.html +++ b/docs/class_webdriver_testing_Assertion_DelegatingMatcher_.html @@ -1,3 +1,13 @@ -webdriver.testing.Assertion.DelegatingMatcher_

      Class webdriver.testing.Assertion.DelegatingMatcher_

      code »
      All implemented interfaces:
      goog.labs.testing.Matcher

      Wraps an object literal implementing the Matcher interface. This is used - to appease the Closure compiler, which will not treat an object literal as - implementing an interface.

      Constructor

      webdriver.testing.Assertion.DelegatingMatcher_ ( obj )
      Parameters
      obj: {matches: function(*): boolean, describe: function(): string}
      The object literal to delegate to.
      Show:

      Instance Methods

      code »matches ( value )
      Parameters
      value
      \ No newline at end of file +Assertion.DelegatingMatcher_

      class Assertion.DelegatingMatcher_

      All implemented interfaces
      goog.labs.testing.Matcher

      Wraps an object literal implementing the Matcher interface. This is used +to appease the Closure compiler, which will not treat an object literal as +implementing an interface.

      +

      new Assertion.DelegatingMatcher_(obj)

      Parameters
      obj{describe: function(): string, matches: function(*): boolean}

      The object literal to delegate to.

      +

      Instance Methods

      describe(value, opt_description)code »

      Describes why the matcher failed.

      +

      Specified by: goog.labs.testing.Matcher

      Parameters
      value*

      The value that didn't match.

      +
      opt_descriptionstring=

      A partial description to which the reason +will be appended.

      +
      Returns
      string

      Description of why the matcher failed.

      +

      matches(value)code »

      Determines whether a value matches the constraints of the match.

      +

      Specified by: goog.labs.testing.Matcher

      Parameters
      value*

      The object to match.

      +
      Returns
      boolean

      Whether the input value matches this matcher.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_testing_ContainsMatcher.html b/docs/class_webdriver_testing_ContainsMatcher.html index d56b8a3..898aed5 100644 --- a/docs/class_webdriver_testing_ContainsMatcher.html +++ b/docs/class_webdriver_testing_ContainsMatcher.html @@ -1 +1,11 @@ -webdriver.testing.ContainsMatcher

      Class webdriver.testing.ContainsMatcher

      code »
      All implemented interfaces:
      goog.labs.testing.Matcher

      Accepts strins or array-like structures that contain value.

      Constructor

      webdriver.testing.ContainsMatcher ( value )
      Parameters
      value: *
      The value to check for.
      Show:

      Instance Methods

      code »describe ( actualValue )string
      Parameters
      actualValue
      code »matches ( actualValue )boolean
      Parameters
      actualValue

      Instance Properties

      \ No newline at end of file +ContainsMatcher

      class ContainsMatcher

      All implemented interfaces
      goog.labs.testing.Matcher

      Accepts strins or array-like structures that contain value.

      +

      new ContainsMatcher(value)

      Parameters
      value*

      The value to check for.

      +

      Instance Methods

      describe(value, opt_description)code »

      Describes why the matcher failed.

      +

      Specified by: goog.labs.testing.Matcher

      Parameters
      value*

      The value that didn't match.

      +
      opt_descriptionstring=

      A partial description to which the reason +will be appended.

      +
      Returns
      string

      Description of why the matcher failed.

      +

      matches(value)code »

      Determines whether a value matches the constraints of the match.

      +

      Specified by: goog.labs.testing.Matcher

      Parameters
      value*

      The object to match.

      +
      Returns
      boolean

      Whether the input value matches this matcher.

      +
      \ No newline at end of file diff --git a/docs/class_webdriver_testing_NegatedAssertion.html b/docs/class_webdriver_testing_NegatedAssertion.html index 6a7d073..0bedb8f 100644 --- a/docs/class_webdriver_testing_NegatedAssertion.html +++ b/docs/class_webdriver_testing_NegatedAssertion.html @@ -1,26 +1,97 @@ -webdriver.testing.NegatedAssertion

      Class webdriver.testing.NegatedAssertion

      code »
      webdriver.testing.Assertion
      -  └ webdriver.testing.NegatedAssertion

      An assertion that negates any applied matchers.

      Constructor

      webdriver.testing.NegatedAssertion ( value )
      Parameters
      value: *
      The value to perform assertions on.
      Show:

      Instance Methods

      Defined in webdriver.testing.NegatedAssertion

      code »apply ( matcher, opt_message )(null|webdriver.promise.Promise)
      Parameters
      matcher
      opt_message

      Defined in webdriver.testing.Assertion

      code »closeTo ( value, range, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a number within a given distance of an - expected value.

      Parameters
      value: number
      The expected value.
      range: number
      The maximum amount the actual value is permitted to - differ from the expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »contains ( value, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string or array-like structure - containing the given value.

      Parameters
      value: *
      The expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »endsWith ( suffix, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string ending with the given suffix.

      Parameters
      suffix: string
      The expected suffix.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »equalTo ( value, opt_message )webdriver.promise.Promise

      Asserts that the value managed by this assertion is strictly equal to the - given value.

      Parameters
      value: *
      The expected value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number strictly - greater than value.

      Parameters
      value: number
      The minimum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number >= the given - value.

      Parameters
      value: number
      The minimum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the wrapped value is an instance of the given class.

      Parameters
      ctor: !Function
      The expected class constructor.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is strictly false.

      Returns
      The assertion result.

      Asserts that the wrapped value is null.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the wrapped value is null or undefined.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is strictly true.

      Returns
      The assertion result.

      Asserts that the wrapped value is undefined.

      Parameters
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »lessThan ( value, opt_message )webdriver.promise.Promise

      Asserts that the value managed by this assertion is a number strictly less - than the given value.

      Parameters
      value: number
      The maximum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Asserts that the value managed by this assertion is a number <= the given - value.

      Parameters
      value: number
      The maximum value.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »matches ( regex, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string that matches the given RegExp.

      Parameters
      regex: !RegExp
      The regex to test.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.
      code »startsWith ( prefix, opt_message )webdriver.promise.Promise

      Asserts that the wrapped value is a string starting with the given prefix.

      Parameters
      prefix: string
      The expected prefix.
      opt_message: string=
      A message to include if the matcher does not - accept the value wrapped by this assertion.
      Returns
      The assertion result.

      Instance Properties

      Defined in webdriver.testing.NegatedAssertion

      Defined in webdriver.testing.Assertion

      A self reference provided for writing fluent assertions: - webdriver.testing.assert(x).is.equalTo(y);

      Negates any matchers applied to this instance's value: - webdriver.testing.assert(x).not.equalTo(y);

      Static Properties

      \ No newline at end of file +NegatedAssertion

      class NegatedAssertion

      webdriver.testing.Assertion
      +  └ webdriver.testing.NegatedAssertion

      An assertion that negates any applied matchers.

      +

      new NegatedAssertion(value)

      Parameters
      value*

      The value to perform assertions on.

      +

      Instance Methods

      apply(matcher, opt_message)code »

      Asserts that the given matcher accepts the value wrapped by this +instance. If the wrapped value is a promise, this function will defer +applying the assertion until the value has been resolved. Otherwise, it +will be applied immediately.

      +

      Overrides: webdriver.testing.Assertion

      Parameters
      matchergoog.labs.testing.Matcher

      The matcher to apply

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The deferred assertion result, or +null if the assertion was immediately applied.

      +

      closeTo(value, range, opt_message)code »

      Asserts that the wrapped value is a number within a given distance of an +expected value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      valuenumber

      The expected value.

      +
      rangenumber

      The maximum amount the actual value is permitted to +differ from the expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      contains(value, opt_message)code »

      Asserts that the wrapped value is a string or array-like structure +containing the given value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      value*

      The expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      endsWith(suffix, opt_message)code »

      Asserts that the wrapped value is a string ending with the given suffix.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      suffixstring

      The expected suffix.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      equalTo(value, opt_message)code »

      Asserts that the value managed by this assertion is strictly equal to the +given value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      value*

      The expected value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      greaterThan(value, opt_message)code »

      Asserts that the value managed by this assertion is a number strictly +greater than value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      valuenumber

      The minimum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      greaterThanEqualTo(value, opt_message)code »

      Asserts that the value managed by this assertion is a number >= the given +value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      valuenumber

      The minimum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      instanceOf(ctor, opt_message)code »

      Asserts that the wrapped value is an instance of the given class.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      ctorFunction

      The expected class constructor.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isFalse()code »

      Asserts that the value managed by this assertion is strictly false.

      +

      Defined by: webdriver.testing.Assertion

      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isNull(opt_message)code »

      Asserts that the wrapped value is null.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isNullOrUndefined(opt_message)code »

      Asserts that the wrapped value is null or undefined.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isTrue()code »

      Asserts that the value managed by this assertion is strictly true.

      +

      Defined by: webdriver.testing.Assertion

      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      isUndefined(opt_message)code »

      Asserts that the wrapped value is undefined.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      lessThan(value, opt_message)code »

      Asserts that the value managed by this assertion is a number strictly less +than the given value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      valuenumber

      The maximum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      lessThanEqualTo(value, opt_message)code »

      Asserts that the value managed by this assertion is a number <= the given +value.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      valuenumber

      The maximum value.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      matches(regex, opt_message)code »

      Asserts that the wrapped value is a string that matches the given RegExp.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      regexRegExp

      The regex to test.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      startsWith(prefix, opt_message)code »

      Asserts that the wrapped value is a string starting with the given prefix.

      +

      Defined by: webdriver.testing.Assertion

      Parameters
      prefixstring

      The expected prefix.

      +
      opt_messagestring=

      A message to include if the matcher does not +accept the value wrapped by this assertion.

      +
      Returns
      webdriver.promise.Promise

      The assertion result.

      +

      Instance Properties

      iswebdriver.testing.Assertion

      A self reference provided for writing fluent assertions: +webdriver.testing.assert(x).is.equalTo(y);

      +
      notwebdriver.testing.NegatedAssertion

      Negates any matchers applied to this instance's value: +webdriver.testing.assert(x).not.equalTo(y);

      +
      value*
      No description.
      \ No newline at end of file diff --git a/docs/class_webdriver_testing_TestCase.html b/docs/class_webdriver_testing_TestCase.html new file mode 100644 index 0000000..071792f --- /dev/null +++ b/docs/class_webdriver_testing_TestCase.html @@ -0,0 +1,76 @@ +webdriver.testing.TestCase

      Class webdriver.testing.TestCase

      code »
      goog.testing.TestCase
      +  └ webdriver.testing.TestCase

      Constructs a test case that synchronizes each test case with the singleton + webdriver.promise.ControlFlow.

      Constructor

      webdriver.testing.TestCase ( client, opt_name )
      Parameters
      client: !webdriver.testing.Client
      The test client to use for + reporting test results.
      opt_name: string=
      The name of the test case, defaults to + 'Untitled Test Case'.
      Show:

      Instance Methods

      Defined in webdriver.testing.TestCase

      Executes the next test inside its own webdriver.Application.

      code »logError ( name, opt_e )goog.testing.TestCase.Error
      Parameters
      name
      opt_e

      Executes a single test, scheduling each phase with the global application. + Each phase will wait for the application to go idle before moving on to the + next test phase. This function models the follow basic test flow: + + try { + this.setUp.call(test.scope); + test.ref.call(test.scope); + } catch (ex) { + onError(ex); + } finally { + try { + this.tearDown.call(test.scope); + } catch (e) { + onError(e); + } + }

      Parameters
      test: !goog.testing.TestCase.Test
      The test to run.
      onError: function(*)
      The function to call each time an error is + detected.
      Returns
      A promise that will be resolved when the + test has finished running.

      Defined in goog.testing.TestCase

      code »add ( test )

      Adds a new test to the test case.

      Parameters
      test: goog.testing.TestCase.Test
      The test to add.
      code »addNewTest ( name, ref, opt_scope )

      Creates and adds a new test. + + Convenience function to make syntax less awkward when not using automatic + test discovery.

      Parameters
      name: string
      The test name.
      ref: !Function
      Reference to the test function.
      opt_scope: !Object=
      Optional scope that the test function should be + called in.

      Adds any functions defined in the global scope that correspond to + lifecycle events for the test case. Overrides setUp, tearDown, setUpPage, + tearDownPage and runTests if they are defined.

      Adds any functions defined in the global scope that are prefixed with "test" + to the test case.

      Clears a timeout created by this.timeout().

      Parameters
      id: number
      A timeout id.

      Counts the number of files that were loaded for dependencies that are + required to run the test.

      Returns
      The number of files loaded.

      Creates a goog.testing.TestCase.Test from an auto-discovered + function.

      Parameters
      name: string
      The name of the function.
      ref: function(): void
      The auto-discovered function.
      Returns
      The newly created test.
      code »doError ( test, opt_e )

      Handles a test that failed.

      Parameters
      test: goog.testing.TestCase.Test
      The test that failed.
      opt_e: *=
      The exception object associated with the + failure or a string.

      Handles a test that passed.

      Parameters
      test: goog.testing.TestCase.Test
      The test that passed.

      Executes each of the tests.

      Finalizes the test case, called when the tests have finished executing.

      Returns the number of tests actually run in the test case, i.e. subtracting + any which are skipped.

      Returns
      The number of un-ignored tests.
      Returns
      The function name prefix used to auto-discover tests.
      Returns
      Time since the last batch of tests was started.

      Returns the number of tests contained in the test case.

      Returns
      The number of tests.
      code »getGlobals ( opt_prefix )!Array

      Gets list of objects that potentially contain test cases. For IE 8 and below, + this is the global "this" (for properties set directly on the global this or + window) and the RuntimeObject (for global variables and functions). For all + other browsers, the array simply contains the global this.

      Parameters
      opt_prefix: string=
      An optional prefix. If specified, only get things + under this prefix. Note that the prefix is only honored in IE, since it + supports the RuntimeObject: + http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx + TODO: Remove this option.
      Returns
      A list of objects that should be inspected.
      Returns
      The name of the test.

      Returns the number of script files that were loaded in order to run the test.

      Returns
      The number of script files.
      code »getReport ( opt_verbose )string

      Returns a string detailing the results from the test.

      Parameters
      opt_verbose: boolean=
      If true results will include data about all + tests, not just what failed.
      Returns
      The results from the test.

      Returns the amount of time it took for the test to run.

      Returns
      The run time, in milliseconds.

      Returns the test results object: a map from test names to a list of test + failures (if any exist).

      Returns
      Tests results object.

      Gets the tests.

      Returns
      The test array.

      Returns the current time.

      Returns
      HH:MM:SS.
      Returns
      Whether the test case is running inside the multi test + runner.
      Returns
      Whether the test was a success.
      code »log ( val )

      Logs an object to the console, if available.

      Parameters
      val: *
      The value to log. Will be ToString'd.

      Checks to see if the test should be marked as failed before it is run. + + If there was an error in setUpPage, we treat that as a failure for all tests + and mark them all as having failed.

      Parameters
      testCase: goog.testing.TestCase.Test
      The current test case.
      Returns
      Whether the test was marked as failed.

      Returns the current test and increments the pointer.

      Returns
      The current test case.
      Returns
      The current time in milliseconds, don't use goog.now as some + tests override it.

      Reorders the tests depending on the order field.

      Parameters
      tests: Array.<goog.testing.TestCase.Test>
      An array of tests to + reorder.
      code »pad_ ( number )string

      Pads a number to make it have a leading zero if it's less than 10.

      Parameters
      number: number
      The number to pad.
      Returns
      The resulting string.

      Resets the test case pointer, so that next returns the first test.

      Executes each of the tests. + Overridable by the individual test case. This allows test cases to defer + when the test is actually started. If overridden, finalize must be called + by the test to indicate it has finished.

      code »saveMessage ( message )

      Saves a message to the result set.

      Parameters
      message: string
      The message to save.
      code »setBatchTime ( batchTime )
      Parameters
      batchTime: number
      Time since the last batch of tests was started.

      Sets the callback function that should be executed when the tests have + completed.

      Parameters
      fn: Function
      The callback function.
      code »setTests ( tests )

      Sets the tests.

      Parameters
      tests: !Array.<goog.testing.TestCase.Test>
      A new test array.

      Gets called before every goog.testing.TestCase.Test is been executed. Can be + overridden to add set up functionality to each test.

      Gets called before any tests are executed. Can be overridden to set up the + environment for the whole test case.

      Can be overridden in test classes to indicate whether the tests in a case + should be run in that particular situation. For example, this could be used + to stop tests running in a particular browser, where browser support for + the class under test was absent.

      Returns
      Whether any of the tests in the case should be run.

      Gets called after every goog.testing.TestCase.Test has been executed. Can be + overriden to add tear down functionality to each test.

      Gets called after all tests have been executed. Can be overridden to tear + down the entire test case.

      code »timeout ( fn, time )number

      Calls a function after a delay, using the protected timeout.

      Parameters
      fn: Function
      The function to call.
      time: number
      Delay in milliseconds.
      Returns
      The timeout id.

      Trims a path to be only that after google3.

      Parameters
      path: string
      The path to trim.
      Returns
      The resulting string.

      Instance Properties

      Defined in webdriver.testing.TestCase

      code »client_ : !webdriver.testing.Client

      Defined in goog.testing.TestCase

      Time since the last batch of tests was started, if batchTime exceeds + #maxRunTime a timeout will be used to stop the tests blocking the + browser and a new batch will be started.

      Pointer to the current test.

      Exception object that was detected before a test runs.

      A name for the test case.

      Optional callback that will be executed when the test has finalized.

      The order to run the auto-discovered tests in.

      Object used to encapsulate the test results.

      Whether the test case is running.

      Timestamp for when the test was started.

      Whether the test case has ever tried to execute.

      Set of test names and/or indices to execute, or null if all tests should + be executed. + + Indices are included to allow automation tools to run a subset of the + tests without knowing the exact contents of the test file. + + Indices should only be used with SORTED ordering. + + Example valid values: +

        +
      • [testName] +
      • [testName1, testName2] +
      • [2] - will run the 3rd test in the order specified +
      • [1,3,5] +
      • [testName1, testName2, 3, 5] - will work +

        Array of test functions that can be executed.

        Static Properties

        \ No newline at end of file diff --git a/docs/class_webdriver_until_Condition.html b/docs/class_webdriver_until_Condition.html new file mode 100644 index 0000000..3f942fc --- /dev/null +++ b/docs/class_webdriver_until_Condition.html @@ -0,0 +1,7 @@ +Condition

        class Condition<OUT>

        finalstruct

        Defines a condition to

        +

        new Condition(message, fn)

        Parameters
        messagestring

        A descriptive error message. Should complete the +sentence "Waiting [...]"

        +
        fnfunction(webdriver.WebDriver): OUT

        The condition function to +evaluate on each iteration of the wait loop.

        +

        Instance Methods

        description()code »

        Returns
        string

        A description of this condition.

        +

        fn(arg0)code »

        Parameters
        arg0webdriver.WebDriver
        Returns
        OUT
        \ No newline at end of file diff --git a/docs/dossier.css b/docs/dossier.css index 53ce031..1a6a134 100644 --- a/docs/dossier.css +++ b/docs/dossier.css @@ -1 +1 @@ -article,details,main,nav,section,summary{display:block}html{font-size:10px}html,body{height:100%}body{margin:0;padding:0;font-size:1.2rem;font-size:1.2em;font-family:sans-serif;background:#fff;color:#424242}a{text-decoration:none}a[href]{color:#66f}a[href]:hover{text-decoration:underline}code{font-size:1.2rem}code.type,code.type a[href],code.type a.unresolved-link{font-weight:normal;color:#080}code.type a.unresolved-link{border-bottom:1px dotted #080}pre,p{margin:1rem 0}.args{color:#828282;font-weight:normal}h1{border-bottom:1px solid #8f8f8f;margin:.2rem 0 0 0;padding:.5rem 0 .1rem;font-size:2.5rem}h1 code{font-size:1.85rem}h1 .deprecation-notice{color:#828282;font-weight:normal;font-style:italic;font-size:.66em}table{border-collapse:collapse;border-spacing:0;margin:0;padding:0;width:100%}tr{border:0;margin:0;padding:0}th{border-bottom:1px solid #8f8f8f;text-align:left;padding-top:.5rem}td>dl{margin-left:1rem}summary:focus{outline:0}summary::-webkit-details-marker{display:none}a.source{float:right}header>dl{margin-left:0}header>dl,header>pre,header>a.source{margin-top:.5rem}header+*{clear:right}dl{margin:0 0 0 1.25rem}dt{font-weight:bold}dd{margin-left:1.25rem}dd+dt{margin-top:.5rem}.type-summary{border-top:1px solid #8f8f8f;border-left:1px solid #8f8f8f}.type-summary dl{padding:.5rem 0 0 1rem;margin-left:0}.member{font-weight:bold}.member.deprecation-notice{text-decoration:line-through}#sidenav-toggle,#menubutton{display:none}#topnav{position:absolute;top:0;right:0;left:0;height:4rem;background:#424242;color:#fff;font-weight:bold;z-index:1}#topnav>div{position:relative;height:4rem;width:100%}#searchbox{position:absolute;right:1.5rem}#searchbox div{display:table-cell;vertical-align:middle;height:4rem}#searchbox input{width:25rem;box-shadow:inset 1px 2px rgba(0,0,0,0.18);border-radius:15px;border:1px solid #0f0f0f;padding:.2rem .5rem}.ac-renderer{position:absolute;background:#fcfcfc;border:1px solid #424242;-moz-box-shadow:2px 2px 2px rgba(102,102,102,0.4);-webkit-box-shadow:2px 2px 2px rgba(102,102,102,0.4);box-shadow:2px 2px 2px rgba(102,102,102,0.4);z-index:2;width:30rem;overflow:auto}.ac-row{cursor:pointer;padding:.5rem}.ac-highlighted{font-weight:bold}.ac-active{background-color:rgba(82,82,82,0.1)}main{margin-top:4rem;padding-bottom:2.5rem;padding-left:2rem;width:-webkit-calc(75% - 2rem);width:calc(75% - 2rem);float:left}.ctor{padding:.5rem 0 .5rem .5rem}dl.public{border-left:.7rem solid rgba(34,139,34,0.6)}dl.protected{border-left:.7rem solid rgba(218,165,32,0.6)}dl.private{border-left:.7rem solid rgba(255,0,0,0.5)}.wrap-details{border-top:1px solid #8f8f8f;border-left:1px solid #8f8f8f;margin-bottom:1px;display:none}.wrap-details.ctor{padding:0;display:block}main.public .wrap-details.public,main.protected .wrap-details.protected,main.private .wrap-details.private{display:block}.wrap-details.public>div:first-child{border-left:.7rem solid rgba(34,139,34,0.6)}.wrap-details.protected>div:first-child{border-left:.7rem solid rgba(218,165,32,0.6)}.wrap-details.private>div:first-child{border-left:.7rem solid rgba(255,0,0,0.5)}.wrap-details.inv{display:block}main.public .wrap-details.inv.public,main.protected .wrap-details.inv.protected,main.private .wrap-details.inv.private{display:none}.wrap-details.inv.public{color:#828282;font-weight:normal}.wrap-details.inv.public>div:first-child{border-left:.7rem solid rgba(34,139,34,0.4)}.wrap-details.inv.protected{color:#828282;font-weight:normal}.wrap-details.inv.protected>div:first-child{border-left:.7rem solid rgba(218,165,32,0.4)}.wrap-details.inv.private{color:#828282;font-weight:normal}.wrap-details.inv.private>div:first-child{border-left:.7rem solid rgba(255,0,0,0.4)}#visibility-controls{float:right}label{cursor:pointer}label[for=show-public]>span{border:1px outset #228b22;border-radius:5px;background:rgba(34,139,34,0.6);margin:0 .3rem}label[for=show-protected]>span{border:1px outset #daa520;border-radius:5px;background:rgba(218,165,32,0.6);margin:0 .3rem}label[for=show-private]>span{border:1px outset #f00;border-radius:5px;background:rgba(255,0,0,0.5);margin:0 .3rem}details>summary{padding-top:.5rem;margin-left:1.25rem;padding-bottom:.5rem}details>summary p{margin-top:.5rem;margin-bottom:0}details>summary pre:last-child{margin-bottom:0}details.function>summary:before{content:'\2b';float:left;margin-left:-1rem}details.function[open]>summary:before{content:'\2212'}details.function[open]>:last-child{padding-bottom:.5rem}header div.deprecation-notice{margin-top:1rem}h2{font-size:1.8rem;margin-top:1.25rem;margin-bottom:.15rem}section h3{margin:.75rem 0 .25rem;font-size:1.4rem}section+section{margin-top:1.75rem}div.deprecation-notice{font-weight:bold;margin:.25rem 0}h1 span.deprecation-notice,span.deprecation-reason{font-weight:normal;font-style:italic}.info{padding:0 1.25rem}table.srcfile,table.licensefile{border:1px solid #8f8f8f;font-size:1.1rem;width:auto}table.srcfile tr:first-child td,table.licensefile tr:first-child td{padding-top:.5rem}table.srcfile tr:last-child td,table.licensefile tr:last-child td{padding-bottom:.5rem}.licensefile td,.srcfile td,.srcfile a{color:#424242}.licensefile td,.srcfile td{padding:.15rem 1rem;background-color:#f4f4f4}.srcfile td:first-child{text-align:right;padding:0 .5rem 0 1rem;border-right:1px solid #8f8f8f;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:-moz-none;-ms-user-select:none;user-select:none}#sidenav{width:24%;margin-top:4rem;float:right;overflow:hidden;line-height:1.5;font-size:1.2rem;background:#fff}#sidenav input{display:none}#sidenav>a[href]{color:#fff}#sidenav h4{color:#fff;width:100%;font-size:1.4rem;font-weight:500;padding:.5rem 1.2rem 0;height:3rem;margin:0;border-top:1px solid rgba(255,255,255,0.2);border-bottom:1px solid #292929;background:#424242}#sidenav h4:hover{background:#545454}#sidenav li{list-style:none}#sidenav li.link:hover{background:rgba(82,82,82,0.1)}#sidenav li ul{padding:0 0 0 1.5rem}#sidenav ul{margin:0;padding:0 0 0 1rem;overflow:hidden;-webkit-transition:height .25s ease;-moz-transition:height .25s ease;-o-transition:height .25s ease;-ms-transition:height .25s ease;transition:height .25s ease}#main-wrapper{min-height:100%;height:100%;margin-bottom:-3rem}footer,#push-footer{clear:both;height:2rem}footer{font-size:1rem;background:#424242;padding:.5rem 0 .5rem 2.25rem;width:-webkit-calc(100% - 2.25rem);width:calc(100% - 2.25rem)}footer a[href]{color:rgba(255,255,255,0.8)} \ No newline at end of file +@import url(https://fonts.googleapis.com/css?family=Roboto+Mono:400,400italic,700,700italic|Roboto);body,html{min-height:100vh;height:100%}body a[id],h1 a[id],h2 a[id],h3 a[id]{display:inline-block}body,h3 .codelink,h5{font-size:1.4rem}nav .interface,table caption{font-style:italic}*{margin:0;padding:0}html{font-size:10px;position:relative}body{color:#333;font-family:Roboto,sans-serif;line-height:1.6;word-wrap:break-word;background:#f0f0f0;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}body .function,body .property,body a[id]{padding-top:6.4rem;margin-top:-6.4rem}a,a:visited{color:#66f;text-decoration:none}a[href]:hover{text-decoration:underline}h1,h2,h3,h4,h5,h6{margin:1.5rem 0}h1,h2,h3{border-bottom:1px solid #8f8f8f}h1{font-size:2.5rem}h1 .function,h1 .property,h1 a[id]{padding-top:7.5rem;margin-top:-7.5rem}h1+.tags{margin-top:-1rem}h2{font-size:2.2rem;margin-top:4rem}h2 .function,h2 .property,h2 a[id]{padding-top:7.2rem;margin-top:-7.2rem}h3{font-size:1.8rem;border:0}h3 .function,h3 .property,h3 a[id]{padding-top:6.8rem;margin-top:-6.8rem}h3+.tags{margin-top:-1.5rem}h3 .codelink{font-weight:400;display:inline}.tags span,dt{font-weight:700}h3 .codelink:after{clear:none;display:none}h4 a[id],h5 a[id],h6 a[id]{display:inline-block}h3 code{font-size:75%}h4{font-size:1.6rem}h4 .function,h4 .property,h4 a[id]{padding-top:6.6rem;margin-top:-6.6rem}h5 .function,h5 .property,h5 a[id]{padding-top:6.4rem;margin-top:-6.4rem}h6{font-size:1.2rem}h6 .function,h6 .property,h6 a[id]{padding-top:6.2rem;margin-top:-6.2rem}.srcfile table,code,pre.inheritance{font-family:"Roboto Mono",Menlo,monospace;font-size:1.2rem}pre{background:#f8f8f8;border-radius:3px;border-left:.2rem solid #d2d2d2;line-height:1.45;word-wrap:normal;margin:1rem 3rem;padding:1rem;overflow:auto}pre.inheritance{border:0;background:initial;padding:0;margin:.5rem 0 1rem}h1+pre.inheritance{margin-top:-1rem}p{margin:0 0 1rem}li code,p code{background:#f8f8f8;padding:2px;border-radius:3px;border:1px solid #d2d2d2}pre code{border:0}.fn-details div:first-child{border-bottom:1px solid #c2c2c2;margin-top:.5rem}hr.fn-sep{border:0;border-top:1px solid #dcdcdc;width:50%;margin:2rem auto auto}.tags span{background:rgba(102,102,255,.25);border:1px solid #66f;border-radius:3px;color:#66f;font-size:75%;padding:2px 4px;line-height:1.4rem}.tags span+span{margin-left:2px}.function:target>div,.property:target>dl{border-left:3px solid rgba(102,102,255,.7);padding-left:9px;margin-left:-9pt}dt.deprecated{text-decoration:line-through}nav .current,nav .selectable:hover,nav :focus{text-decoration:underline}dt>code{font-weight:400;color:#66f;margin-left:.5rem}.ac-highlighted,.srcfile :target+a,nav .current{font-weight:700}dd{margin:0 0 0 3rem}ol,ul{margin:1rem 0 1rem 4rem}blockquote{margin:1rem 3rem}table{border-collapse:collapse;border-spacing:0;margin:0 auto}table tr{background:#fff;border-top:1px solid #ccc}table tr:nth-child(2n){background:#f8f8f8}td,th{padding:.6rem 1.3rem;border:1px solid #ddd}.parentlink{float:left}.codelink a{float:right}.codelink:after{clear:both;content:"";display:table}footer,header{background:#424242}header{position:fixed;top:0;left:0;right:0;height:5rem;box-shadow:0 1px 5px #888}header>div{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;height:100%}header input{height:3rem;width:30rem;padding:0 10px;border-radius:15px;border:0;-webkit-appearance:none;color:#fff;background:rgba(255,255,255,.25)}header input::-webkit-input-placeholder{color:#fff}header input::-moz-placeholder{color:#fff}header input:-ms-input-placeholder{color:#fff}header input:focus{background:#fff;color:#333;outline:0}.ac-renderer{position:absolute;background:#fff;border:1px solid #000;box-shadow:2px 2px 2px rgba(102,102,102,.4);z-index:2;overflow:auto;width:32rem;margin-left:-1rem}nav li,nav li label,nav li>a{text-overflow:ellipsis;overflow:hidden}.ac-row{cursor:pointer;padding:.5rem}.ac-active{background-color:rgba(82,82,82,.1)}@media (max-width:320px){.ac-renderer{width:30rem;margin:0}}@media (max-width:1000px){header>div{-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}}@media (min-width:1000px){header>div{width:100rem;margin:0 auto}}footer{padding:0;margin:0;font-size:1.1rem}footer div{display:table-cell;vertical-align:middle;height:4rem}footer a{padding-left:2.25rem}footer a,footer a:visited{color:rgba(255,255,255,.8)}#nav-modules,#nav-modules:checked~div,#nav-types,#nav-types:checked~div{display:none}#nav-modules~label h3:after,#nav-types~label h3:after{content:'\2335';display:inline-block;float:right;margin-right:1rem;-webkit-transform:rotate(0);transform:rotate(0)}#nav-modules:checked~label h3:after,#nav-types:checked~label h3:after{float:right;margin-right:1rem;-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}nav{margin:0 0 3rem 1rem;font-size:1.3rem;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}nav .selectable,nav a{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}nav :focus{outline:0}nav ul{margin:0;padding:0}nav ul.nav-tree{margin:0 0 0 .3rem;padding:0 0 0 .5rem;border-left:1px dotted rgba(66,66,66,.5)}nav li{list-style:none;word-wrap:initial}nav li input{display:none}nav li input~ul.nav-tree>li{display:block}nav li input:checked~ul.nav-tree>li{display:none}nav li label,nav li>a{display:block;width:100%;white-space:nowrap}nav li label.focused,nav li label:hover,nav li>a:focus,nav li>a:hover{background:rgba(102,102,255,.1)}nav li>a:before{visibility:hidden}nav li label:before,nav li>a:before{float:left;margin-right:1rem;content:'\2212';font-size:1.4rem}nav li input:checked+label:before{content:'\002b'}nav h3{font-size:2rem;border:0;border-top:1px solid #8f8f8f;margin-top:1.2rem}nav h3 a,nav h3 a:visited{color:#424242}nav>h3:first-child{border-top:0;margin-top:0}div.pre-footer>div,main{background:#fff}main{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}article{margin-top:2.5rem;padding:3rem}article.indexfile>h1,article.srcfile>h1{margin-top:0;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}article.indexfile>h1:hover,article.srcfile>h1:hover{overflow:visible}.srcfile div{width:100%;overflow:auto}.srcfile table{background:#f8f8f8;border-radius:3px;border-left:.2rem solid #d2d2d2;line-height:1.45;word-wrap:normal;white-space:pre;margin:1rem 0;width:100%}.srcfile td{padding:0 .8rem;border:0}.srcfile td:first-child{text-align:right;padding-left:.8rem}.srcfile tr{background:#f8f8f8;border:0}.srcfile tr:first-child td{padding-top:1rem}.srcfile tr:last-child td{padding-bottom:1rem}.srcfile tr.hilite{background:rgba(102,102,255,.1)}@media (max-width:1000px){main{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}article{padding:3rem 1rem}}@media (min-width:1000px){main{width:100rem;margin:0 auto}article{width:66rem}nav{margin-top:2.5rem;width:23rem;float:left;padding-top:3rem;padding-right:4rem}} \ No newline at end of file diff --git a/docs/dossier.js b/docs/dossier.js index b2ebb52..9fdece5 100644 --- a/docs/dossier.js +++ b/docs/dossier.js @@ -1,135 +1,86 @@ -(function(){var h,m=this;function aa(){} -function ba(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"== -b&&"undefined"==typeof a.call)return"object";return b}function r(a){return"array"==ba(a)}function ca(a){var b=ba(a);return"array"==b||"object"==b&&"number"==typeof a.length}function s(a){return"string"==typeof a}function da(a){return"number"==typeof a}function ea(a){return"function"==ba(a)}function fa(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}function ga(a){return a[ha]||(a[ha]=++ia)}var ha="closure_uid_"+(1E9*Math.random()>>>0),ia=0; -function ja(a,b,c){return a.call.apply(a.bind,arguments)}function ka(a,b,c){if(!a)throw Error();if(2")&&(a=a.replace(sa,">"));-1!=a.indexOf('"')&&(a=a.replace(ta,"""));return a}var qa=/&/g,ra=//g,ta=/\"/g,pa=/[&<>\"]/;function ua(a){return String(a).replace(/([-()\[\]{}+?*.$\^|,:#c?Math.max(0,a.length+c):c;if(s(a))return s(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:s(a)?a.charAt(c):a[c]}function Aa(a){return z.concat.apply(z,arguments)} -function Ba(a){var b=a.length;if(0=arguments.length?z.slice.call(a,b):z.slice.call(a,b,c)};var Ea,Fa,Ga,Ha,B;function Ia(){return m.navigator?m.navigator.userAgent:null}function Ja(){return m.navigator}Ha=Ga=Fa=Ea=!1;var Ka;if(Ka=Ia()){var La=Ja();Ea=0==Ka.lastIndexOf("Opera",0);Fa=!Ea&&(-1!=Ka.indexOf("MSIE")||-1!=Ka.indexOf("Trident"));Ga=!Ea&&-1!=Ka.indexOf("WebKit");Ha=!Ea&&!Ga&&!Fa&&"Gecko"==La.product}var C=Ea,D=Fa,E=Ha,F=Ga,Ma=Ja();B=-1!=(Ma&&Ma.platform||"").indexOf("Mac");var Na=!!Ja()&&-1!=(Ja().appVersion||"").indexOf("X11"); -function Oa(){var a=m.document;return a?a.documentMode:void 0}var Pa;a:{var Qa="",Ra;if(C&&m.opera)var Sa=m.opera.version,Qa="function"==typeof Sa?Sa():Sa;else if(E?Ra=/rv\:([^\);]+)(\)|;)/:D?Ra=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:F&&(Ra=/WebKit\/(\S+)/),Ra)var Ta=Ra.exec(Ia()),Qa=Ta?Ta[1]:"";if(D){var Ua=Oa();if(Ua>parseFloat(Qa)){Pa=String(Ua);break a}}Pa=Qa}var Va={}; -function G(a){var b;if(!(b=Va[a])){b=0;for(var c=na(String(Pa)).split("."),d=na(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f(0==q[1].length?0:parseInt(q[1],10))?1:0)||((0==p[2].length)<(0==q[2].length)?-1: -(0==p[2].length)>(0==q[2].length)?1:0)||(p[2]q[2]?1:0)}while(0==b)}b=Va[a]=0<=b}return b}var Wa=m.document,H=Wa&&D?Oa()||("CSS1Compat"==Wa.compatMode?parseInt(Pa,10):5):void 0;var Xa=!D||D&&9<=H;!E&&!D||D&&D&&9<=H||E&&G("1.9.1");D&&G("9");var Ya="H3";function Za(a,b){var c;c=a.className;c=s(c)&&c.match(/\S+/g)||[];for(var d=Da(arguments,1),e=c.length+d.length,f=c,g=0;g");c=c.join("")}c=a.createElement(c);d&&(s(d)?c.className=d:r(d)?Za.apply(null,[c].concat(d)):hb(c,d));2"+d,c.removeChild(c.firstChild)):c.innerHTML=d;if(1==c.childNodes.length)d=c.removeChild(c.firstChild);else for(d=e.createDocumentFragment();c.firstChild;)d.appendChild(c.firstChild);return d}function vb(a){return fa(a)?"zSoyz":String(a)}var wb={};/* - - Copyright 2008 Google Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -function N(a){return a&&a.va&&a.va===sb?a.content:String(a).replace(xb,yb)}var zb={"\x00":"�",'"':""","&":"&","'":"'","<":"<",">":">","\t":" ","\n":" ","\x0B":" ","\f":" ","\r":" "," ":" ","-":"-","/":"/","=":"=","`":"`","\u0085":"…","\u00a0":" ","\u2028":"
","\u2029":"
"};function yb(a){return zb[a]}var xb=/[\x00\x22\x26\x27\x3c\x3e]/g;function Ab(a){return'
        "} -function Bb(a){var b="";if(a.types.length){for(var b=b+""}return b} -function Cb(a){var b,c=0");if(c){b+=a.file.name?"
      • "+N(a.file.name)+"/
          ":"";for(var c=a.file.children,d=c.length,e=0;e":""}else a.file.name&&(b+='
        "};var Db=!D||D&&9<=H,Eb=D&&!G("9");!F||G("528");E&&G("1.9b")||D&&G("8")||C&&G("9.5")||F&&G("528");E&&!G("8")||D&&G("9");function O(){0!=Fb&&(Gb[ga(this)]=this)}var Fb=0,Gb={};O.prototype.Oa=!1;O.prototype.B=function(){if(!this.Oa&&(this.Oa=!0,this.e(),0!=Fb)){var a=ga(this);delete Gb[a]}};O.prototype.e=function(){if(this.bb)for(;this.bb.length;)this.bb.shift()()};function Hb(a){a&&"function"==typeof a.B&&a.B()};function P(a,b){this.type=a;this.currentTarget=this.target=b}h=P.prototype;h.e=function(){};h.B=function(){};h.G=!1;h.defaultPrevented=!1;h.nb=!0;h.stopPropagation=function(){this.G=!0};h.preventDefault=function(){this.defaultPrevented=!0;this.nb=!1};var Ib="change";function Jb(a){Jb[" "](a);return a}Jb[" "]=aa;function Q(a,b){a&&Kb(this,a,b)}x(Q,P);h=Q.prototype;h.target=null;h.relatedTarget=null;h.offsetX=0;h.offsetY=0;h.clientX=0;h.clientY=0;h.screenX=0;h.screenY=0;h.button=0;h.keyCode=0;h.charCode=0;h.ctrlKey=!1;h.altKey=!1;h.shiftKey=!1;h.metaKey=!1;h.O=null; -function Kb(a,b,c){var d=a.type=b.type;P.call(a,d);a.target=b.target||b.srcElement;a.currentTarget=c;if(c=b.relatedTarget){if(E){var e;a:{try{Jb(c.nodeName);e=!0;break a}catch(f){}e=!1}e||(c=null)}}else"mouseover"==d?c=b.fromElement:"mouseout"==d&&(c=b.toElement);a.relatedTarget=c;a.offsetX=F||void 0!==b.offsetX?b.offsetX:b.layerX;a.offsetY=F||void 0!==b.offsetY?b.offsetY:b.layerY;a.clientX=void 0!==b.clientX?b.clientX:b.pageX;a.clientY=void 0!==b.clientY?b.clientY:b.pageY;a.screenX=b.screenX||0; -a.screenY=b.screenY||0;a.button=b.button;a.keyCode=b.keyCode||0;a.charCode=b.charCode||("keypress"==d?b.keyCode:0);a.ctrlKey=b.ctrlKey;a.altKey=b.altKey;a.shiftKey=b.shiftKey;a.metaKey=b.metaKey;a.state=b.state;a.O=b;b.defaultPrevented&&a.preventDefault();delete a.G}h.stopPropagation=function(){Q.i.stopPropagation.call(this);this.O.stopPropagation?this.O.stopPropagation():this.O.cancelBubble=!0}; -h.preventDefault=function(){Q.i.preventDefault.call(this);var a=this.O;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,Eb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};h.e=function(){};var Lb="closure_listenable_"+(1E6*Math.random()|0);function Mb(a){try{return!(!a||!a[Lb])}catch(b){return!1}}var Nb=0;function Ob(a,b,c,d,e){this.K=a;this.ma=null;this.src=b;this.type=c;this.capture=!!d;this.ga=e;this.key=++Nb;this.U=this.ea=!1}function Pb(a){a.U=!0;a.K=null;a.ma=null;a.src=null;a.ga=null};function Qb(a){this.src=a;this.l={};this.da=0}Qb.prototype.add=function(a,b,c,d,e){var f=this.l[a];f||(f=this.l[a]=[],this.da++);var g=Rb(f,b,d,e);-1e.keyCode||void 0!=e.returnValue)){a:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(g){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.currentTarget;f;f=f.parentNode)e.push(f);for(var f=a.type,k=e.length-1;!c.G&&0<=k;k--)c.currentTarget=e[k],d&=bc(e[k],f,!0,c);for(k=0;!c.G&&k>>0);function Wb(a){return ea(a)?a:a[dc]||(a[dc]=function(b){return a.handleEvent(b)})};var ec=!!m.DOMTokenList,fc=ec?function(a){return a.classList}:function(a){a=a.className;return s(a)&&a.match(/\S+/g)||[]},S=ec?function(a,b){return a.classList.contains(b)}:function(a,b){var c=fc(a);return 0<=va(c,b)},gc=ec?function(a,b){a.classList.add(b)}:function(a,b){S(a,b)||(a.className+=0=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom:!1}; -T.prototype.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this};function kc(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}kc.prototype.X=function(){return new kc(this.left,this.top,this.width,this.height)};kc.prototype.contains=function(a){return a instanceof kc?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height}; -kc.prototype.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function U(a,b){var c=K(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""}function V(a,b){return U(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]} -function lc(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}D&&a.ownerDocument.body&&(a=a.ownerDocument,b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);return b} -function mc(a){if(D&&!(D&&8<=H))return a.offsetParent;var b=K(a),c=V(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(c=V(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null} -function nc(a){for(var b=new T(0,Infinity,Infinity,0),c=J(a),d=c.j.body,e=c.j.documentElement,f=jb(c.j);a=mc(a);)if(!(D&&0==a.clientWidth||F&&0==a.clientHeight&&a==d||a==d||a==e||"visible"==V(a,"overflow"))){var g=W(a),k;k=a;if(E&&!G("1.9")){var l=parseFloat(U(k,"borderLeftWidth"));if(oc(k))var n=k.offsetWidth-k.clientWidth-l-parseFloat(U(k,"borderRightWidth")),l=l+n;k=new I(l,parseFloat(U(k,"borderTopWidth")))}else k=new I(k.clientLeft,k.clientTop);g.x+=k.x;g.y+=k.y;b.top=Math.max(b.top,g.y);b.right= -Math.min(b.right,g.x+a.clientWidth);b.bottom=Math.min(b.bottom,g.y+a.clientHeight);b.left=Math.max(b.left,g.x)}d=f.scrollLeft;f=f.scrollTop;b.left=Math.max(b.left,d);b.top=Math.max(b.top,f);c=(c.j.parentWindow||c.j.defaultView||window).document;c="CSS1Compat"==c.compatMode?c.documentElement:c.body;c=new ab(c.clientWidth,c.clientHeight);b.right=Math.min(b.right,d+c.width);b.bottom=Math.min(b.bottom,f+c.height);return 0<=b.top&&0<=b.left&&b.bottom>b.top&&b.right>b.left?b:null} -function W(a){var b,c=K(a),d=V(a,"position"),e=E&&c.getBoxObjectFor&&!a.getBoundingClientRect&&"absolute"==d&&(b=c.getBoxObjectFor(a))&&(0>b.screenX||0>b.screenY),f=new I(0,0),g;b=c?K(c):document;g=!D||D&&9<=H||pb(J(b))?b.documentElement:b.body;if(a==g)return f;if(a.getBoundingClientRect)b=lc(a),a=qb(J(c)),f.x=b.left+a.x,f.y=b.top+a.y;else if(c.getBoxObjectFor&&!e)b=c.getBoxObjectFor(a),a=c.getBoxObjectFor(g),f.x=b.screenX-a.screenX,f.y=b.screenY-a.screenY;else{b=a;do{f.x+=b.offsetLeft;f.y+=b.offsetTop; -b!=a&&(f.x+=b.clientLeft||0,f.y+=b.clientTop||0);if(F&&"fixed"==V(b,"position")){f.x+=c.body.scrollLeft;f.y+=c.body.scrollTop;break}b=b.offsetParent}while(b&&b!=a);if(C||F&&"absolute"==d)f.y-=c.body.offsetTop;for(b=a;(b=mc(b))&&b!=c.body&&b!=g;)f.x-=b.scrollLeft,C&&"TR"==b.tagName||(f.y-=b.scrollTop)}return f}function pc(a,b){"number"==typeof a&&(a=(b?Math.round(a):a)+"px");return a} -function qc(a){var b=rc;if("none"!=V(a,"display"))return b(a);var c=a.style,d=c.display,e=c.visibility,f=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";a=b(a);c.display=d;c.position=f;c.visibility=e;return a}function rc(a){var b=a.offsetWidth,c=a.offsetHeight,d=F&&!b&&!c;return(void 0===b||d)&&a.getBoundingClientRect?(a=lc(a),new ab(a.right-a.left,a.bottom-a.top)):new ab(b,c)}function sc(a,b){a.style.display=b?"":"none"}function oc(a){return"rtl"==V(a,"direction")} -var tc=E?"MozUserSelect":F?"WebkitUserSelect":null;function uc(a){var b=a.getElementsByTagName("*");if(tc){var c="none";a.style[tc]=c;if(b){a=0;for(var d;d=b[a];a++)d.style[tc]=c}}else if(D||C)if(c="on",a.setAttribute("unselectable",c),b)for(a=0;d=b[a];a++)d.setAttribute("unselectable",c)} -function vc(a,b){if(/^\d+px?$/.test(b))return parseInt(b,10);var c=a.style.left,d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;a.style.left=b;var e=a.style.pixelLeft;a.style.left=c;a.runtimeStyle.left=d;return e}function wc(a,b){var c=a.currentStyle?a.currentStyle[b]:null;return c?vc(a,c):0}var xc={thin:2,medium:4,thick:6}; -function yc(a,b){if("none"==(a.currentStyle?a.currentStyle[b+"Style"]:null))return 0;var c=a.currentStyle?a.currentStyle[b+"Width"]:null;return c in xc?xc[c]:vc(a,c)}function zc(a){if(D&&!(D&&9<=H)){var b=yc(a,"borderLeft"),c=yc(a,"borderRight"),d=yc(a,"borderTop");a=yc(a,"borderBottom");return new T(d,c,a,b)}b=U(a,"borderLeftWidth");c=U(a,"borderRightWidth");d=U(a,"borderTopWidth");a=U(a,"borderBottomWidth");return new T(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))} -var Ac=/[^\d]+$/,Bc={cm:1,"in":1,mm:1,pc:1,pt:1},Cc={em:1,ex:1};function Dc(){var a=document.documentElement,b=V(a,"fontSize"),c;c=(c=b.match(Ac))&&c[0]||null;if(b&&"px"==c)return parseInt(b,10);if(D){if(c in Bc)return vc(a,b);if(a.parentNode&&1==a.parentNode.nodeType&&c in Cc)return a=a.parentNode,c=V(a,"fontSize"),vc(a,b==c?"1em":b)}c=kb("span",{style:"visibility:hidden;position:absolute;line-height:0;padding:0;margin:0;border:0;height:1em;"});a.appendChild(c);b=c.offsetHeight;ob(c);return b} -var Ec=/matrix\([0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, [0-9\.\-]+, ([0-9\.\-]+)p?x?, ([0-9\.\-]+)p?x?\)/;function Fc(a){this.c=a||[];this.Ub=!0}function Gc(a,b,c){var d=[];if(""!=a){a=ua(a);a=RegExp("(^|\\W+)"+a,"i");for(var e=0;ep?(p=u-p-1,p>q-5&&(p=q-5),l+=p,p=u):(l+=q,q+=5);l<6*g.length&&d.push({Rb:f,ob:l,index:e})}d.sort(function(a,b){var c=a.ob-b.ob;return 0!=c?c:a.index-b.index});a=[];for(y=0;y=a.k&&ca.k)c--;else if(a.Ma&&c==a.k){a.A(-1);break}else if(!a.tb||-1!=c&&c!=a.k)break;else c=b;if(a.A(c))break}}h.A=function(a){var b=Oc(this,a),c=this.c[b];return c&&this.R.za&&this.R.za(c)?!1:(this.D=a,this.p.A(a),-1!=b)}; -function Pc(a){var b=Oc(a,a.D);if(-1!=b){var b=a.c[b],c=a.aa,d=b.toString();if(c.S){var e=Uc(c,c.a.value,Vc(c.a)),f=Wc(c,c.a.value);c.Ob.test(d)||(d=d.replace(/[\s\xa0]+$/,"")+c.yb);c.Wb&&(0==e||/^[\s\xa0]*$/.test(f[e-1])||(d=" "+d),e==f.length-1&&(d+=" "));if(d!=f[e]){f[e]=d;d=c.a;(E||D&&G("9"))&&d.blur();d.value=f.join("");for(var g=0,k=0;k<=e;k++)g+=f[k].length;d.focus();e=g;f=c.a;d=e;Xc(f)?f.selectionStart=d:D&&(g=Yc(f),k=g[0],k.inRange(g[1])&&(d=Zc(f,d),k.collapse(!0),k.move("character",d),k.select())); -f=c.a;Xc(f)?f.selectionEnd=e:D&&(g=Yc(f),d=g[1],g[0].inRange(d)&&(e=Zc(f,e),f=Zc(f,Vc(f)),d.collapse(!0),d.moveEnd("character",e-f),d.select()))}}else c.a.value=d;c.Ja=!0;a.qb?(a.q=null,Rc(a)):a.u();a.dispatchEvent({type:"update",V:b});a.qb&&a.aa.update(!0);return!0}a.u();a.dispatchEvent({type:"update",V:null});return!1}h.u=function(){this.D=-1;this.q=null;this.k+=this.c.length;this.c=[];window.clearTimeout(this.J);this.J=null;this.p.u();this.dispatchEvent("suggestionsupdate");this.dispatchEvent(Nc)}; -function Rc(a){a.J||(a.J=window.setTimeout(t(a.u,a),100))}h.Ua=function(){return this.J?(window.clearTimeout(this.J),this.J=null,!0):!1};function Qc(a){a.Ua()||window.setTimeout(t(a.Ua,a),10)}h.e=function(){Jc.i.e.call(this);delete this.Va;this.p.B();this.aa.B();this.R=null};h.Ib=function(a,b,c){this.q==a&&this.Ha(b,c)}; -h.Ha=function(a,b){var c="object"==ba(b)&&b,d=(c?c.Yb():b)?Oc(this,this.D):-1;this.k+=this.c.length;this.c=a;for(var e=[],f=0;fc||c>=a.c.length?-1:c} -h.ta=function(a){var b=this.aa;b.ta.apply(b,arguments)};h.update=function(a){this.aa.update(a)};function $c(a,b){X.call(this);this.Q=a||1;this.W=b||m;this.ua=t(this.Sb,this);this.Ca=w()}x($c,X);h=$c.prototype;h.enabled=!1;h.f=null;h.Sb=function(){if(this.enabled){var a=w()-this.Ca;0=a||96<=a&&106>=a||65<=a&&90>=a||F&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function jd(a){if(E)a=kd(a);else if(B&&F)a:switch(a){case 93:a=91;break a}return a} -function kd(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};function ld(a,b){X.call(this);a&&md(this,a,b)}x(ld,X);h=ld.prototype;h.b=null;h.ia=null;h.Aa=null;h.ja=null;h.m=-1;h.F=-1;h.sa=!1; -var nd={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},od={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},pd=D||F&&G("525"),qd=B&&E;h=ld.prototype; -h.Eb=function(a){F&&(17==this.m&&!a.ctrlKey||18==this.m&&!a.altKey||B&&91==this.m&&!a.metaKey)&&(this.F=this.m=-1);-1==this.m&&(a.ctrlKey&&17!=a.keyCode?this.m=17:a.altKey&&18!=a.keyCode?this.m=18:a.metaKey&&91!=a.keyCode&&(this.m=91));pd&&!hd(a.keyCode,this.m,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.F=jd(a.keyCode),qd&&(this.sa=a.altKey))};h.Gb=function(a){this.F=this.m=-1;this.sa=a.altKey}; -h.handleEvent=function(a){var b=a.O,c,d,e=b.altKey;D&&"keypress"==a.type?(c=this.F,d=13!=c&&27!=c?b.keyCode:0):F&&"keypress"==a.type?(c=this.F,d=0<=b.charCode&&63232>b.charCode&&id(c)?b.charCode:0):C?(c=this.F,d=id(c)?b.keyCode:0):(c=b.keyCode||this.F,d=b.charCode||0,qd&&(e=this.sa),B&&63==d&&224==c&&(c=191));var f=c=jd(c),g=b.keyIdentifier;c?63232<=c&&c in nd?f=nd[c]:25==c&&a.shiftKey&&(f=9):g&&g in od&&(f=od[g]);a=f==this.m;this.m=f;b=new rd(f,d,a,b);b.altKey=e;this.dispatchEvent(b)}; -function md(a,b,c){a.ja&&a.detach();a.b=b;a.ia=R(a.b,"keypress",a,c);a.Aa=R(a.b,"keydown",a.Eb,c,a);a.ja=R(a.b,"keyup",a.Gb,c,a)}h.detach=function(){this.ia&&(ac(this.ia),ac(this.Aa),ac(this.ja),this.ja=this.Aa=this.ia=null);this.b=null;this.F=this.m=-1};h.e=function(){ld.i.e.call(this);this.detach()};function rd(a,b,c,d){d&&Kb(this,d,void 0);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c}x(rd,Q);var sd,td;td=sd=!1;var ud=Ia();ud&&(-1!=ud.indexOf("Firefox")||-1!=ud.indexOf("Camino")||(-1!=ud.indexOf("iPhone")||-1!=ud.indexOf("iPod")?sd=!0:-1!=ud.indexOf("iPad")&&(td=!0)));var vd=sd,wd=td;function xd(a,b,c,d){O.call(this);d=d||150;this.S=null!=c?c:!0;this.ba=a||",;";this.yb=this.ba.substring(0,1);a=this.S?"[\\s"+this.ba+"]+":"[\\s]+";this.rb=RegExp("^"+a+"|"+a+"$","g");this.Ob=RegExp("\\s*["+this.ba+"]$");this.Za=b||"";this.Lb=this.S;this.f=0=d.right)&&(k&=-2),132==(k&132)&&(g.y=d.bottom)&&(k&=-5),g.xd.right&&k&16&&(e.width=Math.max(e.width-(g.x+e.width-d.right),0),n|=4),g.x+e.width>d.right&&k&1&&(g.x=Math.max(d.right-e.width,d.left),n|=1),k&2&&(n=n|(g.xd.right?32:0)),g.y=d.top&&g.y+e.height>d.bottom&&k&32&&(e.height=Math.max(e.height-(g.y+e.height-d.bottom),0),n|=8),g.y+e.height>d.bottom&&k&4&&(g.y=Math.max(d.bottom-e.height,d.top),n|=2),k&8&&(n=n|(g.yd.bottom?128:0)),d=n):d=256;d&496||(g=f,f=E&&(B||Na)&&G("1.9"),g instanceof I?(d=g.x,g=g.y):(d=g,g=void 0),c.style.left= -pc(d,f),c.style.top=pc(g,f),b==e||b&&e&&b.width==e.width&&b.height==e.height||(d=pb(J(K(c))),!D||d&&G("8")?(c=c.style,E?c.MozBoxSizing="border-box":F?c.WebkitBoxSizing="border-box":c.boxSizing="border-box",c.width=Math.max(e.width,0)+"px",c.height=Math.max(e.height,0)+"px"):(b=c.style,d?(D?(d=wc(c,"paddingLeft"),f=wc(c,"paddingRight"),g=wc(c,"paddingTop"),k=wc(c,"paddingBottom"),d=new T(g,f,k,d)):(d=U(c,"paddingLeft"),f=U(c,"paddingRight"),g=U(c,"paddingTop"),k=U(c,"paddingBottom"),d=new T(parseFloat(g), -parseFloat(f),parseFloat(k),parseFloat(d))),c=zc(c),b.pixelWidth=e.width-c.left-d.left-d.right-c.right,b.pixelHeight=e.height-c.top-d.top-d.bottom-c.bottom):(b.pixelWidth=e.width,b.pixelHeight=e.height))));a.oa&&(a.b.style.visibility="visible")}}h.e=function(){this.b&&($b(this.b,"click",this.Qa,!1,this),$b(this.b,"mousedown",this.Sa,!1,this),$b(this.b,"mouseover",this.Ta,!1,this),this.w.removeNode(this.b),this.b=null,this.t=!1);Hb(this.N);this.Ga=null;Rd.i.e.call(this)}; -function Vd(a,b,c){if(3==b.nodeType){var d=null;r(c)&&1w()-this.pb)&&this.dispatchEvent({type:Kc,V:this.c[a].id})};function Zd(a,b){var c=new Fc(a),d=new Rd,e=new xd(null,null,!1),c=new Jc(c,d,e);e.d=c;e.ta(b);return c};/* - Copyright 2013 Jason Leyba - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -function $d(){var a=m.TYPES;ae(a);M("type-index")&&M("file-index")&&M("module-index")&&(be("file-index",a.files),ce("type-index",a.types),ce("module-index",a.modules,!0));de();setTimeout(la(ee,a),0);setTimeout(fe,0)}var ge=["init"],$=m;ge[0]in $||!$.execScript||$.execScript("var "+ge[0]);for(var he;ge.length&&(he=ge.shift());)ge.length||void 0===$d?$=$[he]?$[he]:$[he]={}:$[he]=$d; -var ie=function(){var a="./";za(document.getElementsByTagName("script"),function(b){b=b.src;var c=b.length;return"dossier.js"===b.substr(c-10)?(a=b.substr(0,c-10),!0):!1});return a}(); -function ee(a){function b(){var a=c[e.value];a&&(window.location.href=ie+a)}var c={},d=Aa(ya(a.files,function(a){var b="file://"+a.name;c[b]=a.href;return b}),ya(a.types,function(a){c[a.name]=a.href;return a.name}));A(a.modules,function(a){c[a.name]=a.href;d=Aa(d,a.name,ya(a.types||[],function(b){var d=a.name+"."+b.name;c[d]=b.href;return d}))});a=M("searchbox");R(a,"submit",function(a){a.preventDefault();a.stopPropagation();b();return!1});var e=a.getElementsByTagName("input")[0];a=Zd(d,e);a.$a=15; -R(a,"update",b)} -function ae(a){function b(a,b,c,d){R(a,Ib,function(){b.style.height=pc(a.checked?c:0,!0);je(d,a)})}if(M("sidenav")){M("sidenav-overview").href=ie+"index.html";var c=M("sidenav-types-ctrl"),d=ce("sidenav-types",a.types),e=M("sidenav-modules-ctrl"),f=ce("sidenav-modules",a.modules),g=M("sidenav-files-ctrl");a=be("sidenav-files",a.files);var k=Dc(),l=qc(d).height/k+"rem",n=qc(a).height/k+"rem",k=qc(f).height/k+"rem";c.checked=ke("dossier.typesList");g.checked=ke("dossier.filesList");e.checked=ke("dossier.modulesList"); -d.style.height=pc(c.checked?l:0,!0);a.style.height=pc(g.checked?n:0,!0);f.style.height=pc(e.checked?k:0,!0);b(c,d,l,"dossier.typesList");b(g,a,n,"dossier.filesList");b(e,f,k,"dossier.modulesList")}}function le(a,b){this.name=a;this.href=b||"";this.children=[]} -function me(a){function b(a){A(a.children,b);!a.href&&1===a.children.length&&a.children[0].children.length&&(a.name=(a.name?a.name+"/":"")+a.children[0].name,a.href=a.children[0].href,a.children=a.children[0].children)}var c=new le("");A(a,function(a){var b=a.name.split(/[\/\\]/),f=c;A(b,function(c,k){if(c)if(k===b.length-1)f.children.push(new le(c,a.href));else{var l=za(f.children,function(a){return a.name===c});l||(l=new le(c),f.children.push(l));f=l}})});b(c);return c} -function be(a,b){var c=M(a),d=c.querySelector("i");if(!b.length)return d;var e=me(b),e=ub(Cb,{file:e,I:ie});ob(d);c.appendChild(e);return e}function ce(a,b,c){a=M(a);var d=a.querySelector("i");return(b=ub(Bb,{types:b,I:ie,ya:!!c}))&&b.childNodes.length?(ob(d),a.appendChild(b),b):d} -function fe(){var a=document.getElementsByTagName("DETAILS");a.length&&!a[0].hasOwnProperty("open")&&A(a,function(a){function c(){(d=!d)?a.setAttribute("open",""):a.removeAttribute("open");A(a.childNodes,function(a){1===a.nodeType&&"SUMMARY"!==a.tagName.toUpperCase()&&sc(a,d)})}var d=!0;a.setAttribute("open","");R(a,"click",c);c()})}function je(a,b){window.localStorage&&(b.checked?window.localStorage.setItem(a,"1"):window.localStorage.removeItem(a))} -function ke(a){return window.localStorage?!!window.localStorage.getItem(a):!1}var ne="public",oe="protected",pe="private";function qe(a){return"dossier.visibility."+a} -function de(){function a(){var a=window.localStorage;a&&!a.getItem("dossier.visibility")&&(a.setItem("dossier.visibility","1"),a.setItem(qe(ne),"1"),a.removeItem(qe(oe)),a.removeItem(qe(pe)))}function b(a,b){var d=document.getElementById(a);if(d){var e=qe(b);d.checked=ke(e);c(d,b);R(d,Ib,function(){je(e,d);c(d,b)})}}function c(a,b){a.checked?gc(f,b):ic(f,b)}function d(a){function b(){for(var a=[],e=d=f=y=0,k=c.length;e>>0),ea=0;function fa(a,b,c){return a.call.apply(a.bind,arguments)} +function ga(a,b,c){if(!a)throw Error();if(2")&&(a=a.replace(za,">"));-1!=a.indexOf('"')&&(a=a.replace(Aa,"""));-1!=a.indexOf("'")&&(a=a.replace(Ba,"'"));-1!=a.indexOf("\x00")&&(a=a.replace(Ca,"�"));return a}var xa=/&/g,ya=//g,Aa=/"/g,Ba=/'/g,Ca=/\x00/g,wa=/[\x00&<>"']/; +function Da(a){return String(a).replace(/([-()\[\]{}+?*.$\^|,:#b?1:0};var B=Array.prototype,Fa=B.indexOf?function(a,b,c){return B.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(t(a))return t(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:t(a)?a.charAt(c):a[c]}function Ia(a,b){var c=Fa(a,b),d;(d=0<=c)&&B.splice.call(a,c,1);return d}function Ja(a){var b=a.length;if(0=arguments.length?B.slice.call(a,b):B.slice.call(a,b,c)};function La(a,b,c,d){var e=document.createElement("li"),f=document.createElement("a");f.classList.add("nav-item");f.textContent=a.getKey();f.tabIndex=2;a.b&&(f.href=b+a.b.href,a.b.interface&&f.classList.add("interface"));var g=a.G();if(g){var h=document.createElement("input");h.type="checkbox";h.id=d+a.getKey();e.appendChild(h);d=document.createElement("label");d.setAttribute("for",h.id);d.appendChild(f);a.b&&a.b.href===c&&d.classList.add("current");e.appendChild(d);var k=document.createElement("ul"); +k.classList.add("nav-tree");e.appendChild(k);g.forEach(function(a){k.appendChild(La(a,b,c,h.id+"."))})}else a.b&&a.b.href===c&&f.classList.add("current"),e.appendChild(f);return e}function Ma(a){if(a=a.G())a.sort(function(a,c){return a.getKey()c.getKey()?1:0}),a.forEach(Ma)} +function Na(a){var b=a.G();b&&(b.forEach(Na),b.filter(function(a){return null===a.b&&1==(a.a?a.a.length:0)}).forEach(function(b){a.removeChild(b);var d=Oa(b);d&&d.forEach(function(d){var f=b.getKey()+"."+d.getKey();d.f=f;Pa(a,d)})}),b.filter(function(a){return a.b&&!a.b.namespace&&!a.b.types&&(a.a?a.a.length:0)}).forEach(function(b){var d=Oa(b);d&&d.forEach(function(d){var f=b.getKey()+"."+d.getKey();d.f=f;Pa(a,d)})}))}function D(a,b){this.f=a;this.b=b;this.a=this.c=null}D.prototype.getKey=function(){return this.f}; +D.prototype.R=function(a){this.b=a};function Pa(a,b){a.a||(a.a=[]);b.c=a;a.a.push(b)}D.prototype.G=function(){return this.a};D.prototype.removeChild=function(a){a.c=null;Ia(this.a,a)};function Oa(a){var b=a.a;a.a&&(a.a.forEach(function(a){a.c=null}),a.a=null);return b}function Qa(a,b){return a.a?Ha(a.a,function(a){return a.f===b}):null} +function Ra(a,b){var c=new D("",null);a.forEach(function(a){if(b){var e;a.types?(e=Ra(a.types,!1),e.f=a.name,e.R(a)):e=new D(a.name,a);Pa(c,e)}else{var f=c;a.name.split(/\./).forEach(function(a){var b=Qa(f,a);b||(b=new D(a,null),Pa(f,b));f=b});f.R(a)}});Na(c);Ma(c);return c}function Sa(a,b,c){var d=Ta;d&&!ta(d,"/")&&(d+="/");a=Ra(a,c);var e=document.createElement("ul");a.G()&&a.G().forEach(function(a){e.appendChild(La(a,d,b,c?".nav-module:":".nav:"))});return e};function Ua(a){if(a.classList)return a.classList;a=a.className;return t(a)&&a.match(/\S+/g)||[]}function Va(a,b){var c;a.classList?c=a.classList.contains(b):(c=Ua(a),c=0<=Fa(c,b));return c}function Wa(a,b){a.classList?a.classList.add(b):Va(a,b)||(a.className+=0=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom:a.x>=this.left&&a.x<=this.right&&a.y>=this.top&&a.y<=this.bottom:!1}; +l.ceil=function(){this.top=Math.ceil(this.top);this.right=Math.ceil(this.right);this.bottom=Math.ceil(this.bottom);this.left=Math.ceil(this.left);return this};l.floor=function(){this.top=Math.floor(this.top);this.right=Math.floor(this.right);this.bottom=Math.floor(this.bottom);this.left=Math.floor(this.left);return this};l.round=function(){this.top=Math.round(this.top);this.right=Math.round(this.right);this.bottom=Math.round(this.bottom);this.left=Math.round(this.left);return this};function kb(a,b,c,d){this.left=a;this.top=b;this.width=c;this.height=d}l=kb.prototype;l.clone=function(){return new kb(this.left,this.top,this.width,this.height)};l.contains=function(a){return a instanceof kb?this.left<=a.left&&this.left+this.width>=a.left+a.width&&this.top<=a.top&&this.top+this.height>=a.top+a.height:a.x>=this.left&&a.x<=this.left+this.width&&a.y>=this.top&&a.y<=this.top+this.height}; +l.ceil=function(){this.left=Math.ceil(this.left);this.top=Math.ceil(this.top);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};l.floor=function(){this.left=Math.floor(this.left);this.top=Math.floor(this.top);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};l.round=function(){this.left=Math.round(this.left);this.top=Math.round(this.top);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function lb(a){this.a=a||[]}function mb(a,b,c){for(var d=[],e=0;ep?(p=da-p-1,p>w-5&&(p=w-5),k+=p,p=da):(k+=w,w+=5);k<6*g.length&&d.push({Da:f,ka:k,index:e})}d.sort(function(a,b){var c=a.ka-b.ka;return 0!=c?c:a.index-b.index});a=[];for(x=0;xparseFloat(a))?String(b):a}(),rb={}; +function N(a){var b;if(!(b=rb[a])){b=0;for(var c=ua(String(qb)).split("."),d=ua(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f=a)}var tb=n.document,ub=pb(),sb=!tb||!J||!ub&&F()?void 0:ub||("CSS1Compat"==tb.compatMode?parseInt(qb,10):5);var vb=!J||O(9),wb=!K&&!J||J&&O(9)||K&&N("1.9.1");J&&N("9");function P(a){return a?new xb(Q(a)):ka||(ka=new xb)}function yb(a){var b=document;return t(a)?b.getElementById(a):a}function zb(a,b){pa(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Ab?a.setAttribute(Ab[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})} +var Ab={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"};function Bb(a){var b=Cb(a);a=Db(a);return J&&N("10")&&a.pageYOffset!=b.scrollTop?new G(b.scrollLeft,b.scrollTop):new G(a.pageXOffset||b.scrollLeft,a.pageYOffset||b.scrollTop)} +function Cb(a){return L||"CSS1Compat"!=a.compatMode?a.body||a.documentElement:a.documentElement}function Db(a){return a.parentWindow||a.defaultView}function Eb(a,b,c){function d(c){c&&b.appendChild(t(c)?a.createTextNode(c):c)}for(var e=2;e");f=f.join("")}f=d.createElement(f);g&&(t(g)?f.className=g:r(g)?f.className=g.join(" "):zb(f,g));2=a.keyCode)a.keyCode=-1}catch(b){}};var Rb="closure_lm_"+(1E6*Math.random()|0),Sb={},Tb=0;function S(a,b,c,d,e){if(r(b)){for(var f=0;fe.keyCode||void 0!=e.returnValue)){a:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(g){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.a;f;f=f.parentNode)e.push(f);for(var f=a.type,h=e.length-1;!c.f&&0<=h;h--){c.a=e[h];var k=$b(e[h],f,!0,c),d=d&&k}for(h=0;!c.f&&h>>0);function Ub(a){if("function"==q(a))return a;a[bc]||(a[bc]=function(b){return a.handleEvent(b)});return a[bc]};function cc(a){y.call(this);this.b=a;this.a={}}v(cc,y);var dc=[];cc.prototype.f=function(a,b,c,d){r(b)||(b&&(dc[0]=b.toString()),b=dc);for(var e=0;e=a||96<=a&&106>=a||65<=a&&90>=a||L&&0==a)return!0;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return!0;default:return!1}}function hc(a){if(K)a=ic(a);else if(M&&L)a:switch(a){case 93:a=91;break a}return a} +function ic(a){switch(a){case 61:return 187;case 59:return 186;case 173:return 189;case 224:return 91;case 0:return 224;default:return a}};function jc(a,b){U.call(this);a&&kc(this,a,b)}v(jc,U);l=jc.prototype;l.I=null;l.O=null;l.V=null;l.P=null;l.s=-1;l.w=-1;l.T=!1; +var lc={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},mc={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},nc=J||L&&N("525"),oc=M&&K; +jc.prototype.a=function(a){L&&(17==this.s&&!a.ctrlKey||18==this.s&&!a.b||M&&91==this.s&&!a.metaKey)&&(this.w=this.s=-1);-1==this.s&&(a.ctrlKey&&17!=a.keyCode?this.s=17:a.b&&18!=a.keyCode?this.s=18:a.metaKey&&91!=a.keyCode&&(this.s=91));nc&&!fc(a.keyCode,this.s,a.l,a.ctrlKey,a.b)?this.handleEvent(a):(this.w=hc(a.keyCode),oc&&(this.T=a.b))};jc.prototype.b=function(a){this.w=this.s=-1;this.T=a.b}; +jc.prototype.handleEvent=function(a){var b=a.c,c,d,e=b.altKey;J&&"keypress"==a.type?(c=this.w,d=13!=c&&27!=c?b.keyCode:0):L&&"keypress"==a.type?(c=this.w,d=0<=b.charCode&&63232>b.charCode&&gc(c)?b.charCode:0):nb?(c=this.w,d=gc(c)?b.keyCode:0):(c=b.keyCode||this.w,d=b.charCode||0,oc&&(e=this.T),M&&63==d&&224==c&&(c=191));var f=c=hc(c),g=b.keyIdentifier;c?63232<=c&&c in lc?f=lc[c]:25==c&&a.l&&(f=9):g&&g in mc&&(f=mc[g]);this.s=f;a=new pc(f,d,0,b);a.b=e;V(this,a)}; +function kc(a,b,c){a.P&&qc(a);a.I=b;a.O=S(a.I,"keypress",a,c);a.V=S(a.I,"keydown",a.a,c,a);a.P=S(a.I,"keyup",a.b,c,a)}function qc(a){a.O&&(T(a.O),T(a.V),T(a.P),a.O=null,a.V=null,a.P=null);a.I=null;a.s=-1;a.w=-1}function pc(a,b,c,d){R.call(this,d);this.type="key";this.keyCode=a;this.i=b}v(pc,R);function W(a,b){var c=Q(a);return c.defaultView&&c.defaultView.getComputedStyle&&(c=c.defaultView.getComputedStyle(a,null))?c[b]||c.getPropertyValue(b)||"":""}function X(a,b){return W(a,b)||(a.currentStyle?a.currentStyle[b]:null)||a.style&&a.style[b]} +function rc(a){var b;try{b=a.getBoundingClientRect()}catch(c){return{left:0,top:0,right:0,bottom:0}}J&&a.ownerDocument.body&&(a=a.ownerDocument,b.left-=a.documentElement.clientLeft+a.body.clientLeft,b.top-=a.documentElement.clientTop+a.body.clientTop);return b} +function sc(a){if(J&&!O(8))return a.offsetParent;var b=Q(a),c=X(a,"position"),d="fixed"==c||"absolute"==c;for(a=a.parentNode;a&&a!=b;a=a.parentNode)if(11==a.nodeType&&a.host&&(a=a.host),c=X(a,"position"),d=d&&"static"==c&&a!=b.documentElement&&a!=b.body,!d&&(a.scrollWidth>a.clientWidth||a.scrollHeight>a.clientHeight||"fixed"==c||"absolute"==c||"relative"==c))return a;return null} +function tc(a){for(var b=new H(0,Infinity,Infinity,0),c=P(a),d=c.a.body,e=c.a.documentElement,f=Cb(c.a);a=sc(a);)if(!(J&&0==a.clientWidth||L&&0==a.clientHeight&&a==d)&&a!=d&&a!=e&&"visible"!=X(a,"overflow")){var g=Y(a),h=new G(a.clientLeft,a.clientTop);g.x+=h.x;g.y+=h.y;b.top=Math.max(b.top,g.y);b.right=Math.min(b.right,g.x+a.clientWidth);b.bottom=Math.min(b.bottom,g.y+a.clientHeight);b.left=Math.max(b.left,g.x)}d=f.scrollLeft;f=f.scrollTop;b.left=Math.max(b.left,d);b.top=Math.max(b.top,f);c=(Db(c.a)|| +window).document;c="CSS1Compat"==c.compatMode?c.documentElement:c.body;c=new A(c.clientWidth,c.clientHeight);b.right=Math.min(b.right,d+c.width);b.bottom=Math.min(b.bottom,f+c.height);return 0<=b.top&&0<=b.left&&b.bottom>b.top&&b.right>b.left?b:null}function Y(a){var b=Q(a),c=new G(0,0),d;d=b?Q(b):document;d=!J||O(9)||Hb(P(d))?d.documentElement:d.body;if(a==d)return c;a=rc(a);b=P(b);b=Bb(b.a);c.x=a.left+b.x;c.y=a.top+b.y;return c}function uc(a){"number"==typeof a&&(a=a+"px");return a} +function vc(a){var b=wc;if("none"!=X(a,"display"))return b(a);var c=a.style,d=c.display,e=c.visibility,f=c.position;c.visibility="hidden";c.position="absolute";c.display="inline";a=b(a);c.display=d;c.position=f;c.visibility=e;return a}function wc(a){var b=a.offsetWidth,c=a.offsetHeight,d=L&&!b&&!c;return(void 0===b||d)&&a.getBoundingClientRect?(a=rc(a),new A(a.right-a.left,a.bottom-a.top)):new A(b,c)}var xc=K?"MozUserSelect":L?"WebkitUserSelect":null; +function yc(a){var b=a.getElementsByTagName("*");if(xc){var c="none";a.style[xc]=c;if(b){a=0;for(var d;d=b[a];a++)d.style[xc]=c}}else if(J||nb)if(c="on",a.setAttribute("unselectable",c),b)for(a=0;d=b[a];a++)d.setAttribute("unselectable",c)}function zc(a,b){if(/^\d+px?$/.test(b))return parseInt(b,10);var c=a.style.left,d=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;a.style.left=b;var e=a.style.pixelLeft;a.style.left=c;a.runtimeStyle.left=d;return e} +function Ac(a,b){var c=a.currentStyle?a.currentStyle[b]:null;return c?zc(a,c):0}var Bc={thin:2,medium:4,thick:6};function Cc(a,b){if("none"==(a.currentStyle?a.currentStyle[b+"Style"]:null))return 0;var c=a.currentStyle?a.currentStyle[b+"Width"]:null;return c in Bc?Bc[c]:zc(a,c)} +function Dc(a){if(J&&!O(9)){var b=Cc(a,"borderLeft"),c=Cc(a,"borderRight"),d=Cc(a,"borderTop");a=Cc(a,"borderBottom");return new H(d,c,a,b)}b=W(a,"borderLeftWidth");c=W(a,"borderRightWidth");d=W(a,"borderTopWidth");a=W(a,"borderBottomWidth");return new H(parseFloat(d),parseFloat(c),parseFloat(a),parseFloat(b))};function Ec(a,b){return(b&4&&"rtl"==X(a,"direction")?b^2:b)&-5};function Fc(a,b){U.call(this);this.c=a||1;this.b=b||n;this.j=u(this.o,this);this.m=ha()}v(Fc,U);Fc.prototype.h=!1;Fc.prototype.a=null;Fc.prototype.o=function(){if(this.h){var a=ha()-this.m;0=a.a&&cc||c>=a.b.length?-1:c}l.update=function(a){this.J.update(a)};function Tc(a,b,c,d){U.call(this);this.v=a||document.body;this.j=P(this.v);this.qa=!a;this.b=null;this.D="";this.c=[];this.h=[];this.J=this.o=-1;this.l=!1;this.className="ac-renderer";this.oa="ac-highlighted";this.m=b||null;this.sa=null!=d?d:!0;this.ra=!!c}v(Tc,U);l=Tc.prototype;l.X=function(a,b,c){this.D=b;this.c=a;this.o=-1;this.J=ha();this.a=c;this.h=[];Uc(this)};l.u=function(){this.a&&Kb(this.a,null);this.l&&(this.l=!1,this.a&&Jb(this.a,"haspopup",!1),this.b.style.display="none")}; +function Vc(a){a.l||(a.l=!0,a.a&&(Ib(a.a,"combobox"),Jb(a.a,"autocomplete","list"),Jb(a.a,"haspopup",!0)),a.b.style.display="")} +function Wc(a,b){var c=0<=b&&b=b.right)&&(k&=-2),132==(k&132)&&(f.y=b.bottom)&&(k&=-5),f.xb.right&&(g.width=Math.min(b.right-f.x,h+g.width-b.left), +g.width=Math.max(g.width,0),m|=4)),f.x+g.width>b.right&&k&1&&(f.x=Math.max(b.right-g.width,b.left),m|=1),k&2&&(m=m|(f.xb.right?32:0)),f.yb.bottom&&(g.height=Math.min(b.bottom-f.y,h+g.height-b.top),g.height=Math.max(g.height,0),m|=8)),f.y+g.height>b.bottom&&k&4&&(f.y=Math.max(b.bottom-g.height,b.top),m|=2),k&8&&(m=m|(f.yb.bottom?128:0)),f=m):f=256;b=new kb(0,0,0, +0);b.left=c.x;b.top=c.y;b.width=e.width;b.height=e.height;f&496||(e=new G(b.left,b.top),e instanceof G?(c=e.x,e=e.y):(c=e,e=void 0),a.style.left=uc(c),a.style.top=uc(e),e=new A(b.width,b.height),d==e||d&&e&&d.width==e.width&&d.height==e.height||(d=e,c=Hb(P(Q(a))),!J||N("10")||c&&N("8")?(a=a.style,K?a.MozBoxSizing="border-box":L?a.WebkitBoxSizing="border-box":a.boxSizing="border-box",a.width=Math.max(d.width,0)+"px",a.height=Math.max(d.height,0)+"px"):(b=a.style,c?(J?(c=Ac(a,"paddingLeft"),e=Ac(a, +"paddingRight"),f=Ac(a,"paddingTop"),g=Ac(a,"paddingBottom"),c=new H(f,e,g,c)):(c=W(a,"paddingLeft"),e=W(a,"paddingRight"),f=W(a,"paddingTop"),g=W(a,"paddingBottom"),c=new H(parseFloat(f),parseFloat(e),parseFloat(g),parseFloat(c))),a=Dc(a),b.pixelWidth=d.width-a.left-c.left-c.right-a.right,b.pixelHeight=d.height-a.top-c.top-c.bottom-a.bottom):(b.pixelWidth=d.width,b.pixelHeight=d.height))))}} +function Yc(a,b,c){if(!a.K)if(3==b.nodeType){var d=null;r(c)&&1d;d++)e=2*d,b.nodeValue=c[e],f=a.j.a.createElement("B"),f.className=a.oa,f.appendChild(a.j.a.createTextNode(String(c[e+1]))),f=b.parentNode.insertBefore(f, +b.nextSibling),b.parentNode.insertBefore(a.j.a.createTextNode(""),f.nextSibling),b=f.nextSibling;b.nodeValue=Ka(c,2).join("");a.K=!0}else d&&Yc(a,b,d)}}else for(b=b.firstChild;b;)d=b.nextSibling,Yc(a,b,c),b=d}function $c(a){var b="";if(!a)return b;r(a)&&(a=Ga(a,function(a){return!/^[\s\xa0]*$/.test(null==a?"":String(a))}));r(a)?b=0ha()-this.J)&&V(this,{type:Ic,C:this.c[a].id})};var bd=E("iPhone")&&!E("iPod")&&!E("iPad")||E("iPod"),cd=E("iPad");!E("Android")||ib()||E("Firefox")||gb();ib();function dd(a,b,c,d){y.call(this);d=d||150;this.c=null!=c?c:!0;this.i=a||",;";this.v=this.i.substring(0,1);a=this.c?"[\\s"+this.i+"]+":"[\\s]+";this.o=new RegExp("^"+a+"|"+a+"$","g");this.S=new RegExp("\\s*["+this.i+"]$");this.m=b||"";this.D=this.c;this.f=0c.a)d--;else break;if(c.B(d))break a}b.preventDefault();return}break;case 9:if(!a.a.c.l||b.l)a.a.u();else if(a.update(),Nc(a.a)&&a.D){b.preventDefault();return}break;case 13:if(a.a.c.l){if(a.update(),Nc(a.a)){b.preventDefault();b.stopPropagation();return}}else a.a.u();break;case 27:if(a.a.c.l){a.a.u(); +b.preventDefault();b.stopPropagation();return}break;case 229:if(!a.A){a.A||(a.b.f(a.g,"keyup",a.da),a.b.f(a.g,"keypress",a.ca),a.A=!0);return}}hd(a,b)}function hd(a,b){var c=a.c&&b.i&&-1!=a.i.indexOf(String.fromCharCode(b.i));c&&a.update();c&&Nc(a.a)&&b.preventDefault()}l.ya=function(){return!1};l.$=function(a){fd(this,a.target||null)}; +function fd(a,b){var c=a.h;pa(c.a,T);c.a={};a.a&&Oc(a.a);b!=a.g&&(a.g=b,a.f&&(c=a.f,c.h=!0,c.a||(c.a=c.b.setTimeout(c.j,c.c),c.m=ha()),a.b.f(a.f,Gc,a.ga)),a.W=a.g.value,kc(a.l,a.g),a.b.f(a.l,"key",a.ea),a.b.f(a.g,"mousedown",a.fa),J&&a.b.f(a.g,"keypress",a.ba))}l.wa=function(){ed?window.setTimeout(u(this.ha,this),0):this.ha()}; +l.ha=function(){if(this.g){this.b.i(this.l,"key",this.ea);qc(this.l);this.b.i(this.g,"keyup",this.ya);this.b.i(this.g,"mousedown",this.fa);J&&this.b.i(this.g,"keypress",this.ba);this.A&&id(this);this.g=null;if(this.f){var a=this.f;a.h=!1;a.a&&(a.b.clearTimeout(a.a),a.a=null);this.b.i(this.f,Gc,this.ga)}this.a&&Pc(this.a)}};l.ga=function(){this.update()};l.Ca=function(a){this.$(a)};l.ea=function(a){this.j=a.keyCode;this.a&&gd(this,a)};l.ca=function(){this.A&&229!=this.j&&id(this)}; +l.da=function(a){this.A&&(13==a.keyCode||77==a.keyCode&&a.ctrlKey)&&id(this)};l.fa=function(){};function id(a){a.A&&(a.A=!1,a.b.i(a.g,"keypress",a.ca),a.b.i(a.g,"keyup",a.da))}l.ba=function(a){hd(this,a)}; +l.update=function(a){if(this.g&&(a||this.g.value!=this.W)){if(a||!this.Y){var b;a=Lb(this.g)[0];b=this.g.value;a=Sc(this,b)[Rc(this,b,a)];b=this.o?String(a).replace(this.o,""):a;if(this.a&&(this.a.o=this.g,a=this.a,a.m!=b)){a.m=b;var c=a.v;b=a.m;var d=a.D,e=u(a.Ba,a),c=c.a,f;f=[];if(""!=b)for(var g=Da(b),g=new RegExp("(^|\\W+)"+g,"i"),h=0;h td a"+location.hash);c&&a(c)}S(window,"hashchange",function(){C(b.querySelectorAll("tr.hilite"),function(a){Ya(a,"hilite")});var c=b.querySelector("tr > td a:target");c&&a(c)})}} +function ld(a){function b(){var a=c[e.value];a&&(window.location.href=Ta+a)}var c={},d=[];a.types&&C(a.types,function(a){qd(d,c,a)});a.modules&&C(a.modules,function(a){qd(d,c,a,!0)});a=document.querySelector("header form");S(a,"submit",function(a){a.preventDefault();a.stopPropagation();b();return!1});var e=a.querySelector("input");e.setAttribute("title","Search ("+(M?"⌘":"Ctrl+")+"E)");a=jd(d,e);a.D=20;S(a,"update",b);S(document.documentElement,"keydown",function(a){if(document.activeElement!==e&& +69===a.keyCode&&(M?a.metaKey:a.ctrlKey))return e.focus(),a.preventDefault(),a.stopPropagation(),!1;document.activeElement===e&&27===a.keyCode&&e.blur()})} +function qd(a,b,c,d,e){var f=c.name;e&&(f=e+(ta(e,")")?" ":".")+f);b[f]=c.href;a.push(f);d&&(f="("+f+")");d&&c.types&&C(c.types,function(c){qd(a,b,c,!1,f)});c.statics&&C(c.statics,function(d){var e=c.href+"#"+d;d=ta(f,")")?f+" "+d:-1===d.indexOf(".")?f+"."+d:f+d.slice(d.lastIndexOf("."));b[d]=e;a.push(d)});c.members&&C(c.members,function(d){b[f+"#"+d]=c.href+"#"+d;a.push(f+"#"+d)})} +function md(a){function b(a){window.localStorage&&window.localStorage.setItem(a.id,a.checked?"closed":"open")}var c="";Ta?c=window.location.pathname.split("/").slice(Ta.split("/").length).join("/"):window.location.pathname&&"/"!==window.location.pathname&&(c=window.location.pathname.slice(window.location.pathname.lastIndexOf("/")+1));var d=yb("nav-types-view");d&&a.types&&d.appendChild(Sa(a.types,c,!1));(d=yb("nav-modules-view"))&&a.modules&&d.appendChild(Sa(a.modules,c,!0));a=document.querySelector("nav"); +S(a,"keydown",function(a){if(37===a.keyCode||39===a.keyCode||32===a.keyCode){var c=Gb(a.target,function(a){return"LABEL"===a.tagName});c&&(c=document.getElementById(c.getAttribute("for")))&&(32===a.keyCode?(c.checked=!c.checked,a.preventDefault()):c.checked=37===a.keyCode,b(c))}});S(a,["focus","blur"],function(a){if(a.target.classList.contains("nav-item")&&"LABEL"===a.target.parentNode.tagName){var b=a.target.parentNode;"focus"===a.type?b.classList.add("focused"):b.classList.remove("focused")}},!0); +window.localStorage&&(C(a.querySelectorAll('input[type="checkbox"][id]'),function(a){var b=window.localStorage.getItem(a.id);a.checked=!t(b)||"closed"===b}),S(a,"change",function(a){b(a.target)}))};;init();})(); diff --git a/docs/enum_bot_ErrorCode.html b/docs/enum_bot_ErrorCode.html index 69098d9..c2f43de 100644 --- a/docs/enum_bot_ErrorCode.html +++ b/docs/enum_bot_ErrorCode.html @@ -1,2 +1,3 @@ -bot.ErrorCode \ No newline at end of file +ErrorCode

        enum ErrorCode

        Typenumber

        Error codes from the Selenium WebDriver protocol: +https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes

        +

        Values and Descriptions

        ELEMENT_NOT_SELECTABLE
        ELEMENT_NOT_VISIBLE
        IME_ENGINE_ACTIVATION_FAILED
        IME_NOT_AVAILABLE
        INVALID_ELEMENT_COORDINATES
        INVALID_ELEMENT_STATE
        INVALID_SELECTOR_ERROR
        INVALID_XPATH_SELECTOR
        INVALID_XPATH_SELECTOR_RETURN_TYPE
        JAVASCRIPT_ERROR
        METHOD_NOT_ALLOWED
        MOVE_TARGET_OUT_OF_BOUNDS
        NO_SUCH_ALERT
        NO_SUCH_ELEMENT
        NO_SUCH_FRAME
        NO_SUCH_WINDOW
        SCRIPT_TIMEOUT
        SESSION_NOT_CREATED
        SQL_DATABASE_ERROR
        STALE_ELEMENT_REFERENCE
        SUCCESS
        TIMEOUT
        UNEXPECTED_ALERT_OPEN
        UNKNOWN_COMMAND
        UNKNOWN_ERROR
        UNSUPPORTED_OPERATION
        XPATH_LOOKUP_ERROR
        \ No newline at end of file diff --git a/docs/enum_bot_Error_State.html b/docs/enum_bot_Error_State.html index 49054c1..9038563 100644 --- a/docs/enum_bot_Error_State.html +++ b/docs/enum_bot_Error_State.html @@ -1 +1,2 @@ -bot.Error.State \ No newline at end of file +Error.State

        enum Error.State

        Typestring

        Status strings enumerated in the W3C WebDriver protocol.

        +

        Values and Descriptions

        ELEMENT_NOT_SELECTABLE
        ELEMENT_NOT_VISIBLE
        INVALID_ARGUMENT
        INVALID_ELEMENT_COORDINATES
        INVALID_ELEMENT_STATE
        INVALID_SELECTOR
        INVALID_SESSION_ID
        JAVASCRIPT_ERROR
        MOVE_TARGET_OUT_OF_BOUNDS
        NO_SUCH_ALERT
        NO_SUCH_ELEMENT
        NO_SUCH_FRAME
        NO_SUCH_WINDOW
        SCRIPT_TIMEOUT
        SESSION_NOT_CREATED
        STALE_ELEMENT_REFERENCE
        TIMEOUT
        UNEXPECTED_ALERT_OPEN
        UNKNOWN_COMMAND
        UNKNOWN_ERROR
        UNKNOWN_METHOD
        UNSUPPORTED_OPERATION
        \ No newline at end of file diff --git a/docs/enum_goog_Disposable_MonitoringMode.html b/docs/enum_goog_Disposable_MonitoringMode.html new file mode 100644 index 0000000..14cbf04 --- /dev/null +++ b/docs/enum_goog_Disposable_MonitoringMode.html @@ -0,0 +1,6 @@ +goog.Disposable.MonitoringMode

        Enum goog.Disposable.MonitoringMode

        code »
        Type: number

        Values and Descriptions

        INTERACTIVE
        INTERACTIVE mode can be switched on and off on the fly without producing + errors. It also doesn't warn if the disposable objects don't call the + goog.Disposable base constructor.
        OFF
        No monitoring.
        PERMANENT
        Creating and disposing the goog.Disposable instances is monitored. All + disposable objects need to call the goog.Disposable base + constructor. The PERMANENT mode must be switched on before creating any + goog.Disposable instances.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_dom_BrowserFeature.html b/docs/enum_goog_dom_BrowserFeature.html new file mode 100644 index 0000000..9aa63db --- /dev/null +++ b/docs/enum_goog_dom_BrowserFeature.html @@ -0,0 +1,8 @@ +goog.dom.BrowserFeature

        Enum goog.dom.BrowserFeature

        code »
        Type: boolean

        Enum of browser capabilities.

        Values and Descriptions

        CAN_ADD_NAME_OR_TYPE_ATTRIBUTES
        Whether attributes 'name' and 'type' can be added to an element after it's + created. False in Internet Explorer prior to version 9.
        CAN_USE_CHILDREN_ATTRIBUTE
        Whether we can use element.children to access an element's Element + children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment + nodes in the collection.)
        CAN_USE_INNER_TEXT
        Opera, Safari 3, and Internet Explorer 9 all support innerText but they + include text nodes in script and style tags. Not document-mode-dependent.
        CAN_USE_PARENT_ELEMENT_PROPERTY
        MSIE, Opera, and Safari>=4 support element.parentElement to access an + element's parent if it is an Element.
        INNER_HTML_NEEDS_SCOPED_ELEMENT
        Whether NoScope elements need a scoped element written before them in + innerHTML. + MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
        LEGACY_IE_RANGES
        Whether we use legacy IE range API.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_dom_NodeType.html b/docs/enum_goog_dom_NodeType.html new file mode 100644 index 0000000..d906098 --- /dev/null +++ b/docs/enum_goog_dom_NodeType.html @@ -0,0 +1,10 @@ +goog.dom.NodeType

        Enum goog.dom.NodeType

        code »
        Type: number

        Constants for the nodeType attribute in the Node interface. + + These constants match those specified in the Node interface. These are + usually present on the Node object in recent browsers, but not in older + browsers (specifically, early IEs) and thus are given here. + + In some browsers (early IEs), these are not defined on the Node object, + so they are provided here. + + See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_dom_TagName.html b/docs/enum_goog_dom_TagName.html new file mode 100644 index 0000000..7e46c2d --- /dev/null +++ b/docs/enum_goog_dom_TagName.html @@ -0,0 +1,2 @@ +goog.dom.TagName \ No newline at end of file diff --git a/docs/enum_goog_events_BrowserEvent_MouseButton.html b/docs/enum_goog_events_BrowserEvent_MouseButton.html new file mode 100644 index 0000000..01bc09b --- /dev/null +++ b/docs/enum_goog_events_BrowserEvent_MouseButton.html @@ -0,0 +1 @@ +goog.events.BrowserEvent.MouseButton

        Enum goog.events.BrowserEvent.MouseButton

        code »
        Type: number

        Normalized button constants for the mouse.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_events_BrowserFeature.html b/docs/enum_goog_events_BrowserFeature.html new file mode 100644 index 0000000..3bb8a01 --- /dev/null +++ b/docs/enum_goog_events_BrowserFeature.html @@ -0,0 +1,4 @@ +goog.events.BrowserFeature

        Enum goog.events.BrowserFeature

        code »
        Type: boolean

        Enum of browser capabilities.

        Values and Descriptions

        HAS_HTML5_NETWORK_EVENT_SUPPORT
        Whether HTML5 network online/offline events are supported.
        HAS_NAVIGATOR_ONLINE_PROPERTY
        Whether the navigator.onLine property is supported.
        HAS_W3C_BUTTON
        Whether the button attribute of the event is W3C compliant. False in + Internet Explorer prior to version 9; document-version dependent.
        HAS_W3C_EVENT_SUPPORT
        Whether the browser supports full W3C event model.
        HTML5_NETWORK_EVENTS_FIRE_ON_BODY
        Whether HTML5 network events fire on document.body, or otherwise the + window.
        SET_KEY_CODE_TO_PREVENT_DEFAULT
        To prevent default in IE7-8 for certain keydown events we need set the + keyCode to -1.
        TOUCH_ENABLED
        Whether touch is enabled in the browser.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_events_CaptureSimulationMode.html b/docs/enum_goog_events_CaptureSimulationMode.html new file mode 100644 index 0000000..81b2ea6 --- /dev/null +++ b/docs/enum_goog_events_CaptureSimulationMode.html @@ -0,0 +1,3 @@ +goog.events.CaptureSimulationMode

        Enum goog.events.CaptureSimulationMode

        code »
        Type: number

        Values and Descriptions

        OFF_AND_FAIL
        Does not perform capture simulation. Will asserts in IE8- when you + add capture listeners.
        OFF_AND_SILENT
        Does not perform capture simulation, silently ignore capture + listeners.
        ON
        Performs capture simulation.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_events_EventType.html b/docs/enum_goog_events_EventType.html new file mode 100644 index 0000000..0315e7f --- /dev/null +++ b/docs/enum_goog_events_EventType.html @@ -0,0 +1 @@ +goog.events.EventType \ No newline at end of file diff --git a/docs/enum_goog_events_KeyCodes.html b/docs/enum_goog_events_KeyCodes.html new file mode 100644 index 0000000..ffa4f95 --- /dev/null +++ b/docs/enum_goog_events_KeyCodes.html @@ -0,0 +1,22 @@ +goog.events.KeyCodes

        Enum goog.events.KeyCodes

        code »
        Type: number

        Key codes for common characters. + + This list is not localized and therefore some of the key codes are not + correct for non US keyboard layouts. See comments below.

        Values and Descriptions

        Show:

        Global Functions

        code »goog.events.KeyCodes.firesKeyPressEvent ( keyCode, opt_heldKeyCode, opt_shiftKey, opt_ctrlKey, opt_altKey )boolean

        Returns true if the key fires a keypress event in the current browser. + + Accoridng to MSDN [1] IE only fires keypress events for the following keys: + - Letters: A - Z (uppercase and lowercase) + - Numerals: 0 - 9 + - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ + - System: ESC, SPACEBAR, ENTER + + That's not entirely correct though, for instance there's no distinction + between upper and lower case letters. + + [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx) + + Safari is similar to IE, but does not fire keypress for ESC. + + Additionally, IE6 does not fire keydown or keypress events for letters when + the control or alt keys are held down and the shift key is not. IE7 does + fire keydown in these cases, though, but not keypress.

        Parameters
        keyCode: number
        A key code.
        opt_heldKeyCode: number=
        Key code of a currently-held key.
        opt_shiftKey: boolean=
        Whether the shift key is held down.
        opt_ctrlKey: boolean=
        Whether the control key is held down.
        opt_altKey: boolean=
        Whether the alt key is held down.
        Returns
        Whether it's a key that fires a keypress event.

        Returns true if the key produces a character. + This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).

        Parameters
        keyCode: number
        A key code.
        Returns
        Whether it's a character key.

        Returns true if the event contains a text modifying key.

        Parameters
        e: goog.events.BrowserEvent
        A key event.
        Returns
        Whether it's a text modifying key.

        Normalizes key codes from their Gecko-specific value to the general one.

        Parameters
        keyCode: number
        The native key code.
        Returns
        The normalized key code.

        Normalizes key codes from OS/Browser-specific value to the general one.

        Parameters
        keyCode: number
        The native key code.
        Returns
        The normalized key code.

        Normalizes key codes from their Mac WebKit-specific value to the general one.

        Parameters
        keyCode: number
        The native key code.
        Returns
        The normalized key code.
        \ No newline at end of file diff --git a/docs/enum_goog_net_XmlHttp_OptionType.html b/docs/enum_goog_net_XmlHttp_OptionType.html index d16e1ee..32b8726 100644 --- a/docs/enum_goog_net_XmlHttp_OptionType.html +++ b/docs/enum_goog_net_XmlHttp_OptionType.html @@ -1,4 +1,4 @@ -goog.net.XmlHttp.OptionType

        Enum goog.net.XmlHttp.OptionType

        code »
        Type: number

        Type of options that an XmlHttp object can have.

        Values and Descriptions

        LOCAL_REQUEST_ERROR
        NOTE(user): In IE if send() errors on a *local* request the readystate +goog.net.XmlHttp.OptionType

        Enum goog.net.XmlHttp.OptionType

        code »
        Type: number

        Type of options that an XmlHttp object can have.

        Values and Descriptions

        LOCAL_REQUEST_ERROR
        NOTE(user): In IE if send() errors on a *local* request the readystate is still changed to COMPLETE. We need to ignore it and allow the try/catch around send() to pick up the error.
        USE_NULL_FUNCTION
        Whether a goog.nullFunction should be used to clear the onreadystatechange - handler instead of null.
        Show:
        \ No newline at end of file + handler instead of null.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_net_XmlHttp_ReadyState.html b/docs/enum_goog_net_XmlHttp_ReadyState.html index 41635c9..7c4404e 100644 --- a/docs/enum_goog_net_XmlHttp_ReadyState.html +++ b/docs/enum_goog_net_XmlHttp_ReadyState.html @@ -1,3 +1,3 @@ -goog.net.XmlHttp.ReadyState

        Enum goog.net.XmlHttp.ReadyState

        code »
        Type: number

        Status constants for XMLHTTP, matches: +goog.net.XmlHttp.ReadyState

        Enum goog.net.XmlHttp.ReadyState

        code »
        Type: number

        Status constants for XMLHTTP, matches: http://msdn.microsoft.com/library/default.asp?url=/library/ - en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp

        Values and Descriptions

        COMPLETE
        Constant for when xmlhttprequest.readyState is completed
        INTERACTIVE
        Constant for when xmlhttprequest.readyState is in an interactive state.
        LOADED
        Constant for when xmlhttprequest.readyState is loaded.
        LOADING
        Constant for when xmlhttprequest.readyState is loading.
        UNINITIALIZED
        Constant for when xmlhttprequest.readyState is uninitialized
        Show:
        \ No newline at end of file + en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp

        Values and Descriptions

        COMPLETE
        Constant for when xmlhttprequest.readyState is completed
        INTERACTIVE
        Constant for when xmlhttprequest.readyState is in an interactive state.
        LOADED
        Constant for when xmlhttprequest.readyState is loaded.
        LOADING
        Constant for when xmlhttprequest.readyState is loading.
        UNINITIALIZED
        Constant for when xmlhttprequest.readyState is uninitialized
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_string_Unicode.html b/docs/enum_goog_string_Unicode.html index 9dd3b43..e0c1683 100644 --- a/docs/enum_goog_string_Unicode.html +++ b/docs/enum_goog_string_Unicode.html @@ -1 +1 @@ -goog.string.Unicode

        Enum goog.string.Unicode

        code »
        Type: string

        Common Unicode string characters.

        Values and Descriptions

        Show:
        \ No newline at end of file +goog.string.Unicode

        Enum goog.string.Unicode

        code »
        Type: string

        Common Unicode string characters.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_testing_TestCase_Order.html b/docs/enum_goog_testing_TestCase_Order.html new file mode 100644 index 0000000..efa7872 --- /dev/null +++ b/docs/enum_goog_testing_TestCase_Order.html @@ -0,0 +1,2 @@ +goog.testing.TestCase.Order

        Enum goog.testing.TestCase.Order

        code »
        Type: string

        The order to run the auto-discovered tests.

        Values and Descriptions

        NATURAL
        This is browser dependent and known to be different in FF and Safari + compared to others.
        RANDOM
        Random order.
        SORTED
        Sorted based on the name.
        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_uri_utils_CharCode_.html b/docs/enum_goog_uri_utils_CharCode_.html index 4c9fb9e..473287a 100644 --- a/docs/enum_goog_uri_utils_CharCode_.html +++ b/docs/enum_goog_uri_utils_CharCode_.html @@ -1 +1 @@ -goog.uri.utils.CharCode_

        Enum goog.uri.utils.CharCode_

        code »
        Type: number

        Character codes inlined to avoid object allocations due to charCode.

        Values and Descriptions

        Show:
        \ No newline at end of file +goog.uri.utils.CharCode_

        Enum goog.uri.utils.CharCode_

        code »
        Type: number

        Character codes inlined to avoid object allocations due to charCode.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_uri_utils_ComponentIndex.html b/docs/enum_goog_uri_utils_ComponentIndex.html index 82afbbb..61588fe 100644 --- a/docs/enum_goog_uri_utils_ComponentIndex.html +++ b/docs/enum_goog_uri_utils_ComponentIndex.html @@ -1 +1 @@ -goog.uri.utils.ComponentIndex

        Enum goog.uri.utils.ComponentIndex

        code »
        Type: number

        The index of each URI component in the return value of goog.uri.utils.split.

        Values and Descriptions

        Show:
        \ No newline at end of file +goog.uri.utils.ComponentIndex

        Enum goog.uri.utils.ComponentIndex

        code »
        Type: number

        The index of each URI component in the return value of goog.uri.utils.split.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_goog_uri_utils_StandardQueryParam.html b/docs/enum_goog_uri_utils_StandardQueryParam.html index 255b326..2ae6cf5 100644 --- a/docs/enum_goog_uri_utils_StandardQueryParam.html +++ b/docs/enum_goog_uri_utils_StandardQueryParam.html @@ -1 +1 @@ -goog.uri.utils.StandardQueryParam

        Enum goog.uri.utils.StandardQueryParam

        code »
        Type: string

        Standard supported query parameters.

        Values and Descriptions

        RANDOM
        Unused parameter for unique-ifying.
        Show:
        \ No newline at end of file +goog.uri.utils.StandardQueryParam

        Enum goog.uri.utils.StandardQueryParam

        code »
        Type: string

        Standard supported query parameters.

        Values and Descriptions

        RANDOM
        Unused parameter for unique-ifying.
        Show:
        \ No newline at end of file diff --git a/docs/enum_webdriver_Browser.html b/docs/enum_webdriver_Browser.html index a50d566..2b55b21 100644 --- a/docs/enum_webdriver_Browser.html +++ b/docs/enum_webdriver_Browser.html @@ -1 +1,2 @@ -webdriver.Browser

        Enum webdriver.Browser

        code »
        Type: string

        Recognized browser names.

        Values and Descriptions

        Show:
        \ No newline at end of file +Browser

        enum Browser

        Typestring

        Recognized browser names.

        +

        Values and Descriptions

        ANDROID
        CHROME
        FIREFOX
        HTMLUNIT
        IE
        INTERNET_EXPLORER
        IPAD
        IPHONE
        OPERA
        PHANTOM_JS
        SAFARI
        \ No newline at end of file diff --git a/docs/enum_webdriver_Button.html b/docs/enum_webdriver_Button.html index 7b2018b..492d103 100644 --- a/docs/enum_webdriver_Button.html +++ b/docs/enum_webdriver_Button.html @@ -1 +1,2 @@ -webdriver.Button

        Enum webdriver.Button

        code »
        Type: number

        Enumeration of the buttons used in the advanced interactions API.

        Values and Descriptions

        Show:
        \ No newline at end of file +Button

        enum Button

        Typenumber

        Enumeration of the buttons used in the advanced interactions API.

        +

        Values and Descriptions

        LEFT
        MIDDLE
        \ No newline at end of file diff --git a/docs/enum_webdriver_Capability.html b/docs/enum_webdriver_Capability.html index a16afd9..6a15c11 100644 --- a/docs/enum_webdriver_Capability.html +++ b/docs/enum_webdriver_Capability.html @@ -1,14 +1,33 @@ -webdriver.Capability

        Enum webdriver.Capability

        code »
        Type: string

        Common webdriver capability keys.

        Values and Descriptions

        ACCEPT_SSL_CERTS
        Indicates whether a driver should accept all SSL certs by default. This - capability only applies when requesting a new session. To query whether - a driver can handle insecure SSL certs, see - webdriver.Capability.SECURE_SSL.
        BROWSER_NAME
        The browser name. Common browser names are defined in the - webdriver.Browser enum.
        HANDLES_ALERTS
        Whether the driver is capable of handling modal alerts (e.g. alert, - confirm, prompt). To define how a driver should handle alerts, - use webdriver.Capability.UNEXPECTED_ALERT_BEHAVIOR.
        LOGGING_PREFS
        Key for the logging driver logging preferences.
        PLATFORM
        Describes the platform the browser is running on. Will be one of - ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When requesting a - session, ANY may be used to indicate no platform preference (this is - semantically equivalent to omitting the platform capability).
        PROXY
        Describes the proxy configuration to use for a new WebDriver session.
        ROTATABLE
        Whether the driver supports changing the brower's orientation.
        SECURE_SSL
        Whether a driver is only capable of handling secure SSL certs. To request - that a driver accept insecure SSL certs by default, use - webdriver.Capability.ACCEPT_SSL_CERTS.
        SUPPORTS_APPLICATION_CACHE
        Whether the driver supports manipulating the app cache.
        SUPPORTS_BROWSER_CONNECTION
        Whether the driver supports controlling the browser's internet - connectivity.
        SUPPORTS_CSS_SELECTORS
        Whether the driver supports locating elements with CSS selectors.
        SUPPORTS_JAVASCRIPT
        Whether the browser supports JavaScript.
        SUPPORTS_LOCATION_CONTEXT
        Whether the driver supports controlling the browser's location info.
        TAKES_SCREENSHOT
        Whether the driver supports taking screenshots.
        UNEXPECTED_ALERT_BEHAVIOR
        Defines how the driver should handle unexpected alerts. The value should - be one of "accept", "dismiss", or "ignore.
        VERSION
        Defines the browser version.
        Show:
        \ No newline at end of file +Capability

        enum Capability

        Typestring

        Common webdriver capability keys.

        +

        Values and Descriptions

        ACCEPT_SSL_CERTS

        Indicates whether a driver should accept all SSL certs by default. This +capability only applies when requesting a new session. To query whether +a driver can handle insecure SSL certs, see #SECURE_SSL.

        +
        BROWSER_NAME

        The browser name. Common browser names are defined in the +webdriver.Browser enum.

        +
        ELEMENT_SCROLL_BEHAVIOR

        Defines how elements should be scrolled into the viewport for interaction. +This capability will be set to zero (0) if elements are aligned with the +top of the viewport, or one (1) if aligned with the bottom. The default +behavior is to align with the top of the viewport.

        +
        HANDLES_ALERTS

        Whether the driver is capable of handling modal alerts (e.g. alert, +confirm, prompt). To define how a driver should handle alerts, +use #UNEXPECTED_ALERT_BEHAVIOR.

        +
        LOGGING_PREFS

        Key for the logging driver logging preferences.

        +
        NATIVE_EVENTS

        Whether this session generates native events when simulating user input.

        +
        PLATFORM

        Describes the platform the browser is running on. Will be one of +ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When requesting a +session, ANY may be used to indicate no platform preference (this is +semantically equivalent to omitting the platform capability).

        +
        PROXY

        Describes the proxy configuration to use for a new WebDriver session.

        +
        ROTATABLE

        Whether the driver supports changing the brower's orientation.

        +
        SECURE_SSL

        Whether a driver is only capable of handling secure SSL certs. To request +that a driver accept insecure SSL certs by default, use +#ACCEPT_SSL_CERTS.

        +
        SUPPORTS_APPLICATION_CACHE

        Whether the driver supports manipulating the app cache.

        +
        SUPPORTS_CSS_SELECTORS

        Whether the driver supports locating elements with CSS selectors.

        +
        SUPPORTS_JAVASCRIPT

        Whether the browser supports JavaScript.

        +
        SUPPORTS_LOCATION_CONTEXT

        Whether the driver supports controlling the browser's location info.

        +
        TAKES_SCREENSHOT

        Whether the driver supports taking screenshots.

        +
        UNEXPECTED_ALERT_BEHAVIOR

        Defines how the driver should handle unexpected alerts. The value should +be one of "accept", "dismiss", or "ignore.

        +
        VERSION

        Defines the browser version.

        +
        \ No newline at end of file diff --git a/docs/enum_webdriver_CommandName.html b/docs/enum_webdriver_CommandName.html index 51e2c1b..8cc80ea 100644 --- a/docs/enum_webdriver_CommandName.html +++ b/docs/enum_webdriver_CommandName.html @@ -1,2 +1,3 @@ -webdriver.CommandName

        Enum webdriver.CommandName

        code »
        Type: string

        Enumeration of predefined names command names that all command processors - will support.

        Values and Descriptions

        ACCEPT_ALERT
        ADD_COOKIE
        CLEAR_APP_CACHE
        CLEAR_ELEMENT
        CLEAR_LOCAL_STORAGE
        CLEAR_SESSION_STORAGE
        CLICK
        CLICK_ELEMENT
        CLOSE
        DELETE_ALL_COOKIES
        DELETE_COOKIE
        DESCRIBE_SESSION
        DISMISS_ALERT
        DOUBLE_CLICK
        ELEMENT_EQUALS
        EXECUTE_ASYNC_SCRIPT
        EXECUTE_SCRIPT
        EXECUTE_SQL
        FIND_CHILD_ELEMENT
        FIND_CHILD_ELEMENTS
        FIND_ELEMENT
        FIND_ELEMENTS
        GET
        GET_ACTIVE_ELEMENT
        GET_ALERT_TEXT
        GET_ALL_COOKIES
        GET_APP_CACHE
        GET_APP_CACHE_STATUS
        GET_AVAILABLE_LOG_TYPES
        GET_COOKIE
        GET_CURRENT_URL
        GET_CURRENT_WINDOW_HANDLE
        GET_ELEMENT_ATTRIBUTE
        GET_ELEMENT_LOCATION
        GET_ELEMENT_LOCATION_IN_VIEW
        GET_ELEMENT_SIZE
        GET_ELEMENT_TAG_NAME
        GET_ELEMENT_TEXT
        GET_ELEMENT_VALUE_OF_CSS_PROPERTY
        GET_LOCAL_STORAGE_ITEM
        GET_LOCAL_STORAGE_KEYS
        GET_LOCAL_STORAGE_SIZE
        GET_LOCATION
        GET_LOG
        GET_PAGE_SOURCE
        GET_SCREEN_ORIENTATION
        GET_SERVER_STATUS
        GET_SESSIONS
        GET_SESSION_LOGS
        GET_SESSION_STORAGE_ITEM
        GET_SESSION_STORAGE_KEYS
        GET_SESSION_STORAGE_SIZE
        GET_TITLE
        GET_WINDOW_HANDLES
        GET_WINDOW_POSITION
        GET_WINDOW_SIZE
        GO_BACK
        GO_FORWARD
        IMPLICITLY_WAIT
        IS_BROWSER_ONLINE
        IS_ELEMENT_DISPLAYED
        IS_ELEMENT_ENABLED
        IS_ELEMENT_SELECTED
        MAXIMIZE_WINDOW
        MOUSE_DOWN
        MOUSE_UP
        MOVE_TO
        NEW_SESSION
        QUIT
        REFRESH
        REMOVE_LOCAL_STORAGE_ITEM
        REMOVE_SESSION_STORAGE_ITEM
        SCREENSHOT
        SEND_KEYS_TO_ACTIVE_ELEMENT
        SEND_KEYS_TO_ELEMENT
        SET_ALERT_TEXT
        SET_BROWSER_ONLINE
        SET_LOCAL_STORAGE_ITEM
        SET_LOCATION
        SET_SCREEN_ORIENTATION
        SET_SCRIPT_TIMEOUT
        SET_SESSION_STORAGE_ITEM
        SET_TIMEOUT
        SET_WINDOW_POSITION
        SET_WINDOW_SIZE
        SUBMIT_ELEMENT
        SWITCH_TO_FRAME
        SWITCH_TO_WINDOW
        TOUCH_DOUBLE_TAP
        TOUCH_DOWN
        TOUCH_FLICK
        TOUCH_LONG_PRESS
        TOUCH_MOVE
        TOUCH_SCROLL
        TOUCH_SINGLE_TAP
        TOUCH_UP
        Show:
        \ No newline at end of file +CommandName

        enum CommandName

        Typestring

        Enumeration of predefined names command names that all command processors +will support.

        +

        Values and Descriptions

        ACCEPT_ALERT
        CLEAR_APP_CACHE
        CLEAR_ELEMENT
        CLEAR_LOCAL_STORAGE
        CLEAR_SESSION_STORAGE
        CLICK
        CLICK_ELEMENT
        CLOSE
        DELETE_ALL_COOKIES
        DESCRIBE_SESSION
        DISMISS_ALERT
        DOUBLE_CLICK
        ELEMENT_EQUALS
        EXECUTE_ASYNC_SCRIPT
        EXECUTE_SCRIPT
        EXECUTE_SQL
        FIND_CHILD_ELEMENT
        FIND_CHILD_ELEMENTS
        FIND_ELEMENT
        FIND_ELEMENTS
        GET
        GET_ACTIVE_ELEMENT
        GET_ALERT_TEXT
        GET_ALL_COOKIES
        GET_APP_CACHE
        GET_APP_CACHE_STATUS
        GET_AVAILABLE_LOG_TYPES
        GET_CURRENT_URL
        GET_CURRENT_WINDOW_HANDLE
        GET_ELEMENT_ATTRIBUTE
        GET_ELEMENT_LOCATION
        GET_ELEMENT_LOCATION_IN_VIEW
        GET_ELEMENT_SIZE
        GET_ELEMENT_TAG_NAME
        GET_ELEMENT_TEXT
        GET_ELEMENT_VALUE_OF_CSS_PROPERTY
        GET_LOCAL_STORAGE_ITEM
        GET_LOCAL_STORAGE_KEYS
        GET_LOCAL_STORAGE_SIZE
        GET_LOCATION
        GET_LOG
        GET_PAGE_SOURCE
        GET_SCREEN_ORIENTATION
        GET_SERVER_STATUS
        GET_SESSIONS
        GET_SESSION_LOGS
        GET_SESSION_STORAGE_ITEM
        GET_SESSION_STORAGE_KEYS
        GET_SESSION_STORAGE_SIZE
        GET_TITLE
        GET_WINDOW_HANDLES
        GET_WINDOW_POSITION
        GET_WINDOW_SIZE
        GO_BACK
        GO_FORWARD
        IMPLICITLY_WAIT
        IS_BROWSER_ONLINE
        IS_ELEMENT_DISPLAYED
        IS_ELEMENT_ENABLED
        IS_ELEMENT_SELECTED
        MAXIMIZE_WINDOW
        MOUSE_DOWN
        MOUSE_UP
        MOVE_TO
        NEW_SESSION
        QUIT
        REFRESH
        REMOVE_LOCAL_STORAGE_ITEM
        REMOVE_SESSION_STORAGE_ITEM
        SCREENSHOT
        SEND_KEYS_TO_ACTIVE_ELEMENT
        SEND_KEYS_TO_ELEMENT
        SET_ALERT_TEXT
        SET_BROWSER_ONLINE
        SET_LOCAL_STORAGE_ITEM
        SET_LOCATION
        SET_SCREEN_ORIENTATION
        SET_SCRIPT_TIMEOUT
        SET_SESSION_STORAGE_ITEM
        SET_TIMEOUT
        SET_WINDOW_POSITION
        SET_WINDOW_SIZE
        SUBMIT_ELEMENT
        SWITCH_TO_FRAME
        SWITCH_TO_WINDOW
        TOUCH_DOUBLE_TAP
        TOUCH_DOWN
        TOUCH_FLICK
        TOUCH_LONG_PRESS
        TOUCH_MOVE
        TOUCH_SCROLL
        TOUCH_SINGLE_TAP
        TOUCH_UP
        UPLOAD_FILE
        \ No newline at end of file diff --git a/docs/enum_webdriver_FirefoxDomExecutor_Attribute_.html b/docs/enum_webdriver_FirefoxDomExecutor_Attribute_.html index 991d820..e8dddad 100644 --- a/docs/enum_webdriver_FirefoxDomExecutor_Attribute_.html +++ b/docs/enum_webdriver_FirefoxDomExecutor_Attribute_.html @@ -1 +1 @@ -webdriver.FirefoxDomExecutor.Attribute_

        Enum webdriver.FirefoxDomExecutor.Attribute_

        code »
        Type: string

        Attributes used to communicate with the FirefoxDriver extension.

        Values and Descriptions

        Show:
        \ No newline at end of file +webdriver.FirefoxDomExecutor.Attribute_

        Enum webdriver.FirefoxDomExecutor.Attribute_

        code »
        Type: string

        Attributes used to communicate with the FirefoxDriver extension.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_webdriver_FirefoxDomExecutor_EventType_.html b/docs/enum_webdriver_FirefoxDomExecutor_EventType_.html index 77449eb..f59505e 100644 --- a/docs/enum_webdriver_FirefoxDomExecutor_EventType_.html +++ b/docs/enum_webdriver_FirefoxDomExecutor_EventType_.html @@ -1 +1 @@ -webdriver.FirefoxDomExecutor.EventType_

        Enum webdriver.FirefoxDomExecutor.EventType_

        code »
        Type: string

        Events used to communicate with the FirefoxDriver extension.

        Values and Descriptions

        Show:
        \ No newline at end of file +webdriver.FirefoxDomExecutor.EventType_

        Enum webdriver.FirefoxDomExecutor.EventType_

        code »
        Type: string

        Events used to communicate with the FirefoxDriver extension.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_webdriver_Key.html b/docs/enum_webdriver_Key.html index 04cb3f2..f10c503 100644 --- a/docs/enum_webdriver_Key.html +++ b/docs/enum_webdriver_Key.html @@ -1,9 +1,12 @@ -webdriver.Key

        Enum webdriver.Key

        code »
        Type: string

        Representations of pressable keys that aren't text. These are stored in - the Unicode PUA (Private Use Area) code points, 0xE000-0xF8FF. Refer to - http://www.google.com.au/search?&q=unicode+pua&btnG=Search

        Values and Descriptions

        Show:

        Global Functions

        Simulate pressing many keys at once in a "chord". Takes a sequence of - webdriver.Keys or strings, appends each of the values to a string, - and adds the chord termination key (webdriver.Key.NULL) and returns - the resultant string. - - Note: when the low-level webdriver key handlers see Keys.NULL, active - modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.

        Parameters
        var_args: ...string
        The key sequence to concatenate.
        Returns
        The null-terminated key sequence.
        \ No newline at end of file +Key

        enum Key

        Typestring

        Representations of pressable keys that aren't text. These are stored in +the Unicode PUA (Private Use Area) code points, 0xE000-0xF8FF. Refer to +http://www.google.com.au/search?&q=unicode+pua&btnG=Search

        +

        Values and Descriptions

        ADD
        ALT
        ARROW_DOWN
        ARROW_LEFT
        ARROW_RIGHT
        ARROW_UP
        BACK_SPACE
        CANCEL
        CLEAR
        COMMAND
        CONTROL
        DECIMAL
        DELETE
        DIVIDE
        DOWN
        END
        ENTER
        EQUALS
        ESCAPE
        F1
        F10
        F11
        F12
        F2
        F3
        F4
        F5
        F6
        F7
        F8
        F9
        HELP
        HOME
        INSERT
        LEFT
        META
        MULTIPLY
        NULL
        NUMPAD0
        NUMPAD1
        NUMPAD2
        NUMPAD3
        NUMPAD4
        NUMPAD5
        NUMPAD6
        NUMPAD7
        NUMPAD8
        NUMPAD9
        PAGE_DOWN
        PAGE_UP
        PAUSE
        RETURN
        SEMICOLON
        SEPARATOR
        SHIFT
        SPACE
        SUBTRACT
        TAB
        UP

        Functions

        Key.chord(var_args)code »

        Simulate pressing many keys at once in a "chord". Takes a sequence of +webdriver.Keys or strings, appends each of the values to a string, +and adds the chord termination key (webdriver.Key.NULL) and returns +the resultant string.

        +

        Note: when the low-level webdriver key handlers see Keys.NULL, active +modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.

        +
        Parameters
        var_args...string

        The key sequence to concatenate.

        +
        Returns
        string

        The null-terminated key sequence.

        +
        \ No newline at end of file diff --git a/docs/enum_webdriver_logging_Level.html b/docs/enum_webdriver_logging_Level.html index 96cce56..be4d555 100644 --- a/docs/enum_webdriver_logging_Level.html +++ b/docs/enum_webdriver_logging_Level.html @@ -1 +1 @@ -webdriver.logging.Level

        Enum webdriver.logging.Level

        code »
        Type: {value: number, name: webdriver.logging.LevelName}

        Logging levels.

        Values and Descriptions

        Show:
        \ No newline at end of file +webdriver.logging.Level

        Enum webdriver.logging.Level

        code »
        Type: {value: number, name: string}

        Logging levels.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/enum_webdriver_logging_Type.html b/docs/enum_webdriver_logging_Type.html index c69f777..af2f036 100644 --- a/docs/enum_webdriver_logging_Type.html +++ b/docs/enum_webdriver_logging_Type.html @@ -1 +1,6 @@ -webdriver.logging.Type

        Enum webdriver.logging.Type

        code »
        Type: string

        Common log types.

        Values and Descriptions

        BROWSER
        Logs originating from the browser.
        CLIENT
        Logs from a WebDriver client.
        DRIVER
        Logs from a WebDriver implementation.
        PERFORMANCE
        Logs related to performance.
        SERVER
        Logs from the remote server.
        Show:
        \ No newline at end of file +Type

        enum Type

        Typestring

        Values and Descriptions

        BROWSER

        Logs originating from the browser.

        +
        CLIENT

        Logs from a WebDriver client.

        +
        DRIVER

        Logs from a WebDriver implementation.

        +
        PERFORMANCE

        Logs related to performance.

        +
        SERVER

        Logs from the remote server.

        +
        \ No newline at end of file diff --git a/docs/enum_webdriver_promise_ControlFlow_EventType.html b/docs/enum_webdriver_promise_ControlFlow_EventType.html index 6947eb6..e5bc00a 100644 --- a/docs/enum_webdriver_promise_ControlFlow_EventType.html +++ b/docs/enum_webdriver_promise_ControlFlow_EventType.html @@ -1,4 +1,9 @@ -webdriver.promise.ControlFlow.EventType

        Enum webdriver.promise.ControlFlow.EventType

        code »
        Type: string

        Events that may be emitted by an webdriver.promise.ControlFlow.

        Values and Descriptions

        IDLE
        Emitted when all tasks have been successfully executed.
        SCHEDULE_TASK
        Emitted whenever a new task has been scheduled.
        UNCAUGHT_EXCEPTION
        Emitted whenever a control flow aborts due to an unhandled promise - rejection. This event will be emitted along with the offending rejection - reason. Upon emitting this event, the control flow will empty its task - queue and revert to its initial state.
        Show:
        \ No newline at end of file +ControlFlow.EventType

        enum ControlFlow.EventType

        Typestring

        Events that may be emitted by an webdriver.promise.ControlFlow.

        +

        Values and Descriptions

        IDLE

        Emitted when all tasks have been successfully executed.

        +
        RESET

        Emitted when a ControlFlow has been reset.

        +
        SCHEDULE_TASK

        Emitted whenever a new task has been scheduled.

        +
        UNCAUGHT_EXCEPTION

        Emitted whenever a control flow aborts due to an unhandled promise +rejection. This event will be emitted along with the offending rejection +reason. Upon emitting this event, the control flow will empty its task +queue and revert to its initial state.

        +
        \ No newline at end of file diff --git a/docs/enum_webdriver_promise_Deferred_State_.html b/docs/enum_webdriver_promise_Deferred_State_.html index 7468bf8..80ca09e 100644 --- a/docs/enum_webdriver_promise_Deferred_State_.html +++ b/docs/enum_webdriver_promise_Deferred_State_.html @@ -1 +1 @@ -webdriver.promise.Deferred.State_

        Enum webdriver.promise.Deferred.State_

        code »
        Type: number

        The three states a webdriver.promise.Deferred object may be in.

        Values and Descriptions

        Show:
        \ No newline at end of file +webdriver.promise.Deferred.State_

        Enum webdriver.promise.Deferred.State_

        code »
        Type: number

        The three states a webdriver.promise.Deferred object may be in.

        Values and Descriptions

        Show:
        \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 4d91ae5..64bb6d9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,60 +1,123 @@ -Index

        selenium-webdriver

        +Index

        selenium-webdriver

        +

        Selenium is a browser automation library. Most often used for testing +web-applications, Selenium may be used for any task that requires automating +interaction with the browser.

        Installation

        -

        Install the latest published version using npm:

        +

        Install via npm with

        npm install selenium-webdriver
         
        -

        In addition to the npm package, you will to download the WebDriver -implementations you wish to utilize. As of 2.34.0, selenium-webdriver -natively supports the ChromeDriver. -Simply download a copy and make sure it can be found on your PATH. The other -drivers (e.g. Firefox, Internet Explorer, and Safari), still require the -standalone Selenium server.

        -

        Running the tests

        -

        To run the tests, you will need to download a copy of the -ChromeDriver and make -sure it can be found on your PATH.

        -
        npm test selenium-webdriver
        -
        -

        To run the tests against multiple browsers, download the -Selenium server and -specify its location through the SELENIUM_SERVER_JAR environment variable. -You can use the SELENIUM_BROWSER environment variable to define a -comma-separated list of browsers you wish to test against. For example:

        -
        export SELENIUM_SERVER_JAR=path/to/selenium-server-standalone-2.33.0.jar
        -SELENIUM_BROWSER=chrome,firefox npm test selenium-webdriver
        -
        +

        Out of the box, Selenium includes everything you need to work with Firefox. You +will need to download additional components to work with the other major +browsers. The drivers for Chrome, IE, PhantomJS, and Opera are all standalone +executables that should be placed on your +PATH. The SafariDriver +browser extension should be installed in your browser before using Selenium; we +recommend disabling the extension when using the browser without Selenium or +installing the extension in a profile only used for testing.

        Usage

        -
        var webdriver = require('selenium-webdriver');
        -
        -var driver = new webdriver.Builder().
        -    withCapabilities(webdriver.Capabilities.chrome()).
        -    build();
        +

        The sample below and others are included in the example directory. You may +also find the tests for selenium-webdriver informative.

        +
        var webdriver = require('selenium-webdriver'),
        +    By = require('selenium-webdriver').By,
        +    until = require('selenium-webdriver').until;
         
        -driver.get('http://www.google.com');
        -driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
        -driver.findElement(webdriver.By.name('btnG')).click();
        -driver.wait(function() {
        -  return driver.getTitle().then(function(title) {
        -    return title === 'webdriver - Google Search';
        -  });
        -}, 1000);
        +var driver = new webdriver.Builder()
        +    .forBrowser('firefox')
        +    .build();
         
        +driver.get('http://www.google.com/ncr');
        +driver.findElement(By.name('q')).sendKeys('webdriver');
        +driver.findElement(By.name('btnG')).click();
        +driver.wait(until.titleIs('webdriver - Google Search'), 1000);
         driver.quit();
         
        +

        Using the Builder API

        +

        The Builder class is your one-stop shop for configuring new WebDriver +instances. Rather than clutter your code with branches for the various browsers, +the builder lets you set all options in one flow. When you call +Builder#build(), all options irrelevant to the selected browser are dropped:

        +
        var webdriver = require('selenium-webdriver'),
        +    chrome = require('selenium-webdriver/chrome'),
        +    firefox = require('selenium-webdriver/firefox');
        +
        +var driver = new webdriver.Builder()
        +    .forBrowser('firefox')
        +    .setChromeOptions(/* ... */)
        +    .setFirefoxOptions(/* ... */)
        +    .build();
        +
        +

        Why would you want to configure options irrelevant to the target browser? The +Builder's API defines your default configuration. You can change the target +browser at runtime through the SELENIUM_BROWSER environment variable. For +example, the example/google_search.js script is configured to run against +Firefox. You can run the example against other browsers just by changing the +runtime environment

        +
        # cd node_modules/selenium-webdriver
        +node example/google_search
        +SELENIUM_BROWSER=chrome node example/google_search
        +SELENIUM_BROWSER=safari node example/google_search
        +
        +

        The Standalone Selenium Server

        +

        The standalone Selenium Server acts as a proxy between your script and the +browser-specific drivers. The server may be used when running locally, but it's +not recommend as it introduces an extra hop for each request and will slow +things down. The server is required, however, to use a browser on a remote host +(most browser drivers, like the IEDriverServer, do not accept remote +connections).

        +

        To use the Selenium Server, you will need to install the +JDK and +download the latest server from Selenium. Once downloaded, run the +server with

        +
        java -jar selenium-server-standalone-2.45.0.jar
        +
        +

        You may configure your tests to run against a remote server through the Builder +API:

        +
        var driver = new webdriver.Builder()
        +    .forBrowser('firefox')
        +    .usingServer('http://localhost:4444/wd/hub')
        +    .build();
        +
        +

        Or change the Builder's configuration at runtime with the SELENIUM_REMOTE_URL +environment variable:

        +
        SELENIUM_REMOTE_URL="http://localhost:4444/wd/hub" node script.js
        +
        +

        You can experiment with these options using the example/google_search.js +script provided with selenium-webdriver.

        Documentation

        -

        Full documentation is available on the Selenium project wiki.

        +

        API documentation is included in the docs directory and is also available +online from the Selenium project. Addition resources include

        + +

        Contributing

        +

        Contributions are accepted either through GitHub pull requests or patches +via the Selenium issue tracker. You must sign our +Contributor License Agreement before your changes will be accepted.

        Issues

        -

        Please report any issues using the Selenium issue tracker.

        +

        Please report any issues using the Selenium issue tracker. When using +the issue tracker

        +
        • Do include a detailed description of the problem.
        • Do include a link to a gist with any +interesting stack traces/logs (you may also attach these directly to the bug +report).
        • Do include a reduced test case. Reporting "unable to find +element on the page" is not a valid report - there's nothing for us to +look into. Expect your bug report to be closed if you do not provide enough +information for us to investigate.
        • Do not use the issue tracker to submit basic help requests. All help +inquiries should be directed to the user forum or #selenium IRC +channel.
        • Do not post empty "I see this too" or "Any updates?" comments. These +provide no additional information and clutter the log.
        • Do not report regressions on closed bugs as they are not actively +monitored for upates (especially bugs that are >6 months old). Please open a +new issue and reference the original bug in your report.

        License

        -

        Copyright 2009-2014 Software Freedom Conservancy

        -

        Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at

        -
         http://www.apache.org/licenses/LICENSE-2.0
        -
        -

        Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License.

        -
        \ No newline at end of file +

        Licensed to the Software Freedom Conservancy (SFC) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The SFC licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at

        +

        http://www.apache.org/licenses/LICENSE-2.0

        +

        Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License.

        + \ No newline at end of file diff --git a/docs/interface_goog_debug_EntryPointMonitor.html b/docs/interface_goog_debug_EntryPointMonitor.html new file mode 100644 index 0000000..e730ac1 --- /dev/null +++ b/docs/interface_goog_debug_EntryPointMonitor.html @@ -0,0 +1,11 @@ +goog.debug.EntryPointMonitor

        Interface goog.debug.EntryPointMonitor

        code »
        Show:

        Instance Methods

        Try to remove an instrumentation wrapper created by this monitor. + If the function passed to unwrap is not a wrapper created by this + monitor, then we will do nothing. + + Notice that some wrappers may not be unwrappable. For example, if other + monitors have applied their own wrappers, then it will be impossible to + unwrap them because their wrappers will have captured our wrapper. + + So it is important that entry points are unwrapped in the reverse + order that they were wrapped.

        Parameters
        fn: !Function
        A function to unwrap.
        Returns
        The unwrapped function, or fn if it was not + a wrapped function created by this monitor.
        code »wrap ( fn )!Function

        Instruments a function.

        Parameters
        fn: !Function
        A function to instrument.
        Returns
        The instrumented function.
        \ No newline at end of file diff --git a/docs/interface_goog_disposable_IDisposable.html b/docs/interface_goog_disposable_IDisposable.html new file mode 100644 index 0000000..54a3411 --- /dev/null +++ b/docs/interface_goog_disposable_IDisposable.html @@ -0,0 +1,3 @@ +goog.disposable.IDisposable

        Interface goog.disposable.IDisposable

        code »

        Interface for a disposable object. If a instance requires cleanup + (references COM objects, DOM notes, or other disposable objects), it should + implement this interface (it may subclass goog.Disposable).

        Show:

        Instance Methods

        code »dispose ( )void

        Disposes of the object and its resources.

        Returns
        Nothing.
        Returns
        Whether the object has been disposed of.
        \ No newline at end of file diff --git a/docs/interface_goog_events_Listenable.html b/docs/interface_goog_events_Listenable.html new file mode 100644 index 0000000..e5837e8 --- /dev/null +++ b/docs/interface_goog_events_Listenable.html @@ -0,0 +1,84 @@ +goog.events.Listenable

        Interface goog.events.Listenable

        code »

        A listenable interface. A listenable is an object with the ability + to dispatch/broadcast events to "event listeners" registered via + listen/listenOnce. + + The interface allows for an event propagation mechanism similar + to one offered by native browser event targets, such as + capture/bubble mechanism, stopping propagation, and preventing + default actions. Capture/bubble mechanism depends on the ancestor + tree constructed via #getParentEventTarget; this tree + must be directed acyclic graph. The meaning of default action(s) + in preventDefault is specific to a particular use case. + + Implementations that do not support capture/bubble or can not have + a parent listenable can simply not implement any ability to set the + parent listenable (and have #getParentEventTarget return + null). + + Implementation of this class can be used with or independently from + goog.events. + + Implementation must call #addImplementation(implClass).

        Show:

        Instance Methods

        Dispatches an event (or event like object) and calls all listeners + listening for events of this type. The type of the event is decided by the + type property on the event object. + + If any of the listeners returns false OR calls preventDefault then this + function will return false. If one of the capture listeners calls + stopPropagation, then the bubble listeners won't fire.

        Parameters
        e: goog.events.EventLike
        Event object.
        Returns
        If anyone called preventDefault on the event object (or + if any of the listeners returns false) this will also return false.
        code »<EVENTOBJ> fireListeners ( type, capture, eventObject )boolean

        Fires all registered listeners in this listenable for the given + type and capture mode, passing them the given eventObject. This + does not perform actual capture/bubble. Only implementors of the + interface should be using this.

        Parameters
        type: (string|!goog.events.EventId.<EVENTOBJ>)
        The type of the + listeners to fire.
        capture: boolean
        The capture mode of the listeners to fire.
        eventObject: EVENTOBJ
        The event object to fire.
        Returns
        Whether all listeners succeeded without + attempting to prevent default behavior. If any listener returns + false or called goog.events.Event#preventDefault, this returns + false.
        code »<SCOPE, EVENTOBJ> getListener ( type, listener, capture, opt_listenerScope )goog.events.ListenableKey

        Gets the goog.events.ListenableKey for the event or null if no such + listener is in use.

        Parameters
        type: (string|!goog.events.EventId.<EVENTOBJ>)
        The name of the event + without the 'on' prefix.
        listener: function(this: SCOPE, EVENTOBJ): (boolean|undefined)
        The + listener function to get.
        capture: boolean
        Whether the listener is a capturing listener.
        opt_listenerScope: SCOPE=
        Object in whose scope to call the + listener.
        Returns
        the found listener or null if not found.
        code »<EVENTOBJ> getListeners ( type, capture )!Array.<goog.events.ListenableKey>

        Gets all listeners in this listenable for the given type and + capture mode.

        Parameters
        type: (string|!goog.events.EventId)
        The type of the listeners to fire.
        capture: boolean
        The capture mode of the listeners to fire.
        Returns
        An array of registered + listeners.

        Returns the parent of this event target to use for capture/bubble + mechanism. + + NOTE(user): The name reflects the original implementation of + custom event target (goog.events.EventTarget). We decided + that changing the name is not worth it.

        Returns
        The parent EventTarget or null if + there is no parent.
        code »<EVENTOBJ> hasListener ( opt_type, opt_capture )boolean

        Whether there is any active listeners matching the specified + signature. If either the type or capture parameters are + unspecified, the function will match on the remaining criteria.

        Parameters
        opt_type: (string|!goog.events.EventId.<EVENTOBJ>)=
        Event type.
        opt_capture: boolean=
        Whether to check for capture or bubble + listeners.
        Returns
        Whether there is any active listeners matching + the requested type and/or capture phase.
        code »<SCOPE, EVENTOBJ> listen ( type, listener, opt_useCapture, opt_listenerScope )goog.events.ListenableKey

        Adds an event listener. A listener can only be added once to an + object and if it is added again the key for the listener is + returned. Note that if the existing listener is a one-off listener + (registered via listenOnce), it will no longer be a one-off + listener after a call to listen().

        Parameters
        type: (string|!goog.events.EventId.<EVENTOBJ>)
        The event type id.
        listener: function(this: SCOPE, EVENTOBJ): (boolean|undefined)
        Callback + method.
        opt_useCapture: boolean=
        Whether to fire in capture phase + (defaults to false).
        opt_listenerScope: SCOPE=
        Object in whose scope to call the + listener.
        Returns
        Unique key for the listener.
        code »<SCOPE, EVENTOBJ> listenOnce ( type, listener, opt_useCapture, opt_listenerScope )goog.events.ListenableKey

        Adds an event listener that is removed automatically after the + listener fired once. + + If an existing listener already exists, listenOnce will do + nothing. In particular, if the listener was previously registered + via listen(), listenOnce() will not turn the listener into a + one-off listener. Similarly, if there is already an existing + one-off listener, listenOnce does not modify the listeners (it is + still a once listener).

        Parameters
        type: (string|!goog.events.EventId.<EVENTOBJ>)
        The event type id.
        listener: function(this: SCOPE, EVENTOBJ): (boolean|undefined)
        Callback + method.
        opt_useCapture: boolean=
        Whether to fire in capture phase + (defaults to false).
        opt_listenerScope: SCOPE=
        Object in whose scope to call the + listener.
        Returns
        Unique key for the listener.

        Removes all listeners from this listenable. If type is specified, + it will only remove listeners of the particular type. otherwise all + registered listeners will be removed.

        Parameters
        opt_type: string=
        Type of event to remove, default is to + remove all types.
        Returns
        Number of listeners removed.
        code »<SCOPE, EVENTOBJ> unlisten ( type, listener, opt_useCapture, opt_listenerScope )boolean

        Removes an event listener which was added with listen() or listenOnce().

        Parameters
        type: (string|!goog.events.EventId.<EVENTOBJ>)
        The event type id.
        listener: function(this: SCOPE, EVENTOBJ): (boolean|undefined)
        Callback + method.
        opt_useCapture: boolean=
        Whether to fire in capture phase + (defaults to false).
        opt_listenerScope: SCOPE=
        Object in whose scope to call + the listener.
        Returns
        Whether any listener was removed.

        Removes an event listener which was added with listen() by the key + returned by listen().

        Parameters
        key: goog.events.ListenableKey
        The key returned by + listen() or listenOnce().
        Returns
        Whether any listener was removed.

        Global Functions

        Marks a given class (constructor) as an implementation of + Listenable, do that we can query that fact at runtime. The class + must have already implemented the interface.

        Parameters
        cls: !Function
        The class constructor. The corresponding + class must have already implemented the interface.
        Parameters
        obj: Object
        The object to check.
        Returns
        Whether a given instance implements Listenable. The + class/superclass of the instance must call addImplementation.

        Global Properties

        An expando property to indicate that an object implements + goog.events.Listenable. + + See addImplementation/isImplementedBy.

        \ No newline at end of file diff --git a/docs/interface_goog_events_ListenableKey.html b/docs/interface_goog_events_ListenableKey.html new file mode 100644 index 0000000..53fd38b --- /dev/null +++ b/docs/interface_goog_events_ListenableKey.html @@ -0,0 +1,2 @@ +goog.events.ListenableKey

        Interface goog.events.ListenableKey

        code »

        An interface that describes a single registered listener.

        Show:

        Instance Properties

        Whether the listener works on capture phase.

        The 'this' object for the listener function's scope.

        A globally unique number to identify the key.

        code »listener : (function(?): ?|{handleEvent: function(?): ?}|null)

        The listener function.

        The event type the listener is listening to.

        Global Functions

        Reserves a key to be used for ListenableKey#key field.

        Returns
        A number to be used to fill ListenableKey#key + field.

        Global Properties

        Counter used to create a unique key

        \ No newline at end of file diff --git a/docs/interface_goog_labs_testing_Matcher.html b/docs/interface_goog_labs_testing_Matcher.html index 1db4111..9495c9b 100644 --- a/docs/interface_goog_labs_testing_Matcher.html +++ b/docs/interface_goog_labs_testing_Matcher.html @@ -1,2 +1,2 @@ goog.labs.testing.Matcher

        Interface goog.labs.testing.Matcher

        code »

        A matcher object to be used in assertThat statements.

        Show:

        Instance Methods

        code »describe ( value, opt_description )string

        Describes why the matcher failed.

        Parameters
        value: *
        The value that didn't match.
        opt_description: string=
        A partial description to which the reason - will be appended.
        Returns
        Description of why the matcher failed.
        code »matches ( value )boolean

        Determines whether a value matches the constraints of the match.

        Parameters
        value: *
        The object to match.
        Returns
        Whether the input value matches this matcher.
        \ No newline at end of file + will be appended.Returns
        Description of why the matcher failed.
        code »matches ( value )boolean

        Determines whether a value matches the constraints of the match.

        Parameters
        value: *
        The object to match.
        Returns
        Whether the input value matches this matcher.

        Global Functions

        code »goog.labs.testing.Matcher.makeMatcher ( matchesFunction, opt_describeFunction )!Function

        Generates a Matcher from the ‘matches’ and ‘describe’ functions passed in.

        Parameters
        matchesFunction: !Function
        The ‘matches’ function.
        opt_describeFunction: Function=
        The ‘describe’ function.
        Returns
        The custom matcher.
        \ No newline at end of file diff --git a/docs/interface_goog_net_XhrLike.html b/docs/interface_goog_net_XhrLike.html new file mode 100644 index 0000000..ebff7e3 --- /dev/null +++ b/docs/interface_goog_net_XhrLike.html @@ -0,0 +1,3 @@ +goog.net.XhrLike

        Interface goog.net.XhrLike

        code »

        Interface for the common parts of XMLHttpRequest. + + Mostly copied from externs/w3c_xml.js.

        Show:

        Type Definitions

        Typedef that refers to either native or custom-implemented XHR objects.

        Instance Methods

        Parameters
        header
        code »open ( method, url, opt_async, opt_user, opt_password )
        Parameters
        method
        url
        opt_async
        opt_user
        opt_password
        code »send ( opt_data )
        Parameters
        opt_data
        code »setRequestHeader ( header, value )
        Parameters
        header
        value

        Instance Properties

        \ No newline at end of file diff --git a/docs/interface_goog_structs_Collection.html b/docs/interface_goog_structs_Collection.html new file mode 100644 index 0000000..e94fe48 --- /dev/null +++ b/docs/interface_goog_structs_Collection.html @@ -0,0 +1 @@ +goog.structs.Collection

        Interface goog.structs.Collection.<T>

        code »

        An interface for a collection of values.

        Show:

        Instance Methods

        code »add ( value )
        Parameters
        value: T
        Value to add to the collection.
        code »contains ( value )boolean
        Parameters
        value: T
        Value to find in the collection.
        Returns
        Whether the collection contains the specified value.
        Returns
        The number of values stored in the collection.
        code »remove ( value )
        Parameters
        value: T
        Value to remove from the collection.
        \ No newline at end of file diff --git a/docs/interface_goog_testing_MockInterface.html b/docs/interface_goog_testing_MockInterface.html new file mode 100644 index 0000000..c5af2d4 --- /dev/null +++ b/docs/interface_goog_testing_MockInterface.html @@ -0,0 +1,3 @@ +goog.testing.MockInterface

        Interface goog.testing.MockInterface

        code »
        Show:

        Instance Methods

        Write down all the expected functions that have been called on the + mock so far. From here on out, future function calls will be + compared against this list.

        Reset the mock.

        Assert that the expected function calls match the actual calls.

        \ No newline at end of file diff --git a/docs/interface_webdriver_CommandExecutor.html b/docs/interface_webdriver_CommandExecutor.html index 9b45f41..88db5f1 100644 --- a/docs/interface_webdriver_CommandExecutor.html +++ b/docs/interface_webdriver_CommandExecutor.html @@ -1,5 +1,9 @@ -webdriver.CommandExecutor

        Interface webdriver.CommandExecutor

        code »

        Handles the execution of webdriver.Command objects.

        Show:

        Instance Methods

        code »execute ( command, callback )

        Executes the given command. If there is an error executing the - command, the provided callback will be invoked with the offending error. - Otherwise, the callback will be invoked with a null Error and non-null - bot.response.ResponseObject object.

        Parameters
        command: !webdriver.Command
        The command to execute.
        callback: function(Error, !bot.response.ResponseObject==)
        the function - to invoke when the command response is ready.
        \ No newline at end of file +CommandExecutor

        interface CommandExecutor

        Handles the execution of WebDriver commands.

        +

        Instance Methods

        execute(command, callback)code »

        Executes the given command. If there is an error executing the +command, the provided callback will be invoked with the offending error. +Otherwise, the callback will be invoked with a null Error and non-null +bot.response.ResponseObject object.

        +
        Parameters
        commandwebdriver.Command

        The command to execute.

        +
        callbackfunction(Error, {status: number, value: *}=): ?

        the function +to invoke when the command response is ready.

        +
        \ No newline at end of file diff --git a/docs/interface_webdriver_http_Client.html b/docs/interface_webdriver_http_Client.html index 68f0fcc..1f09a13 100644 --- a/docs/interface_webdriver_http_Client.html +++ b/docs/interface_webdriver_http_Client.html @@ -1,6 +1,10 @@ -webdriver.http.Client

        Interface webdriver.http.Client

        code »

        Interface used for sending individual HTTP requests to the server.

        Show:

        Instance Methods

        code »send ( request, callback )

        Sends a request to the server. If an error occurs while sending the request, - such as a failure to connect to the server, the provided callback will be - invoked with a non-null Error describing the error. Otherwise, when - the server's response has been received, the callback will be invoked with a - null Error and non-null webdriver.http.Response object.

        Parameters
        request: !webdriver.http.Request
        The request to send.
        callback: function(Error, !webdriver.http.Response==)
        the function to - invoke when the server's response is ready.
        \ No newline at end of file +Client

        interface Client

        Interface used for sending individual HTTP requests to the server.

        +

        Instance Methods

        send(request, callback)code »

        Sends a request to the server. If an error occurs while sending the request, +such as a failure to connect to the server, the provided callback will be +invoked with a non-null Error describing the error. Otherwise, when +the server's response has been received, the callback will be invoked with a +null Error and non-null webdriver.http.Response object.

        +
        Parameters
        requestwebdriver.http.Request

        The request to send.

        +
        callbackfunction(Error, webdriver.http.Response=): ?

        the function to +invoke when the server's response is ready.

        +
        \ No newline at end of file diff --git a/docs/interface_webdriver_promise_Thenable.html b/docs/interface_webdriver_promise_Thenable.html new file mode 100644 index 0000000..6edeec3 --- /dev/null +++ b/docs/interface_webdriver_promise_Thenable.html @@ -0,0 +1,77 @@ +Thenable

        interface Thenable<T>

        All extended interfaces
        IThenable<T>

        Thenable is a promise-like object with a then method which may be +used to schedule callbacks on a promised value.

        +

        Instance Methods

        cancel(opt_reason)code »

        Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

        +
        Parameters
        opt_reason?(string|webdriver.promise.CancellationError)=

        The reason this +promise is being cancelled.

        +

        isPending()code »

        Returns
        boolean

        Whether this promise's value is still being computed.

        +

        <R> then(opt_callback, opt_errback)code »

        Registers listeners for when this instance is resolved.

        +

        Specified by: IThenable

        Parameters
        opt_callback?function(T): (R|IThenable<R>)=

        The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

        +
        opt_errback?function(*): (R|IThenable<R>)=

        The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

        +
        Returns
        webdriver.promise.Promise

        A new promise which will be +resolved with the result of the invoked callback.

        +

        <R> thenCatch(errback)code »

        Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

        +
        // Synchronous API:
        +try {
        +  doSynchronousWork();
        +} catch (ex) {
        +  console.error(ex);
        +}
        +
        +// Asynchronous promise API:
        +doAsynchronousWork().thenCatch(function(ex) {
        +  console.error(ex);
        +});
        +
        +
        Parameters
        errbackfunction(*): (R|IThenable<R>)

        The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

        +
        Returns
        webdriver.promise.Promise

        A new promise which will be +resolved with the result of the invoked callback.

        +

        <R> thenFinally(callback)code »

        Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

        +
        // Synchronous API:
        +try {
        +  doSynchronousWork();
        +} finally {
        +  cleanUp();
        +}
        +
        +// Asynchronous promise API:
        +doAsynchronousWork().thenFinally(cleanUp);
        +
        +

        Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

        +
        try {
        +  throw Error('one');
        +} finally {
        +  throw Error('two');  // Hides Error: one
        +}
        +
        +promise.rejected(Error('one'))
        +    .thenFinally(function() {
        +      throw Error('two');  // Hides Error: one
        +    });
        +
        +
        Parameters
        callbackfunction(): (R|IThenable<R>)

        The function +to call when this promise is resolved.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be fulfilled +with the callback result.

        +

        Functions

        Thenable.addImplementation(ctor)code »

        Adds a property to a class prototype to allow runtime checks of whether +instances of that class implement the Thenable interface. This function will +also ensure the prototype's then function is exported from compiled +code.

        +
        Parameters
        ctorfunction(new: webdriver.promise.Thenable, ...?): ?

        The +constructor whose prototype to modify.

        +

        Thenable.isImplementation(object)code »

        Checks if an object has been tagged for implementing the Thenable interface +as defined by webdriver.promise.Thenable.addImplementation.

        +
        Parameters
        object*

        The object to test.

        +
        Returns
        boolean

        Whether the object is an implementation of the Thenable +interface.

        +
        \ No newline at end of file diff --git a/docs/license.html b/docs/license.html index 962258d..3c544de 100644 --- a/docs/license.html +++ b/docs/license.html @@ -202,4 +202,4 @@ See the License for the specific language governing permissions and limitations under the License. - \ No newline at end of file + \ No newline at end of file diff --git a/docs/module_selenium-webdriver.html b/docs/module_selenium-webdriver.html index 2764d07..5478878 100644 --- a/docs/module_selenium-webdriver.html +++ b/docs/module_selenium-webdriver.html @@ -1,4 +1,23 @@ -selenium-webdriver

        Module selenium-webdriver

        code »

        The main user facing module. Exports WebDriver's primary - public API and provides convenience assessors to certain sub-modules.

        Classes

        ActionSequence
        Class for defining sequences of complex user interactions.
        Builder
        Creates new WebDriver instances.
        Capabilities
        No Description.
        Command
        Describes a command to be executed by the WebDriverJS framework.
        EventEmitter
        Object that can emit events for others to listen for.
        Session
        Contains information about a WebDriver session.
        WebDriver
        Creates a new WebDriver client, which provides control over a browser.
        WebElement
        Represents a DOM element.

        Enumerations

        Browser
        Recognized browser names.
        Button
        Enumeration of the buttons used in the advanced interactions API.
        Capability
        Common webdriver capability keys.
        CommandName
        Enumeration of predefined names command names that all command processors - will support.
        Key
        Representations of pressable keys that aren't text.
        Show:

        Properties

        A collection of factory functions for creating webdriver.Locator - instances.

        \ No newline at end of file +selenium-webdriver

        module selenium-webdriver

        The main user facing module. Exports WebDriver's primary +public API and provides convenience assessors to certain sub-modules.

        +

        Types

        ActionSequence

        Class for defining sequences of complex user interactions.

        +
        Browser

        Recognized browser names.

        +
        Builder

        Creates new WebDriver instances.

        +
        Button

        Enumeration of the buttons used in the advanced interactions API.

        +
        Capabilities

        No description.

        +
        Capability

        Common webdriver capability keys.

        +
        Command

        Describes a command to be executed by the WebDriverJS framework.

        +
        CommandName

        Enumeration of predefined names command names that all command processors +will support.

        +
        EventEmitter

        Object that can emit events for others to listen for.

        +
        FileDetector

        Used with WebElement#sendKeys on file +input elements (<input type="file">) to detect when the entered key +sequence defines the path to a file.

        +
        Key

        Representations of pressable keys that aren't text.

        +
        Serializable

        Defines an object that can be asynchronously serialized to its WebDriver +wire representation.

        +
        Session

        Contains information about a WebDriver session.

        +
        WebDriver

        Creates a new WebDriver client, which provides control over a browser.

        +
        WebElement

        Represents a DOM element.

        +
        WebElementPromise

        WebElementPromise is a promise that will be fulfilled with a WebElement.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver__base.html b/docs/module_selenium-webdriver__base.html index 02cc0a1..8806b9a 100644 --- a/docs/module_selenium-webdriver__base.html +++ b/docs/module_selenium-webdriver__base.html @@ -1,14 +1,23 @@ -selenium-webdriver/_base

        Module selenium-webdriver/_base

        code »

        The base module responsible for bootstrapping the Closure - library and providing a means of loading Closure-based modules. - -

        Each script loaded by this module will be granted access to this module's - require function; all required non-native modules must be specified - relative to this module. - -

        This module will load all scripts from the "lib" subdirectory, unless the - SELENIUM_DEV_MODE environment variable has been set to 1, in which case all - scripts will be loaded from the Selenium client containing this script.

        Show:

        Functions

        Loads a symbol by name from the protected Closure context and exports its - public API to the provided object. This function relies on Closure code - conventions to define the public API of an object as those properties whose - name does not end with "_".

        Parameters
        symbol: string
        The symbol to load. This must resolve to an object.
        Returns
        An object with the exported API.
        Throws
        Error
        If the symbol has not been defined or does not resolve to - an object.
        Returns
        Whether this script was loaded in dev mode.
        code »require ( symbol )

        Loads a symbol by name from the protected Closure context.

        Parameters
        symbol: string
        The symbol to load.
        Returns
        The loaded symbol, or null if not found.
        Throws
        Error
        If the symbol has not been defined.

        Properties

        \ No newline at end of file +selenium-webdriver/_base

        module selenium-webdriver/_base

        The base module responsible for bootstrapping the Closure +library and providing a means of loading Closure-based modules.

        +

        Each script loaded by this module will be granted access to this module's +require function; all required non-native modules must be specified +relative to this module. +

        This module will load all scripts from the "lib" subdirectory, unless the +SELENIUM_DEV_MODE environment variable has been set to 1, in which case all +scripts will be loaded from the Selenium client containing this script. +

        Functions

        exportPublicApi(symbol)code »

        Loads a symbol by name from the protected Closure context and exports its +public API to the provided object. This function relies on Closure code +conventions to define the public API of an object as those properties whose +name does not end with "_".

        +
        Parameters
        symbolstring

        The symbol to load. This must resolve to an object.

        +
        Returns
        Object

        An object with the exported API.

        +
        Throws
        Error

        If the symbol has not been defined or does not resolve to +an object.

        +

        isDevMode()code »

        Returns
        boolean

        Whether this script was loaded in dev mode.

        +

        require(symbol)code »

        Loads a symbol by name from the protected Closure context.

        +
        Parameters
        symbolstring

        The symbol to load.

        +
        Returns

        The loaded symbol, or null if not found.

        +
        Throws
        Error

        If the symbol has not been defined.

        +

        Properties

        closure?
        No description.

        Types

        Context

        Maintains a unique context for Closure library-based code.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver__base_class_Context.html b/docs/module_selenium-webdriver__base_class_Context.html new file mode 100644 index 0000000..03304a3 --- /dev/null +++ b/docs/module_selenium-webdriver__base_class_Context.html @@ -0,0 +1,5 @@ +Context

        class Context

        Maintains a unique context for Closure library-based code.

        +

        new Context(opt_configureForTesting)

        Parameters
        opt_configureForTestingboolean=

        Whether to configure a fake DOM +for Closure-testing code that (incorrectly) assumes a DOM is always +present.

        +

        Instance Properties

        closure?
        No description.
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_builder.html b/docs/module_selenium-webdriver_builder.html index 6b20841..04aa449 100644 --- a/docs/module_selenium-webdriver_builder.html +++ b/docs/module_selenium-webdriver_builder.html @@ -1 +1,2 @@ -selenium-webdriver/builder

        Module selenium-webdriver/builder

        code »

        Classes

        Builder
        Creates new WebDriver instances.
        Show:
        \ No newline at end of file +selenium-webdriver/builder
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_builder_class_Builder.html b/docs/module_selenium-webdriver_builder_class_Builder.html index bac7e53..33a1d06 100644 --- a/docs/module_selenium-webdriver_builder_class_Builder.html +++ b/docs/module_selenium-webdriver_builder_class_Builder.html @@ -1,13 +1,138 @@ -Builder

        Class Builder

        code »
        webdriver.AbstractBuilder
        -  └ Builder

        Creates new WebDriver instances.

        Constructor

        Builder ( )
        Show:

        Instance Methods

        Defined in Builder

        code »build ( )webdriver.WebDriver
        code »setChromeOptions ( options )!Builder

        Sets Chrome-specific options for drivers created by this builder.

        Parameters
        options: !chrome.Options
        The ChromeDriver options to use.
        Returns
        A self reference.
        code »setProxy ( config )!Builder

        Sets the proxy configuration to use for WebDriver clients created by this - builder. Any calls to #withCapabilities after this function will - overwrite these settings.

        Parameters
        config: !proxy.ProxyConfig
        The configuration to use.
        Returns
        A self reference.

        Defined in webdriver.AbstractBuilder

        Returns
        The current desired capabilities for this - builder.
        Returns
        The URL of the WebDriver server this instance is configured - to use.

        Configures which WebDriver server should be used for new sessions. Overrides - the value loaded from the webdriver.AbstractBuilder.SERVER_URL_ENV - upon creation of this instance.

        Parameters
        url: string
        URL of the server to use.
        Returns
        This Builder instance for chain calling.

        Sets the desired capabilities when requesting a new session. This will - overwrite any previously set desired capabilities.

        Parameters
        capabilities: !(Object|webdriver.Capabilities)
        The desired - capabilities for a new session.
        Returns
        This Builder instance for chain calling.

        Instance Properties

        Defined in webdriver.AbstractBuilder

        The desired capabilities to use when creating a new session.

        URL of the remote server to use for new clients; initialized from the - value of the webdriver.AbstractBuilder.SERVER_URL_ENV environment - variable, but may be overridden using - webdriver.AbstractBuilder#usingServer.

        \ No newline at end of file +Builder

        class Builder

        Creates new WebDriver instances. The environment +variables listed below may be used to override a builder's configuration, +allowing quick runtime changes.

        +
        • +

          SELENIUM_BROWSER: defines the target browser in the form +browser[:version][:platform].

          +
        • +

          SELENIUM_REMOTE_URL: defines the remote URL for all builder +instances. This environment variable should be set to a fully qualified +URL for a WebDriver server (e.g. http://localhost:4444/wd/hub). This +option always takes precedence over SELENIUM_SERVER_JAR.

          +
        • +

          SELENIUM_SERVER_JAR: defines the path to the + +standalone Selenium server jar to use. The server will be started the +first time a WebDriver instance and be killed when the process exits.

          +
        +

        Suppose you had mytest.js that created WebDriver with

        +
        var driver = new webdriver.Builder()
        +    .forBrowser('chrome')
        +    .build();
        +
        +

        This test could be made to use Firefox on the local machine by running with +SELENIUM_BROWSER=firefox node mytest.js. Rather than change the code to +target Google Chrome on a remote machine, you can simply set the +SELENIUM_BROWSER and SELENIUM_REMOTE_URL environment variables:

        +
        SELENIUM_BROWSER=chrome:36:LINUX \
        +SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \
        +node mytest.js
        +
        +

        You could also use a local copy of the standalone Selenium server:

        +
        SELENIUM_BROWSER=chrome:36:LINUX \
        +SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \
        +node mytest.js
        +
        +

        new Builder()

        Parameters
        None.

        Instance Methods

        build()code »

        Creates a new WebDriver client based on this builder's current +configuration.

        +
        Returns
        webdriver.WebDriver

        A new WebDriver instance.

        +
        Throws
        Error

        If the current configuration is invalid.

        +

        disableEnvironmentOverrides()code »

        Configures this builder to ignore any environment variable overrides and to +only use the configuration specified through this instance's API.

        +
        Returns
        Builder

        A self reference.

        +

        forBrowser(name, opt_version, opt_platform)code »

        Configures the target browser for clients created by this instance. +Any calls to #withCapabilities after this function will +overwrite these settings.

        +

        You may also define the target browser using the SELENIUM_BROWSER +environment variable. If set, this environment variable should be of the +form browser[:[version][:platform]].

        +
        Parameters
        namestring

        The name of the target browser; +common defaults are available on the webdriver.Browser enum.

        +
        opt_versionstring=

        A desired version; may be omitted if any +version should be used.

        +
        opt_platformstring=

        The desired platform; may be omitted if any +version may be used.

        +
        Returns
        Builder

        A self reference.

        +

        getCapabilities()code »

        Returns the base set of capabilities this instance is currently configured +to use.

        +
        Returns
        webdriver.Capabilities

        The current capabilities for this builder.

        +

        getServerUrl()code »

        Returns
        string

        The URL of the WebDriver server this instance is configured +to use.

        +

        getWebDriverProxy()code »

        Returns
        string

        The URL of the proxy server to use for the WebDriver's HTTP +connections.

        +

        setAlertBehavior(beahvior)code »

        Sets the default action to take with an unexpected alert before returning +an error.

        +
        Parameters
        beahviorstring

        The desired behavior; should be "accept", "dismiss", +or "ignore". Defaults to "dismiss".

        +
        Returns
        Builder

        A self reference.

        +

        setChromeOptions(options)code »

        Sets Chrome specific options +for drivers created by this builder. Any logging or proxy settings defined +on the given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionschrome.Options

        The ChromeDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setControlFlow(flow)code »

        Sets the control flow that created drivers should execute actions in. If +the flow is never set, or is set to null, it will use the active +flow at the time #build() is called.

        +
        Parameters
        flowwebdriver.promise.ControlFlow

        The control flow to use, or +null to

        +
        Returns
        Builder

        A self reference.

        +

        setEnableNativeEvents(enabled)code »

        Sets whether native events should be used.

        +
        Parameters
        enabledboolean

        Whether to enable native events.

        +
        Returns
        Builder

        A self reference.

        +

        setFirefoxOptions(options)code »

        Sets Firefox specific options +for drivers created by this builder. Any logging or proxy settings defined +on the given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionsfirefox.Options

        The FirefoxDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setIeOptions(options)code »

        Sets Internet Explorer specific +options for drivers created by +this builder. Any proxy settings defined on the given options will take +precedence over those set through #setProxy.

        +
        Parameters
        optionsie.Options

        The IEDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences for the created session. Preferences may be +changed by repeated calls, or by calling #withCapabilities.

        +
        Parameters
        prefs(webdriver.logging.Preferences|Object<string, string>)

        The +desired logging preferences.

        +
        Returns
        Builder

        A self reference.

        +

        setOperaOptions(options)code »

        Sets Opera specific options for +drivers created by this builder. Any logging or proxy settings defined on the +given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionsopera.Options

        The OperaDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setProxy(config)code »

        Sets the proxy configuration to use for WebDriver clients created by this +builder. Any calls to #withCapabilities after this function will +overwrite these settings.

        +
        Parameters
        config{proxyType: string}

        The configuration to use.

        +
        Returns
        Builder

        A self reference.

        +

        setSafariOptions(options)code »

        Sets Safari specific options +for drivers created by this builder. Any logging settings defined on the +given options will take precedence over those set through +#setLoggingPrefs.

        +
        Parameters
        optionssafari.Options

        The Safari options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setScrollBehavior(behavior)code »

        Sets how elements should be scrolled into view for interaction.

        +
        Parameters
        behaviornumber

        The desired scroll behavior: either 0 to align with +the top of the viewport or 1 to align with the bottom.

        +
        Returns
        Builder

        A self reference.

        +

        usingServer(url)code »

        Sets the URL of a remote WebDriver server to use. Once a remote URL has been +specified, the builder direct all new clients to that server. If this method +is never called, the Builder will attempt to create all clients locally.

        +

        As an alternative to this method, you may also set the SELENIUM_REMOTE_URL +environment variable.

        +
        Parameters
        urlstring

        The URL of a remote server to use.

        +
        Returns
        Builder

        A self reference.

        +

        usingWebDriverProxy(proxy)code »

        Sets the URL of the proxy to use for the WebDriver's HTTP connections. +If this method is never called, the Builder will create a connection without +a proxy.

        +
        Parameters
        proxystring

        The URL of a proxy to use.

        +
        Returns
        Builder

        A self reference.

        +

        withCapabilities(capabilities)code »

        Sets the desired capabilities when requesting a new session. This will +overwrite any previously set capabilities.

        +
        Parameters
        capabilitiesObject

        The desired +capabilities for a new session.

        +
        Returns
        Builder

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_chrome.html b/docs/module_selenium-webdriver_chrome.html index f5d6a01..ce0ec26 100644 --- a/docs/module_selenium-webdriver_chrome.html +++ b/docs/module_selenium-webdriver_chrome.html @@ -1,5 +1,90 @@ -selenium-webdriver/chrome

        Module selenium-webdriver/chrome

        code »

        Classes

        Options
        Class for managing ChromeDriver specific options.
        ServiceBuilder
        Creates remote.DriverService instances that manage a ChromeDriver - server.
        Show:

        Functions

        code »createDriver ( opt_options, opt_service )!webdriver.WebDriver

        Creates a new ChromeDriver session.

        Parameters
        opt_options: (webdriver.Capabilities|Options)=
        The session options.
        opt_service: remote.DriverService=
        The session to use; will use - the default service by default.
        Returns
        A new WebDriver instance.
        code »getDefaultService ( )!remote.DriverService

        Returns the default ChromeDriver service. If such a service has not been - configured, one will be constructed using the default configuration for - a ChromeDriver executable found on the system PATH.

        Returns
        The default ChromeDriver service.

        Sets the default service to use for new ChromeDriver instances.

        Parameters
        service: !remote.DriverService
        The service to use.
        Throws
        Error
        If the default service is currently running.
        \ No newline at end of file +selenium-webdriver/chrome

        module selenium-webdriver/chrome

        Defines a WebDriver client for the Chrome +web browser. Before using this module, you must download the latest +ChromeDriver release and ensure it can be found on your system PATH.

        +

        There are three primary classes exported by this module:

        +
        1. +

          ServiceBuilder: configures the +remote.DriverService +that manages the ChromeDriver child process.

          +
        2. +

          Options: defines configuration options for each new Chrome +session, such as which proxy to use, +what extensions to install, or +what command-line switches to use when +starting the browser.

          +
        3. +

          Driver: the WebDriver client; each new instance will control +a unique browser session with a clean user profile (unless otherwise +configured through the Options class).

          +
        +

        Customizing the ChromeDriver Server

        +

        By default, every Chrome session will use a single driver service, which is +started the first time a Driver instance is created and terminated +when this process exits. The default service will inherit its environment +from the current process and direct all output to /dev/null. You may obtain +a handle to this default service using +getDefaultService() and change its configuration +with setDefaultService().

        +

        You may also create a Driver with its own driver service. This is +useful if you need to capture the server's log output for a specific session:

        +
        var chrome = require('selenium-webdriver/chrome');
        +
        +var service = new chrome.ServiceBuilder()
        +    .loggingTo('/my/log/file.txt')
        +    .enableVerboseLogging()
        +    .build();
        +
        +var options = new chrome.Options();
        +// configure browser options ...
        +
        +var driver = new chrome.Driver(options, service);
        +
        +

        Users should only instantiate the Driver class directly when they +need a custom driver service configuration (as shown above). For normal +operation, users should start Chrome using the +selenium-webdriver.Builder.

        +

        Working with Android

        +

        The ChromeDriver supports running tests on the Chrome browser as +well as WebView apps starting in Android 4.4 (KitKat). In order to +work with Android, you must first start the adb

        +
        adb start-server
        +
        +

        By default, adb will start on port 5037. You may change this port, but this +will require configuring a custom server that will connect +to adb on the correct port:

        +
        var service = new chrome.ServiceBuilder()
        +    .setAdbPort(1234)
        +    build();
        +// etc.
        +
        +

        The ChromeDriver may be configured to launch Chrome on Android using +Options#androidChrome():

        +
        var driver = new Builder()
        +    .forBrowser('chrome')
        +    .setChromeOptions(new chrome.Options().androidChrome())
        +    .build();
        +
        +

        Alternatively, you can configure the ChromeDriver to launch an app with a +Chrome-WebView by setting the androidActivity option:

        +
        var driver = new Builder()
        +    .forBrowser('chrome')
        +    .setChromeOptions(new chrome.Options()
        +        .androidPackage('com.example')
        +        .androidActivity('com.example.Activity'))
        +    .build();
        +
        +

        [Refer to the ChromeDriver site] for more information on using the +ChromeDriver with Android.

        +

        Functions

        getDefaultService()code »

        Returns the default ChromeDriver service. If such a service has not been +configured, one will be constructed using the default configuration for +a ChromeDriver executable found on the system PATH.

        +
        Returns
        DriverService

        The default ChromeDriver service.

        +

        setDefaultService(service)code »

        Sets the default service to use for new ChromeDriver instances.

        +
        Parameters
        serviceDriverService

        The service to use.

        +
        Throws
        Error

        If the default service is currently running.

        +

        Types

        Driver

        Creates a new WebDriver client for Chrome.

        +
        Options

        Class for managing ChromeDriver specific options.

        +
        ServiceBuilder

        Creates selenium-webdriver/remote.DriverService instances that manage +a ChromeDriver +server in a child process.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_chrome_class_Driver.html b/docs/module_selenium-webdriver_chrome_class_Driver.html new file mode 100644 index 0000000..61b84dc --- /dev/null +++ b/docs/module_selenium-webdriver_chrome_class_Driver.html @@ -0,0 +1,280 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        Creates a new WebDriver client for Chrome.

        +

        new Driver(opt_config, opt_service, opt_flow)

        Parameters
        opt_config?(Capabilities|Options)=

        The configuration +options.

        +
        opt_service?DriverService=

        The session to use; will use +the default service by default.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow to use, or +null to use the currently active flow.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        launchApp(id)code »

        Schedules a command to launch Chrome App with given ID.

        +
        Parameters
        idstring

        ID of the App to launch.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved +when app is launched.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        This function is a no-op as file detectors are not supported by this +implementation.

        +

        Overrides: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_chrome_class_Options.html b/docs/module_selenium-webdriver_chrome_class_Options.html index 0e21c20..4b92901 100644 --- a/docs/module_selenium-webdriver_chrome_class_Options.html +++ b/docs/module_selenium-webdriver_chrome_class_Options.html @@ -1,22 +1,127 @@ -Options

        Class Options

        code »

        Class for managing ChromeDriver specific options.

        Constructor

        Options ( )
        Show:

        Instance Methods

        code »addArguments ( var_args )!Options

        Add additional command line arguments to use when launching the Chrome - browser. Each argument may be specified with or without the "--" prefix - (e.g. "--foo" and "foo"). Arguments with an associated value should be - delimited by an "=": "foo=bar".

        Parameters
        var_args: ...(string|!Array.<string>)
        The arguments to add.
        Returns
        A self reference.
        code »addExtensions ( var_args )!Options

        Add additional extensions to install when launching Chrome. Each extension - should be specified as the path to the packed CRX file, or a Buffer for an - extension.

        Parameters
        var_args: ...(string|!Buffer|!Array)
        The - extensions to add.
        Returns
        A self reference.
        code »detachDriver ( detach )!Options

        Sets whether to leave the started Chrome browser running if the controlling - ChromeDriver service is killed before webdriver.WebDriver#quit() is - called.

        Parameters
        detach: boolean
        Whether to leave the browser running if the - chromedriver service is killed before the session.
        Returns
        A self reference.
        code »setChromeBinaryPath ( path )!Options

        Sets the path to the Chrome binary to use. On Mac OS X, this path should - reference the actual Chrome executable, not just the application binary - (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"). +Options

        class Options

        webdriver.Serializable
        +  └ Options

        Class for managing ChromeDriver specific options.

        +

        new Options()

        Parameters
        None.

        Instance Methods

        addArguments(var_args)code »

        Add additional command line arguments to use when launching the Chrome +browser. Each argument may be specified with or without the "--" prefix +(e.g. "--foo" and "foo"). Arguments with an associated value should be +delimited by an "=": "foo=bar".

        +
        Parameters
        var_args...(string|Array<string>)

        The arguments to add.

        +
        Returns
        Options

        A self reference.

        +

        addExtensions(var_args)code »

        Add additional extensions to install when launching Chrome. Each extension +should be specified as the path to the packed CRX file, or a Buffer for an +extension.

        +
        Parameters
        var_args...(string|Buffer|Array<(string|Buffer)>)

        The +extensions to add.

        +
        Returns
        Options

        A self reference.

        +

        androidActivity(name)code »

        Sets the name of the activity hosting a Chrome-based Android WebView. This +option must be set to connect to an Android WebView

        +
        Parameters
        namestring

        The activity name.

        +
        Returns
        Options

        A self reference.

        +

        androidChrome()code »

        Configures the ChromeDriver to launch Chrome on Android via adb. This +function is shorthand for +options.androidPackage('com.android.chrome').

        +
        Returns
        Options

        A self reference.

        +

        androidDeviceSerial(serial)code »

        Sets the device serial number to connect to via ADB. If not specified, the +ChromeDriver will select an unused device at random. An error will be +returned if all devices already have active sessions.

        +
        Parameters
        serialstring

        The device serial number to connect to.

        +
        Returns
        Options

        A self reference.

        +

        androidPackage(pkg)code »

        Sets the package name of the Chrome or WebView app.

        +
        Parameters
        pkg?string

        The package to connect to, or null to disable Android +and switch back to using desktop Chrome.

        +
        Returns
        Options

        A self reference.

        +

        androidProcess(processName)code »

        Sets the process name of the Activity hosting the WebView (as given by ps). +If not specified, the process name is assumed to be the same as +#androidPackage.

        +
        Parameters
        processNamestring

        The main activity name.

        +
        Returns
        Options

        A self reference.

        +

        androidUseRunningApp(useRunning)code »

        Sets whether to connect to an already-running instead of the specified +app instead of launching the app with a clean +data directory.

        +
        Parameters
        useRunningboolean

        Whether to connect to a running instance.

        +
        Returns
        Options

        A self reference.

        +

        detachDriver(detach)code »

        Sets whether to leave the started Chrome browser running if the controlling +ChromeDriver service is killed before webdriver.WebDriver#quit() is +called.

        +
        Parameters
        detachboolean

        Whether to leave the browser running if the +chromedriver service is killed before the session.

        +
        Returns
        Options

        A self reference.

        +

        excludeSwitches(var_args)code »

        List of Chrome command line switches to exclude that ChromeDriver by default +passes when starting Chrome. Do not prefix switches with "--".

        +
        Parameters
        var_args...(string|Array<string>)

        The switches to exclude.

        +
        Returns
        Options

        A self reference.

        +

        serialize()code »

        Converts this instance to its JSON wire protocol representation. Note this +function is an implementation not intended for general use.

        +

        Overrides: webdriver.Serializable

        Returns
        {args: Array<string>, binary: (string|undefined), detach: boolean, extensions: Array<(string|webdriver.promise.Promise)>, localState: ?(Object), logPath: (string|undefined), prefs: ?(Object)}

        The JSON wire protocol representation +of this instance.

        +

        setChromeBinaryPath(path)code »

        Sets the path to the Chrome binary to use. On Mac OS X, this path should +reference the actual Chrome executable, not just the application binary +(e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").

        +

        The binary path be absolute or relative to the chromedriver server +executable, but it must exist on the machine that will launch Chrome.

        +
        Parameters
        pathstring

        The path to the Chrome binary to use.

        +
        Returns
        Options

        A self reference.

        +

        setChromeLogFile(path)code »

        Sets the path to Chrome's log file. This path should exist on the machine +that will launch Chrome.

        +
        Parameters
        pathstring

        Path to the log file to use.

        +
        Returns
        Options

        A self reference.

        +

        setChromeMinidumpPath(path)code »

        Sets the directory to store Chrome minidumps in. This option is only +supported when ChromeDriver is running on Linux.

        +
        Parameters
        pathstring

        The directory path.

        +
        Returns
        Options

        A self reference.

        +

        setLocalState(state)code »

        Sets preferences for the "Local State" file in Chrome's user data +directory.

        +
        Parameters
        stateObject

        Dictionary of local state preferences.

        +
        Returns
        Options

        A self reference.

        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences for the new session.

        +
        Parameters
        prefswebdriver.logging.Preferences

        The logging preferences.

        +
        Returns
        Options

        A self reference.

        +

        setMobileEmulation(config)code »

        Configures Chrome to emulate a mobile device. For more information, refer to +the ChromeDriver project page on mobile emulation. Configuration +options include:

        +
        • deviceName: The name of a pre-configured emulated device
        • width: screen width, in pixels
        • height: screen height, in pixels
        • pixelRatio: screen pixel ratio
        +

        Example 1: Using a Pre-configured Device

        +
        var options = new chrome.Options().setMobileEmulation(
        +    {deviceName: 'Google Nexus 5'});
         
        - The binary path be absolute or relative to the chromedriver server
        - executable, but it must exist on the machine that will launch Chrome.
        Parameters
        path: string
        The path to the Chrome binary to use.
        Returns
        A self reference.
        code »setChromeLogFile ( path )!Options

        Sets the path to Chrome's log file. This path should exist on the machine - that will launch Chrome.

        Parameters
        path: string
        Path to the log file to use.
        Returns
        A self reference.
        code »setLocalState ( state )!Options

        Sets preferences for the "Local State" file in Chrome's user data - directory.

        Parameters
        state: !Object
        Dictionary of local state preferences.
        Returns
        A self reference.
        code »setLoggingPreferences ( prefs )!Options

        Sets the logging preferences for the new session.

        Parameters
        prefs: !webdriver.logging.Preferences
        The logging preferences.
        Returns
        A self reference.
        code »setProxy ( proxy )!Options

        Sets the proxy settings for the new session.

        Parameters
        proxy: ProxyConfig
        The proxy configuration to use.
        Returns
        A self reference.
        code »setUserPreferences ( prefs )!Options

        Sets the user preferences for Chrome's user profile. See the "Preferences" - file in Chrome's user data directory for examples.

        Parameters
        prefs: !Object
        Dictionary of user preferences to use.
        Returns
        A self reference.

        Converts this options instance to a webdriver.Capabilities object.

        Parameters
        opt_capabilities: webdriver.Capabilities=
        The capabilities to merge - these options into, if any.
        Returns
        The capabilities.
        code »toJSON ( ){args: !Array.<string>, binary: (string|undefined), detach: boolean, extensions: !Array.<string>, localState: (Object|undefined), logFile: (string|undefined), prefs: (Object|undefined)}

        Converts this instance to its JSON wire protocol representation. Note this - function is an implementation not intended for general use.

        Returns
        The JSON wire protocol representation - of this instance.

        Instance Properties

        Static Functions

        code »Options.fromCapabilities ( capabilities )!Options

        Extracts the ChromeDriver specific options from the given capabilities - object.

        Parameters
        capabilities: !webdriver.Capabilities
        The capabilities object.
        Returns
        The ChromeDriver options.
        \ No newline at end of file +var driver = new chrome.Driver(options); + +

        Example 2: Using Custom Screen Configuration

        +
        var options = new chrome.Options().setMobileEmulation({
        +    width: 360,
        +    height: 640,
        +    pixelRatio: 3.0
        +});
        +
        +var driver = new chrome.Driver(options);
        +
        +
        Parameters
        config?({deviceName: string}|{height: number, pixelRatio: number, width: number})

        The +mobile emulation configuration, or null to disable emulation.

        +
        Returns
        Options

        A self reference.

        +

        setPerfLoggingPrefs(prefs)code »

        Sets the performance logging preferences. Options include:

        +
        • enableNetwork: Whether or not to collect events from Network domain.
        • enablePage: Whether or not to collect events from Page domain.
        • enableTimeline: Whether or not to collect events from Timeline domain. +Note: when tracing is enabled, Timeline domain is implicitly disabled, +unless enableTimeline is explicitly set to true.
        • tracingCategories: A comma-separated string of Chrome tracing categories +for which trace events should be collected. An unspecified or empty +string disables tracing.
        • bufferUsageReportingInterval: The requested number of milliseconds +between DevTools trace buffer usage events. For example, if 1000, then +once per second, DevTools will report how full the trace buffer is. If a +report indicates the buffer usage is 100%, a warning will be issued.
        +
        Parameters
        prefs{bufferUsageReportingInterval: number, enableNetwork: boolean, enablePage: boolean, enableTimeline: boolean, tracingCategories: string}

        The performance +logging preferences.

        +
        Returns
        Options

        A self reference.

        +

        setProxy(proxy)code »

        Sets the proxy settings for the new session.

        +
        Parameters
        proxyselenium-webdriver.ProxyConfig

        The proxy configuration to use.

        +
        Returns
        Options

        A self reference.

        +

        setUserPreferences(prefs)code »

        Sets the user preferences for Chrome's user profile. See the "Preferences" +file in Chrome's user data directory for examples.

        +
        Parameters
        prefsObject

        Dictionary of user preferences to use.

        +
        Returns
        Options

        A self reference.

        +

        toCapabilities(opt_capabilities)code »

        Converts this options instance to a webdriver.Capabilities object.

        +
        Parameters
        opt_capabilities?Capabilities=

        The capabilities to merge +these options into, if any.

        +
        Returns
        webdriver.Capabilities

        The capabilities.

        +

        Static Functions

        Options.fromCapabilities(capabilities)code »

        Extracts the ChromeDriver specific options from the given capabilities +object.

        +
        Parameters
        capabilitiesCapabilities

        The capabilities object.

        +
        Returns
        Options

        The ChromeDriver options.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_chrome_class_ServiceBuilder.html b/docs/module_selenium-webdriver_chrome_class_ServiceBuilder.html index 3d478af..fad8b45 100644 --- a/docs/module_selenium-webdriver_chrome_class_ServiceBuilder.html +++ b/docs/module_selenium-webdriver_chrome_class_ServiceBuilder.html @@ -1,13 +1,46 @@ -ServiceBuilder

        Class ServiceBuilder

        code »

        Creates remote.DriverService instances that manage a ChromeDriver - server.

        Constructor

        ServiceBuilder ( opt_exe )
        Parameters
        opt_exe: string=
        Path to the server executable to use. If omitted, - the builder will attempt to locate the chromedriver on the current - PATH.
        Throws
        Error
        If provided executable does not exist, or the chromedriver - cannot be found on the PATH.
        Show:

        Instance Methods

        code »build ( )remote.DriverService

        Creates a new DriverService using this instance's current configuration.

        Returns
        A new driver service using this instance's - current configuration.
        Throws
        Error
        If the driver exectuable was not specified and a default - could not be found on the current PATH.
        code »enableVerboseLogging ( )!ServiceBuilder

        Enables verbose logging.

        Returns
        A self reference.
        code »loggingTo ( path )!ServiceBuilder

        Sets the path of the log file the driver should log to. If a log file is - not specified, the driver will log to stderr.

        Parameters
        path: string
        Path of the log file to use.
        Returns
        A self reference.
        code »setNumHttpThreads ( n )!ServiceBuilder

        Sets the number of threads the driver should use to manage HTTP requests. - By default, the driver will use 4 threads.

        Parameters
        n: number
        The number of threads to use.
        Returns
        A self reference.
        code »setStdio ( config )!ServiceBuilder

        Defines the stdio configuration for the driver service. See - child_process.spawn for more information.

        Parameters
        config: (string|!Array)
        The - configuration to use.
        Returns
        A self reference.
        code »setUrlBasePath ( path )!ServiceBuilder

        Sets the base path for WebDriver REST commands (e.g. "/wd/hub"). - By default, the driver will accept commands relative to "/".

        Parameters
        path: string
        The base path to use.
        Returns
        A self reference.
        code »usingPort ( port )!ServiceBuilder

        Sets the port to start the ChromeDriver on.

        Parameters
        port: number
        The port to use, or 0 for any free port.
        Returns
        A self reference.
        Throws
        Error
        If the port is invalid.
        code »withEnvironment ( env )!ServiceBuilder

        Defines the environment to start the server under. This settings will be - inherited by every browser session started by the server.

        Parameters
        env: !Object.<string, string>
        The environment to use.
        Returns
        A self reference.

        Instance Properties

        \ No newline at end of file +ServiceBuilder

        class ServiceBuilder

        Creates selenium-webdriver/remote.DriverService instances that manage +a ChromeDriver +server in a child process.

        +

        new ServiceBuilder(opt_exe)

        Parameters
        opt_exestring=

        Path to the server executable to use. If omitted, +the builder will attempt to locate the chromedriver on the current +PATH.

        +
        Throws
        Error

        If provided executable does not exist, or the chromedriver +cannot be found on the PATH.

        +

        Instance Methods

        build()code »

        Creates a new DriverService using this instance's current configuration.

        +
        Returns
        DriverService

        A new driver service using this instance's +current configuration.

        +
        Throws
        Error

        If the driver exectuable was not specified and a default +could not be found on the current PATH.

        +

        enableVerboseLogging()code »

        Enables verbose logging.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        loggingTo(path)code »

        Sets the path of the log file the driver should log to. If a log file is +not specified, the driver will log to stderr.

        +
        Parameters
        pathstring

        Path of the log file to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        setAdbPort(port)code »

        Sets which port adb is listening to. The ChromeDriver will connect to adb +if an Android session is requested, but +adb must be started beforehand.

        +
        Parameters
        portnumber

        Which port adb is running on.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        setNumHttpThreads(n)code »

        Sets the number of threads the driver should use to manage HTTP requests. +By default, the driver will use 4 threads.

        +
        Parameters
        nnumber

        The number of threads to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        setStdio(config)code »

        Defines the stdio configuration for the driver service. See +child_process.spawn for more information.

        +
        Parameters
        config(string|Array<?(string|number|Stream)>)

        The +configuration to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        setUrlBasePath(path)code »

        Sets the base path for WebDriver REST commands (e.g. "/wd/hub"). +By default, the driver will accept commands relative to "/".

        +
        Parameters
        pathstring

        The base path to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        usingPort(port)code »

        Sets the port to start the ChromeDriver on.

        +
        Parameters
        portnumber

        The port to use, or 0 for any free port.

        +
        Returns
        ServiceBuilder

        A self reference.

        +
        Throws
        Error

        If the port is invalid.

        +

        withEnvironment(env)code »

        Defines the environment to start the server under. This settings will be +inherited by every browser session started by the server.

        +
        Parameters
        envObject<string, string>

        The environment to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_ActionSequence.html b/docs/module_selenium-webdriver_class_ActionSequence.html new file mode 100644 index 0000000..b1f433b --- /dev/null +++ b/docs/module_selenium-webdriver_class_ActionSequence.html @@ -0,0 +1,115 @@ +ActionSequence

        class ActionSequence

        Alias for webdriver.ActionSequence

        Class for defining sequences of complex user interactions. Each sequence +will not be executed until #perform is called.

        +

        Example:

        +
        new webdriver.ActionSequence(driver).
        +    keyDown(webdriver.Key.SHIFT).
        +    click(element1).
        +    click(element2).
        +    dragAndDrop(element3, element4).
        +    keyUp(webdriver.Key.SHIFT).
        +    perform();
        +
        +

        new ActionSequence(driver)

        Parameters
        driverwebdriver.WebDriver

        The driver instance to use.

        +

        Instance Methods

        click(opt_elementOrButton, opt_button)code »

        Clicks a mouse button.

        +

        If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

        +
        sequence.mouseMove(element).click()
        +
        +
        Parameters
        opt_elementOrButton?(webdriver.WebElement|number)=

        Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

        +
        opt_buttonnumber=

        The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        doubleClick(opt_elementOrButton, opt_button)code »

        Double-clicks a mouse button.

        +

        If an element is provided, the mouse will first be moved to the center of +that element. This is equivalent to:

        +
        sequence.mouseMove(element).doubleClick()
        +
        +

        Warning: this method currently only supports the left mouse button. See +issue 4047.

        +
        Parameters
        opt_elementOrButton?(webdriver.WebElement|number)=

        Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

        +
        opt_buttonnumber=

        The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        dragAndDrop(element, location)code »

        Convenience function for performing a "drag and drop" manuever. The target +element may be moved to the location of another element, or by an offset (in +pixels).

        +
        Parameters
        elementwebdriver.WebElement

        The element to drag.

        +
        location(webdriver.WebElement|{x: number, y: number})

        The +location to drag to, either as another WebElement or an offset in pixels.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        keyDown(key)code »

        Performs a modifier key press. The modifier key is not released +until #keyUp or #sendKeys is called. The key press will be +targetted at the currently focused element.

        +
        Parameters
        keystring

        The modifier key to push. Must be one of +{ALT, CONTROL, SHIFT, COMMAND, META}.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +
        Throws
        Error

        If the key is not a valid modifier key.

        +

        keyUp(key)code »

        Performs a modifier key release. The release is targetted at the currently +focused element.

        +
        Parameters
        keystring

        The modifier key to release. Must be one of +{ALT, CONTROL, SHIFT, COMMAND, META}.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +
        Throws
        Error

        If the key is not a valid modifier key.

        +

        mouseDown(opt_elementOrButton, opt_button)code »

        Presses a mouse button. The mouse button will not be released until +#mouseUp is called, regardless of whether that call is made in this +sequence or another. The behavior for out-of-order events (e.g. mouseDown, +click) is undefined.

        +

        If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

        +
        sequence.mouseMove(element).mouseDown()
        +
        +

        Warning: this method currently only supports the left mouse button. See +issue 4047.

        +
        Parameters
        opt_elementOrButton?(webdriver.WebElement|number)=

        Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

        +
        opt_buttonnumber=

        The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        mouseMove(location, opt_offset)code »

        Moves the mouse. The location to move to may be specified in terms of the +mouse's current location, an offset relative to the top-left corner of an +element, or an element (in which case the middle of the element is used).

        +
        Parameters
        location(webdriver.WebElement|{x: number, y: number})

        The +location to drag to, as either another WebElement or an offset in pixels.

        +
        opt_offset{x: number, y: number}=

        If the target location +is defined as a webdriver.WebElement, this parameter defines an +offset within that element. The offset should be specified in pixels +relative to the top-left corner of the element's bounding box. If +omitted, the element's center will be used as the target offset.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        mouseUp(opt_elementOrButton, opt_button)code »

        Releases a mouse button. Behavior is undefined for calling this function +without a previous call to #mouseDown.

        +

        If an element is provided, the mouse will first be moved to the center +of that element. This is equivalent to:

        +
        sequence.mouseMove(element).mouseUp()
        +
        +

        Warning: this method currently only supports the left mouse button. See +issue 4047.

        +
        Parameters
        opt_elementOrButton?(webdriver.WebElement|number)=

        Either +the element to interact with or the button to click with. +Defaults to webdriver.Button.LEFT if neither an element nor +button is specified.

        +
        opt_buttonnumber=

        The button to use. Defaults to +webdriver.Button.LEFT. Ignored if a button is provided as the +first argument.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +

        perform()code »

        Executes this action sequence.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved once +this sequence has completed.

        +

        sendKeys(var_args)code »

        Simulates typing multiple keys. Each modifier key encountered in the +sequence will not be released until it is encountered again. All key events +will be targetted at the currently focused element.

        +
        Parameters
        var_args...(string|Array<string>)

        The keys to type.

        +
        Returns
        webdriver.ActionSequence

        A self reference.

        +
        Throws
        Error

        If the key is not a valid modifier key.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_Builder.html b/docs/module_selenium-webdriver_class_Builder.html new file mode 100644 index 0000000..024bc6e --- /dev/null +++ b/docs/module_selenium-webdriver_class_Builder.html @@ -0,0 +1,138 @@ +Builder

        class Builder

        Alias for module(selenium-webdriver/builder).Builder

        Creates new WebDriver instances. The environment +variables listed below may be used to override a builder's configuration, +allowing quick runtime changes.

        +
        • +

          SELENIUM_BROWSER: defines the target browser in the form +browser[:version][:platform].

          +
        • +

          SELENIUM_REMOTE_URL: defines the remote URL for all builder +instances. This environment variable should be set to a fully qualified +URL for a WebDriver server (e.g. http://localhost:4444/wd/hub). This +option always takes precedence over SELENIUM_SERVER_JAR.

          +
        • +

          SELENIUM_SERVER_JAR: defines the path to the + +standalone Selenium server jar to use. The server will be started the +first time a WebDriver instance and be killed when the process exits.

          +
        +

        Suppose you had mytest.js that created WebDriver with

        +
        var driver = new webdriver.Builder()
        +    .forBrowser('chrome')
        +    .build();
        +
        +

        This test could be made to use Firefox on the local machine by running with +SELENIUM_BROWSER=firefox node mytest.js. Rather than change the code to +target Google Chrome on a remote machine, you can simply set the +SELENIUM_BROWSER and SELENIUM_REMOTE_URL environment variables:

        +
        SELENIUM_BROWSER=chrome:36:LINUX \
        +SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \
        +node mytest.js
        +
        +

        You could also use a local copy of the standalone Selenium server:

        +
        SELENIUM_BROWSER=chrome:36:LINUX \
        +SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \
        +node mytest.js
        +
        +

        new Builder()

        Parameters
        None.

        Instance Methods

        build()code »

        Creates a new WebDriver client based on this builder's current +configuration.

        +
        Returns
        webdriver.WebDriver

        A new WebDriver instance.

        +
        Throws
        Error

        If the current configuration is invalid.

        +

        disableEnvironmentOverrides()code »

        Configures this builder to ignore any environment variable overrides and to +only use the configuration specified through this instance's API.

        +
        Returns
        Builder

        A self reference.

        +

        forBrowser(name, opt_version, opt_platform)code »

        Configures the target browser for clients created by this instance. +Any calls to #withCapabilities after this function will +overwrite these settings.

        +

        You may also define the target browser using the SELENIUM_BROWSER +environment variable. If set, this environment variable should be of the +form browser[:[version][:platform]].

        +
        Parameters
        namestring

        The name of the target browser; +common defaults are available on the webdriver.Browser enum.

        +
        opt_versionstring=

        A desired version; may be omitted if any +version should be used.

        +
        opt_platformstring=

        The desired platform; may be omitted if any +version may be used.

        +
        Returns
        Builder

        A self reference.

        +

        getCapabilities()code »

        Returns the base set of capabilities this instance is currently configured +to use.

        +
        Returns
        webdriver.Capabilities

        The current capabilities for this builder.

        +

        getServerUrl()code »

        Returns
        string

        The URL of the WebDriver server this instance is configured +to use.

        +

        getWebDriverProxy()code »

        Returns
        string

        The URL of the proxy server to use for the WebDriver's HTTP +connections.

        +

        setAlertBehavior(beahvior)code »

        Sets the default action to take with an unexpected alert before returning +an error.

        +
        Parameters
        beahviorstring

        The desired behavior; should be "accept", "dismiss", +or "ignore". Defaults to "dismiss".

        +
        Returns
        Builder

        A self reference.

        +

        setChromeOptions(options)code »

        Sets Chrome specific options +for drivers created by this builder. Any logging or proxy settings defined +on the given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionschrome.Options

        The ChromeDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setControlFlow(flow)code »

        Sets the control flow that created drivers should execute actions in. If +the flow is never set, or is set to null, it will use the active +flow at the time #build() is called.

        +
        Parameters
        flowwebdriver.promise.ControlFlow

        The control flow to use, or +null to

        +
        Returns
        Builder

        A self reference.

        +

        setEnableNativeEvents(enabled)code »

        Sets whether native events should be used.

        +
        Parameters
        enabledboolean

        Whether to enable native events.

        +
        Returns
        Builder

        A self reference.

        +

        setFirefoxOptions(options)code »

        Sets Firefox specific options +for drivers created by this builder. Any logging or proxy settings defined +on the given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionsfirefox.Options

        The FirefoxDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setIeOptions(options)code »

        Sets Internet Explorer specific +options for drivers created by +this builder. Any proxy settings defined on the given options will take +precedence over those set through #setProxy.

        +
        Parameters
        optionsie.Options

        The IEDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences for the created session. Preferences may be +changed by repeated calls, or by calling #withCapabilities.

        +
        Parameters
        prefs(webdriver.logging.Preferences|Object<string, string>)

        The +desired logging preferences.

        +
        Returns
        Builder

        A self reference.

        +

        setOperaOptions(options)code »

        Sets Opera specific options for +drivers created by this builder. Any logging or proxy settings defined on the +given options will take precedence over those set through +#setLoggingPrefs and #setProxy, respectively.

        +
        Parameters
        optionsopera.Options

        The OperaDriver options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setProxy(config)code »

        Sets the proxy configuration to use for WebDriver clients created by this +builder. Any calls to #withCapabilities after this function will +overwrite these settings.

        +
        Parameters
        config{proxyType: string}

        The configuration to use.

        +
        Returns
        Builder

        A self reference.

        +

        setSafariOptions(options)code »

        Sets Safari specific options +for drivers created by this builder. Any logging settings defined on the +given options will take precedence over those set through +#setLoggingPrefs.

        +
        Parameters
        optionssafari.Options

        The Safari options to use.

        +
        Returns
        Builder

        A self reference.

        +

        setScrollBehavior(behavior)code »

        Sets how elements should be scrolled into view for interaction.

        +
        Parameters
        behaviornumber

        The desired scroll behavior: either 0 to align with +the top of the viewport or 1 to align with the bottom.

        +
        Returns
        Builder

        A self reference.

        +

        usingServer(url)code »

        Sets the URL of a remote WebDriver server to use. Once a remote URL has been +specified, the builder direct all new clients to that server. If this method +is never called, the Builder will attempt to create all clients locally.

        +

        As an alternative to this method, you may also set the SELENIUM_REMOTE_URL +environment variable.

        +
        Parameters
        urlstring

        The URL of a remote server to use.

        +
        Returns
        Builder

        A self reference.

        +

        usingWebDriverProxy(proxy)code »

        Sets the URL of the proxy to use for the WebDriver's HTTP connections. +If this method is never called, the Builder will create a connection without +a proxy.

        +
        Parameters
        proxystring

        The URL of a proxy to use.

        +
        Returns
        Builder

        A self reference.

        +

        withCapabilities(capabilities)code »

        Sets the desired capabilities when requesting a new session. This will +overwrite any previously set capabilities.

        +
        Parameters
        capabilitiesObject

        The desired +capabilities for a new session.

        +
        Returns
        Builder

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_Capabilities.html b/docs/module_selenium-webdriver_class_Capabilities.html new file mode 100644 index 0000000..2c78d90 --- /dev/null +++ b/docs/module_selenium-webdriver_class_Capabilities.html @@ -0,0 +1,60 @@ +Capabilities

        class Capabilities

        webdriver.Serializable<Object<string, ?>>
        +  └ Capabilities
        Alias for webdriver.Capabilities

        new Capabilities(opt_other)

        Parameters
        opt_other?Object=

        Another set of +capabilities to merge into this instance.

        +

        Instance Methods

        get(key)code »

        Parameters
        keystring

        The capability to return.

        +
        Returns

        The capability with the given key, or null if it has +not been set.

        +

        has(key)code »

        Parameters
        keystring

        The capability to check.

        +
        Returns
        boolean

        Whether the specified capability is set.

        +

        merge(other)code »

        Merges another set of capabilities into this instance. Any duplicates in +the provided set will override those already set on this instance.

        +
        Parameters
        otherObject

        The capabilities to +merge into this instance.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        serialize()code »

        Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

        +

        Overrides: webdriver.Serializable

        Returns
        Object<string, ?>

        The JSON representation of this instance. Note, +the returned object may contain nested promises that are promised values.

        +

        set(key, value)code »

        Parameters
        keystring

        The capability to set.

        +
        value*

        The capability value. Capability values must be JSON +serializable. Pass null to unset the capability.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        setAlertBehavior(behavior)code »

        Sets the default action to take with an unexpected alert before returning +an error.

        +
        Parameters
        behaviorstring

        The desired behavior; should be "accept", "dismiss", +or "ignore". Defaults to "dismiss".

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        setEnableNativeEvents(enabled)code »

        Sets whether native events should be used.

        +
        Parameters
        enabledboolean

        Whether to enable native events.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences. Preferences may be specified as a +webdriver.logging.Preferences instance, or a as a map of log-type to +log-level.

        +
        Parameters
        prefs(webdriver.logging.Preferences|Object<string, string>)

        The +logging preferences.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        setProxy(proxy)code »

        Sets the proxy configuration for this instance.

        +
        Parameters
        proxy{proxyType: string}

        The desired proxy configuration.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        setScrollBehavior(behavior)code »

        Sets how elements should be scrolled into view for interaction.

        +
        Parameters
        behaviornumber

        The desired scroll behavior: either 0 to align with +the top of the viewport or 1 to align with the bottom.

        +
        Returns
        webdriver.Capabilities

        A self reference.

        +

        Static Functions

        Capabilities.android()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for Android.

        +

        Capabilities.chrome()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for Chrome.

        +

        Capabilities.firefox()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for Firefox.

        +

        Capabilities.htmlunit()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for HTMLUnit.

        +

        Capabilities.htmlunitwithjs()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for HTMLUnit +with enabled Javascript.

        +

        Capabilities.ie()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for +Internet Explorer.

        +

        Capabilities.ipad()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for iPad.

        +

        Capabilities.iphone()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for iPhone.

        +

        Capabilities.opera()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for Opera.

        +

        Capabilities.phantomjs()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for +PhantomJS.

        +

        Capabilities.safari()code »

        Returns
        webdriver.Capabilities

        A basic set of capabilities for Safari.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_Command.html b/docs/module_selenium-webdriver_class_Command.html new file mode 100644 index 0000000..8a50c03 --- /dev/null +++ b/docs/module_selenium-webdriver_class_Command.html @@ -0,0 +1,15 @@ +Command

        class Command

        Alias for webdriver.Command

        Describes a command to be executed by the WebDriverJS framework.

        +

        new Command(name)

        Parameters
        namestring

        The name of this command.

        +

        Instance Methods

        getName()code »

        Returns
        string

        This command's name.

        +

        getParameter(key)code »

        Returns a named command parameter.

        +
        Parameters
        keystring

        The parameter key to look up.

        +
        Returns

        The parameter value, or undefined if it has not been set.

        +

        getParameters()code »

        Returns
        Object<?, *>

        The parameters to send with this command.

        +

        setParameter(name, value)code »

        Sets a parameter to send with this command.

        +
        Parameters
        namestring

        The parameter name.

        +
        value*

        The parameter value.

        +
        Returns
        webdriver.Command

        A self reference.

        +

        setParameters(parameters)code »

        Sets the parameters for this command.

        +
        Parameters
        parametersObject<?, *>

        The command parameters.

        +
        Returns
        webdriver.Command

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_EventEmitter.html b/docs/module_selenium-webdriver_class_EventEmitter.html new file mode 100644 index 0000000..57f15d6 --- /dev/null +++ b/docs/module_selenium-webdriver_class_EventEmitter.html @@ -0,0 +1,35 @@ +EventEmitter

        class EventEmitter

        Alias for webdriver.EventEmitter

        Object that can emit events for others to listen for. This is used instead +of Closure's event system because it is much more light weight. The API is +based on Node's EventEmitters.

        +

        new EventEmitter()

        Parameters
        None.

        Instance Methods

        addListener(type, listenerFn, opt_scope)code »

        Registers a listener.

        +
        Parameters
        typestring

        The type of event to listen for.

        +
        listenerFnFunction

        The function to invoke when the event is fired.

        +
        opt_scope?Object=

        The object in whose scope to invoke the listener.

        +
        Returns
        webdriver.EventEmitter

        A self reference.

        +

        emit(type, var_args)code »

        Fires an event and calls all listeners.

        +
        Parameters
        typestring

        The type of event to emit.

        +
        var_args...*

        Any arguments to pass to each listener.

        +

        listeners(type)code »

        Returns a mutable list of listeners for a specific type of event.

        +
        Parameters
        typestring

        The type of event to retrieve the listeners for.

        +
        Returns
        Array<{fn: Function, oneshot: boolean, scope: ?(Object)}>

        The registered listeners for +the given event type.

        +

        on(type, listenerFn, opt_scope)code »

        An alias for #addListener().

        +
        Parameters
        typestring

        The type of event to listen for.

        +
        listenerFnFunction

        The function to invoke when the event is fired.

        +
        opt_scope?Object=

        The object in whose scope to invoke the listener.

        +
        Returns
        webdriver.EventEmitter

        A self reference.

        +

        once(type, listenerFn, opt_scope)code »

        Registers a one-time listener which will be called only the first time an +event is emitted, after which it will be removed.

        +
        Parameters
        typestring

        The type of event to listen for.

        +
        listenerFnFunction

        The function to invoke when the event is fired.

        +
        opt_scope?Object=

        The object in whose scope to invoke the listener.

        +
        Returns
        webdriver.EventEmitter

        A self reference.

        +

        removeAllListeners(opt_type)code »

        Removes all listeners for a specific type of event. If no event is +specified, all listeners across all types will be removed.

        +
        Parameters
        opt_typestring=

        The type of event to remove listeners from.

        +
        Returns
        webdriver.EventEmitter

        A self reference.

        +

        removeListener(type, listenerFn)code »

        Removes a previously registered event listener.

        +
        Parameters
        typestring

        The type of event to unregister.

        +
        listenerFnFunction

        The handler function to remove.

        +
        Returns
        webdriver.EventEmitter

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_FileDetector.html b/docs/module_selenium-webdriver_class_FileDetector.html new file mode 100644 index 0000000..e9c4fe4 --- /dev/null +++ b/docs/module_selenium-webdriver_class_FileDetector.html @@ -0,0 +1,21 @@ +FileDetector

        class FileDetector

        Alias for webdriver.FileDetector

        Used with WebElement#sendKeys on file +input elements (<input type="file">) to detect when the entered key +sequence defines the path to a file.

        +

        By default, WebElement's will enter all +key sequences exactly as entered. You may set a +file detector on the parent +WebDriver instance to define custom behavior for handling file elements. Of +particular note is the selenium-webdriver/remote.FileDetector, which +should be used when running against a remote +Selenium Server.

        +

        new FileDetector()

        Parameters
        None.

        Instance Methods

        handleFile(driver, path)code »

        Handles the file specified by the given path, preparing it for use with +the current browser. If the path does not refer to a valid file, it will +be returned unchanged, otherwisee a path suitable for use with the current +browser will be returned.

        +

        This default implementation is a no-op. Subtypes may override this +function for custom tailored file handling.

        +
        Parameters
        driverwebdriver.WebDriver

        The driver for the current browser.

        +
        pathstring

        The path to process.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise for the processed +file path.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_Serializable.html b/docs/module_selenium-webdriver_class_Serializable.html new file mode 100644 index 0000000..b32c21f --- /dev/null +++ b/docs/module_selenium-webdriver_class_Serializable.html @@ -0,0 +1,9 @@ +Serializable

        class Serializable<T>

        Alias for webdriver.Serializable

        Defines an object that can be asynchronously serialized to its WebDriver +wire representation.

        +

        new Serializable()

        Parameters
        None.

        Instance Methods

        serialize()code »

        Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

        +
        Returns
        (T|IThenable<T>)

        This instance's serialized wire format.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_Session.html b/docs/module_selenium-webdriver_class_Session.html new file mode 100644 index 0000000..60f2a3d --- /dev/null +++ b/docs/module_selenium-webdriver_class_Session.html @@ -0,0 +1,13 @@ +Session

        class Session

        Alias for webdriver.Session

        Contains information about a WebDriver session.

        +

        new Session(id, capabilities)

        Parameters
        idstring

        The session ID.

        +
        capabilitiesObject

        The session +capabilities.

        +

        Instance Methods

        getCapabilities()code »

        Returns
        webdriver.Capabilities

        This session's capabilities.

        +

        getCapability(key)code »

        Retrieves the value of a specific capability.

        +
        Parameters
        keystring

        The capability to retrieve.

        +
        Returns

        The capability value.

        +

        getId()code »

        Returns
        string

        This session's ID.

        +

        toJSON(arg0)code »

        Returns the JSON representation of this object, which is just the string +session ID.

        +
        Parameters
        arg0string=
        Returns
        string

        The JSON representation of this Session.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_WebDriver.html b/docs/module_selenium-webdriver_class_WebDriver.html new file mode 100644 index 0000000..99951bc --- /dev/null +++ b/docs/module_selenium-webdriver_class_WebDriver.html @@ -0,0 +1,313 @@ +WebDriver

        class WebDriver

        Alias for webdriver.WebDriver

        Creates a new WebDriver client, which provides control over a browser.

        +

        Every WebDriver command returns a webdriver.promise.Promise that +represents the result of that command. Callbacks may be registered on this +object to manipulate the command result or catch an expected error. Any +commands scheduled with a callback are considered sub-commands and will +execute before the next command in the current frame. For example:

        +
        var message = [];
        +driver.call(message.push, message, 'a').then(function() {
        +  driver.call(message.push, message, 'b');
        +});
        +driver.call(message.push, message, 'c');
        +driver.call(function() {
        +  alert('message is abc? ' + (message.join('') == 'abc'));
        +});
        +
        +

        new WebDriver(session, executor, opt_flow)

        Parameters
        session(webdriver.Session|webdriver.promise.Promise)

        Either a +known session or a promise that will be resolved to a session.

        +
        executorwebdriver.CommandExecutor

        The executor to use when +sending commands to the browser.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The flow to +schedule commands through. Defaults to the active flow object.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +
        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +
        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +
        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +
        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +
        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +
        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +
        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +
        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        Sets the file detector that should be +used with this instance.

        +
        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +
        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +
        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +
        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +

        Static Functions

        WebDriver.attachToSession(executor, sessionId, opt_flow)code »

        Creates a new WebDriver client for an existing session.

        +
        Parameters
        executorwebdriver.CommandExecutor

        Command executor to use when +querying for session details.

        +
        sessionIdstring

        ID of the session to attach to.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow all driver +commands should execute under. Defaults to the +currently active control flow.

        +
        Returns
        webdriver.WebDriver

        A new client for the specified session.

        +

        WebDriver.createSession(executor, desiredCapabilities, opt_flow)code »

        Creates a new WebDriver session.

        +
        Parameters
        executorwebdriver.CommandExecutor

        The executor to create the new +session with.

        +
        desiredCapabilitieswebdriver.Capabilities

        The desired +capabilities for the new session.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow all driver +commands should execute under, including the initial session creation. +Defaults to the currently active +control flow.

        +
        Returns
        webdriver.WebDriver

        The driver for the newly created session.

        +

        Types

        WebDriver.Logs

        Interface for managing WebDriver log records.

        +
        WebDriver.Navigation

        Interface for navigating back and forth in the browser history.

        +
        WebDriver.Options

        Provides methods for managing browser and driver state.

        +
        WebDriver.TargetLocator

        An interface for changing the focus of the driver to another frame or window.

        +
        WebDriver.Timeouts

        An interface for managing timeout behavior for WebDriver instances.

        +
        WebDriver.Window

        An interface for managing the current window.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_WebElement.html b/docs/module_selenium-webdriver_class_WebElement.html new file mode 100644 index 0000000..9d195a1 --- /dev/null +++ b/docs/module_selenium-webdriver_class_WebElement.html @@ -0,0 +1,216 @@ +WebElement

        class WebElement

        webdriver.Serializable<{ELEMENT: string}>
        +  └ WebElement
        Alias for webdriver.WebElement

        Represents a DOM element. WebElements can be found by searching from the +document root using a webdriver.WebDriver instance, or by searching +under another WebElement:

        +
        driver.get('http://www.google.com');
        +var searchForm = driver.findElement(By.tagName('form'));
        +var searchBox = searchForm.findElement(By.name('q'));
        +searchBox.sendKeys('webdriver');
        +
        +

        The WebElement is implemented as a promise for compatibility with the promise +API. It will always resolve itself when its internal state has been fully +resolved and commands may be issued against the element. This can be used to +catch errors when an element cannot be located on the page:

        +
        driver.findElement(By.id('not-there')).then(function(element) {
        +  alert('Found an element that was not expected to be there!');
        +}, function(error) {
        +  alert('The element was not found, as expected');
        +});
        +
        +

        new WebElement(driver, id)

        Parameters
        driverwebdriver.WebDriver

        The parent WebDriver instance for this +element.

        +
        id(webdriver.promise.Promise<{ELEMENT: string}>|{ELEMENT: string})

        The server-assigned opaque ID for the +underlying DOM element.

        +

        Instance Methods

        clear()code »

        Schedules a command to clear the value of this element. This command +has no effect if the underlying DOM element is neither a text INPUT element +nor a TEXTAREA element.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the element has been cleared.

        +

        click()code »

        Schedules a command to click on this element.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the click command has completed.

        +

        findElement(locator)code »

        Schedule a command to find a descendant of this element. If the element +cannot be found, a bot.ErrorCode.NO_SUCH_ELEMENT result will +be returned by the driver. Unlike other commands, this error cannot be +suppressed. In other words, scheduling a command to find an element doubles +as an assert that the element is present on the page. To test whether an +element is present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = element.findElement(By.id('foo'));
        +var e2 = element.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = element.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(element) {
        +  var links = element.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the element.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedules a command to find all of the descendants of this element that +match the given search criteria.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the elements.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        getAttribute(attributeName)code »

        Schedules a command to query for the value of the given attribute of the +element. Will return the current value, even if it has been modified after +the page has been loaded. More exactly, this method will return the value of +the given attribute, unless that attribute is not present, in which case the +value of the property with the same name is returned. If neither value is +set, null is returned (for example, the "value" property of a textarea +element). The "style" attribute is converted as best can be to a +text representation with a trailing semi-colon. The following are deemed to +be "boolean" attributes and will return either "true" or null:

        +

        async, autofocus, autoplay, checked, compact, complete, controls, declare, +defaultchecked, defaultselected, defer, disabled, draggable, ended, +formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, +loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, +paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, +selected, spellcheck, truespeed, willvalidate

        +

        Finally, the following commonly mis-capitalized attribute/property names +are evaluated as expected:

        +
        • "class"
        • "readonly"
        +
        Parameters
        attributeNamestring

        The name of the attribute to query.

        +
        Returns
        webdriver.promise.Promise<?string>

        A promise that will be +resolved with the attribute's value. The returned value will always be +either a string or null.

        +

        getCssValue(cssStyleProperty)code »

        Schedules a command to query for the computed style of the element +represented by this instance. If the element inherits the named style from +its parent, the parent will be queried for its value. Where possible, color +values will be converted to their hex representation (e.g. #00ff00 instead of +rgb(0, 255, 0)).

        +

        Warning: the value returned will be as the browser interprets it, so +it may be tricky to form a proper assertion.

        +
        Parameters
        cssStylePropertystring

        The name of the CSS style property to look +up.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the requested CSS value.

        +

        getDriver()code »

        Returns
        webdriver.WebDriver

        The parent driver for this instance.

        +

        getId()code »

        Returns
        webdriver.promise.Promise<{ELEMENT: string}>

        A promise +that resolves to this element's JSON representation as defined by the +WebDriver wire protocol.

        +

        getInnerHtml()code »

        Schedules a command to retrieve the inner HTML of this element.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's inner HTML.

        +

        getLocation()code »

        Schedules a command to compute the location of this element in page space.

        +
        Returns
        webdriver.promise.Promise<{x: number, y: number}>

        A promise that +will be resolved to the element's location as a +{x:number, y:number} object.

        +

        getOuterHtml()code »

        Schedules a command to retrieve the outer HTML of this element.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's outer HTML.

        +

        getRawId()code »

        Returns the raw ID string ID for this element.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that resolves to this +element's raw ID as a string value.

        +

        getSize()code »

        Schedules a command to compute the size of this element's bounding box, in +pixels.

        +
        Returns
        webdriver.promise.Promise<{height: number, width: number}>

        A +promise that will be resolved with the element's size as a +{width:number, height:number} object.

        +

        getTagName()code »

        Schedules a command to query for the tag/node name of this element.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's tag name.

        +

        getText()code »

        Get the visible (i.e. not hidden by CSS) innerText of this element, including +sub-elements, without any leading or trailing whitespace.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's visible text.

        +

        isDisplayed()code »

        Schedules a command to test whether this element is currently displayed.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently visible on the page.

        +

        isElementPresent(locator)code »

        Schedules a command to test if there is at least one descendant of this +element that matches the given search criteria.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether an element could be located on the page.

        +

        isEnabled()code »

        Schedules a command to query whether the DOM element represented by this +instance is enabled, as dicted by the disabled attribute.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently enabled.

        +

        isSelected()code »

        Schedules a command to query whether this element is selected.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently selected.

        +

        sendKeys(var_args)code »

        Schedules a command to type a sequence on the DOM element represented by this +instance.

        +

        Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is +processed in the keysequence, that key state is toggled until one of the +following occurs:

        +
        • +

          The modifier key is encountered again in the sequence. At this point the +state of the key is toggled (along with the appropriate keyup/down events).

          +
        • +

          The webdriver.Key.NULL key is encountered in the sequence. When +this key is encountered, all modifier keys current in the down state are +released (with accompanying keyup events). The NULL key can be used to +simulate common keyboard shortcuts:

          +
            element.sendKeys("text was",
          +                   webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
          +                   "now text is");
          +  // Alternatively:
          +  element.sendKeys("text was",
          +                   webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
          +                   "now text is");
          +
          +
        • +

          The end of the keysequence is encountered. When there are no more keys +to type, all depressed modifier keys are released (with accompanying keyup +events).

          +
        +

        If this element is a file input (<input type="file">), the +specified key sequence should specify the path to the file to attach to +the element. This is analgous to the user clicking "Browse..." and entering +the path into the file select dialog.

        +
        var form = driver.findElement(By.css('form'));
        +var element = form.findElement(By.css('input[type=file]'));
        +element.sendKeys('/path/to/file.txt');
        +form.submit();
        +
        +

        For uploads to function correctly, the entered path must reference a file +on the browser's machine, not the local machine running this script. When +running against a remote Selenium server, a webdriver.FileDetector +may be used to transparently copy files to the remote machine before +attempting to upload them in the browser.

        +

        Note: On browsers where native keyboard events are not supported +(e.g. Firefox on OS X), key events will be synthesized. Special +punctionation keys will be synthesized according to a standard QWERTY en-us +keyboard layout.

        +
        Parameters
        var_args...(string|webdriver.promise.Promise<string>)

        The sequence +of keys to type. All arguments will be joined into a single sequence.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when all keys have been typed.

        +

        serialize()code »

        Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

        +

        Overrides: webdriver.Serializable

        Returns
        (webdriver.WebElement.Id|IThenable<webdriver.WebElement.Id>)

        This instance's serialized wire format.

        +

        submit()code »

        Schedules a command to submit the form containing this element (or this +element if it is a FORM element). This command is a no-op if the element is +not contained in a form.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the form has been submitted.

        +

        Static Functions

        WebElement.equals(a, b)code »

        Compares to WebElements for equality.

        +
        Parameters
        awebdriver.WebElement

        A WebElement.

        +
        bwebdriver.WebElement

        A WebElement.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved to whether the two WebElements are equal.

        +

        Static Properties

        WebElement.ELEMENT_KEYstring

        The property key used in the wire protocol to indicate that a JSON object +contains the ID of a WebElement.

        +

        Type Definitions

        WebElement.Id{ELEMENT: string}

        Wire protocol definition of a WebElement ID.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_class_WebElementPromise.html b/docs/module_selenium-webdriver_class_WebElementPromise.html new file mode 100644 index 0000000..d7119f7 --- /dev/null +++ b/docs/module_selenium-webdriver_class_WebElementPromise.html @@ -0,0 +1,266 @@ +WebElementPromise

        class WebElementPromise

        webdriver.Serializable<{ELEMENT: string}>
        +  └ webdriver.WebElement
        +      └ WebElementPromise
        Alias for webdriver.WebElementPromise
        All implemented interfaces
        IThenable<T>
        webdriver.promise.Thenable<webdriver.WebElement>

        WebElementPromise is a promise that will be fulfilled with a WebElement. +This serves as a forward proxy on WebElement, allowing calls to be +scheduled without directly on this instance before the underlying +WebElement has been fulfilled. In other words, the following two statements +are equivalent:

        +
        driver.findElement({id: 'my-button'}).click();
        +driver.findElement({id: 'my-button'}).then(function(el) {
        +  return el.click();
        +});
        +
        +

        new WebElementPromise(driver, el)

        Parameters
        driverwebdriver.WebDriver

        The parent WebDriver instance for this +element.

        +
        elwebdriver.promise.Promise<webdriver.WebElement>

        A promise +that will resolve to the promised element.

        +

        Instance Methods

        cancel(opt_reason)code »

        Cancels the computation of this promise's value, rejecting the promise in the +process. This method is a no-op if the promise has already been resolved.

        +

        Specified by: webdriver.promise.Thenable

        Parameters
        opt_reason?(string|webdriver.promise.CancellationError)=

        The reason this +promise is being cancelled.

        +

        clear()code »

        Schedules a command to clear the value of this element. This command +has no effect if the underlying DOM element is neither a text INPUT element +nor a TEXTAREA element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the element has been cleared.

        +

        click()code »

        Schedules a command to click on this element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the click command has completed.

        +

        findElement(locator)code »

        Schedule a command to find a descendant of this element. If the element +cannot be found, a bot.ErrorCode.NO_SUCH_ELEMENT result will +be returned by the driver. Unlike other commands, this error cannot be +suppressed. In other words, scheduling a command to find an element doubles +as an assert that the element is present on the page. To test whether an +element is present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = element.findElement(By.id('foo'));
        +var e2 = element.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = element.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(element) {
        +  var links = element.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        Defined by: webdriver.WebElement

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the element.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedules a command to find all of the descendants of this element that +match the given search criteria.

        +

        Defined by: webdriver.WebElement

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the elements.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        getAttribute(attributeName)code »

        Schedules a command to query for the value of the given attribute of the +element. Will return the current value, even if it has been modified after +the page has been loaded. More exactly, this method will return the value of +the given attribute, unless that attribute is not present, in which case the +value of the property with the same name is returned. If neither value is +set, null is returned (for example, the "value" property of a textarea +element). The "style" attribute is converted as best can be to a +text representation with a trailing semi-colon. The following are deemed to +be "boolean" attributes and will return either "true" or null:

        +

        async, autofocus, autoplay, checked, compact, complete, controls, declare, +defaultchecked, defaultselected, defer, disabled, draggable, ended, +formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, +loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, +paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, +selected, spellcheck, truespeed, willvalidate

        +

        Finally, the following commonly mis-capitalized attribute/property names +are evaluated as expected:

        +
        • "class"
        • "readonly"
        +

        Defined by: webdriver.WebElement

        Parameters
        attributeNamestring

        The name of the attribute to query.

        +
        Returns
        webdriver.promise.Promise<?string>

        A promise that will be +resolved with the attribute's value. The returned value will always be +either a string or null.

        +

        getCssValue(cssStyleProperty)code »

        Schedules a command to query for the computed style of the element +represented by this instance. If the element inherits the named style from +its parent, the parent will be queried for its value. Where possible, color +values will be converted to their hex representation (e.g. #00ff00 instead of +rgb(0, 255, 0)).

        +

        Warning: the value returned will be as the browser interprets it, so +it may be tricky to form a proper assertion.

        +

        Defined by: webdriver.WebElement

        Parameters
        cssStylePropertystring

        The name of the CSS style property to look +up.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the requested CSS value.

        +

        getDriver()code »

        Defined by: webdriver.WebElement

        Returns
        webdriver.WebDriver

        The parent driver for this instance.

        +

        getId()code »

        Defers returning the element ID until the wrapped WebElement has been +resolved.

        +

        Overrides: webdriver.WebElement

        Returns
        webdriver.promise.Promise<{ELEMENT: string}>

        A promise +that resolves to this element's JSON representation as defined by the +WebDriver wire protocol.

        +

        getInnerHtml()code »

        Schedules a command to retrieve the inner HTML of this element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's inner HTML.

        +

        getLocation()code »

        Schedules a command to compute the location of this element in page space.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<{x: number, y: number}>

        A promise that +will be resolved to the element's location as a +{x:number, y:number} object.

        +

        getOuterHtml()code »

        Schedules a command to retrieve the outer HTML of this element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's outer HTML.

        +

        getRawId()code »

        Returns the raw ID string ID for this element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<string>

        A promise that resolves to this +element's raw ID as a string value.

        +

        getSize()code »

        Schedules a command to compute the size of this element's bounding box, in +pixels.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<{height: number, width: number}>

        A +promise that will be resolved with the element's size as a +{width:number, height:number} object.

        +

        getTagName()code »

        Schedules a command to query for the tag/node name of this element.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's tag name.

        +

        getText()code »

        Get the visible (i.e. not hidden by CSS) innerText of this element, including +sub-elements, without any leading or trailing whitespace.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the element's visible text.

        +

        isDisplayed()code »

        Schedules a command to test whether this element is currently displayed.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently visible on the page.

        +

        isElementPresent(locator)code »

        Schedules a command to test if there is at least one descendant of this +element that matches the given search criteria.

        +

        Defined by: webdriver.WebElement

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether an element could be located on the page.

        +

        isEnabled()code »

        Schedules a command to query whether the DOM element represented by this +instance is enabled, as dicted by the disabled attribute.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently enabled.

        +

        isPending()code »

        Specified by: webdriver.promise.Thenable

        Returns
        boolean

        Whether this promise's value is still being computed.

        +

        isSelected()code »

        Schedules a command to query whether this element is selected.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<boolean>

        A promise that will be +resolved with whether this element is currently selected.

        +

        sendKeys(var_args)code »

        Schedules a command to type a sequence on the DOM element represented by this +instance.

        +

        Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is +processed in the keysequence, that key state is toggled until one of the +following occurs:

        +
        • +

          The modifier key is encountered again in the sequence. At this point the +state of the key is toggled (along with the appropriate keyup/down events).

          +
        • +

          The webdriver.Key.NULL key is encountered in the sequence. When +this key is encountered, all modifier keys current in the down state are +released (with accompanying keyup events). The NULL key can be used to +simulate common keyboard shortcuts:

          +
            element.sendKeys("text was",
          +                   webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
          +                   "now text is");
          +  // Alternatively:
          +  element.sendKeys("text was",
          +                   webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
          +                   "now text is");
          +
          +
        • +

          The end of the keysequence is encountered. When there are no more keys +to type, all depressed modifier keys are released (with accompanying keyup +events).

          +
        +

        If this element is a file input (<input type="file">), the +specified key sequence should specify the path to the file to attach to +the element. This is analgous to the user clicking "Browse..." and entering +the path into the file select dialog.

        +
        var form = driver.findElement(By.css('form'));
        +var element = form.findElement(By.css('input[type=file]'));
        +element.sendKeys('/path/to/file.txt');
        +form.submit();
        +
        +

        For uploads to function correctly, the entered path must reference a file +on the browser's machine, not the local machine running this script. When +running against a remote Selenium server, a webdriver.FileDetector +may be used to transparently copy files to the remote machine before +attempting to upload them in the browser.

        +

        Note: On browsers where native keyboard events are not supported +(e.g. Firefox on OS X), key events will be synthesized. Special +punctionation keys will be synthesized according to a standard QWERTY en-us +keyboard layout.

        +

        Defined by: webdriver.WebElement

        Parameters
        var_args...(string|webdriver.promise.Promise<string>)

        The sequence +of keys to type. All arguments will be joined into a single sequence.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when all keys have been typed.

        +

        serialize()code »

        Returns either this instance's serialized represention, if immediately +available, or a promise for its serialized representation. This function is +conceptually equivalent to objects that have a toJSON() property, +except the serialize() result may be a promise or an object containing a +promise (which are not directly JSON friendly).

        +

        Defined by: webdriver.WebElement
        Overrides: webdriver.Serializable

        Returns
        (webdriver.WebElement.Id|IThenable<webdriver.WebElement.Id>)

        This instance's serialized wire format.

        +

        submit()code »

        Schedules a command to submit the form containing this element (or this +element if it is a FORM element). This command is a no-op if the element is +not contained in a form.

        +

        Defined by: webdriver.WebElement

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the form has been submitted.

        +

        then(opt_callback, opt_errback)code »

        Registers listeners for when this instance is resolved.

        +

        Specified by: webdriver.promise.Thenable, IThenable

        Parameters
        opt_callback?function(T): (R|IThenable<R>)=

        The +function to call if this promise is successfully resolved. The function +should expect a single argument: the promise's resolved value.

        +
        opt_errback?function(*): (R|IThenable<R>)=

        The function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

        +
        Returns
        webdriver.promise.Promise

        A new promise which will be +resolved with the result of the invoked callback.

        +

        thenCatch(errback)code »

        Registers a listener for when this promise is rejected. This is synonymous +with the catch clause in a synchronous API:

        +
        // Synchronous API:
        +try {
        +  doSynchronousWork();
        +} catch (ex) {
        +  console.error(ex);
        +}
        +
        +// Asynchronous promise API:
        +doAsynchronousWork().thenCatch(function(ex) {
        +  console.error(ex);
        +});
        +
        +

        Specified by: webdriver.promise.Thenable

        Parameters
        errbackfunction(*): (R|IThenable<R>)

        The +function to call if this promise is rejected. The function should +expect a single argument: the rejection reason.

        +
        Returns
        webdriver.promise.Promise

        A new promise which will be +resolved with the result of the invoked callback.

        +

        thenFinally(callback)code »

        Registers a listener to invoke when this promise is resolved, regardless +of whether the promise's value was successfully computed. This function +is synonymous with the finally clause in a synchronous API:

        +
        // Synchronous API:
        +try {
        +  doSynchronousWork();
        +} finally {
        +  cleanUp();
        +}
        +
        +// Asynchronous promise API:
        +doAsynchronousWork().thenFinally(cleanUp);
        +
        +

        Note: similar to the finally clause, if the registered +callback returns a rejected promise or throws an error, it will silently +replace the rejection error (if any) from this promise:

        +
        try {
        +  throw Error('one');
        +} finally {
        +  throw Error('two');  // Hides Error: one
        +}
        +
        +promise.rejected(Error('one'))
        +    .thenFinally(function() {
        +      throw Error('two');  // Hides Error: one
        +    });
        +
        +

        Specified by: webdriver.promise.Thenable

        Parameters
        callbackfunction(): (R|IThenable<R>)

        The function +to call when this promise is resolved.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be fulfilled +with the callback result.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_enum_Browser.html b/docs/module_selenium-webdriver_enum_Browser.html new file mode 100644 index 0000000..c2dbf58 --- /dev/null +++ b/docs/module_selenium-webdriver_enum_Browser.html @@ -0,0 +1,2 @@ +Browser
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_enum_Button.html b/docs/module_selenium-webdriver_enum_Button.html new file mode 100644 index 0000000..0b0a57d --- /dev/null +++ b/docs/module_selenium-webdriver_enum_Button.html @@ -0,0 +1,2 @@ +Button
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_enum_Capability.html b/docs/module_selenium-webdriver_enum_Capability.html new file mode 100644 index 0000000..889d551 --- /dev/null +++ b/docs/module_selenium-webdriver_enum_Capability.html @@ -0,0 +1,33 @@ +Capability

        enum Capability

        Typestring
        Alias for webdriver.Capability

        Common webdriver capability keys.

        +

        Values and Descriptions

        ACCEPT_SSL_CERTS

        Indicates whether a driver should accept all SSL certs by default. This +capability only applies when requesting a new session. To query whether +a driver can handle insecure SSL certs, see #SECURE_SSL.

        +
        BROWSER_NAME

        The browser name. Common browser names are defined in the +webdriver.Browser enum.

        +
        ELEMENT_SCROLL_BEHAVIOR

        Defines how elements should be scrolled into the viewport for interaction. +This capability will be set to zero (0) if elements are aligned with the +top of the viewport, or one (1) if aligned with the bottom. The default +behavior is to align with the top of the viewport.

        +
        HANDLES_ALERTS

        Whether the driver is capable of handling modal alerts (e.g. alert, +confirm, prompt). To define how a driver should handle alerts, +use #UNEXPECTED_ALERT_BEHAVIOR.

        +
        LOGGING_PREFS

        Key for the logging driver logging preferences.

        +
        NATIVE_EVENTS

        Whether this session generates native events when simulating user input.

        +
        PLATFORM

        Describes the platform the browser is running on. Will be one of +ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When requesting a +session, ANY may be used to indicate no platform preference (this is +semantically equivalent to omitting the platform capability).

        +
        PROXY

        Describes the proxy configuration to use for a new WebDriver session.

        +
        ROTATABLE

        Whether the driver supports changing the brower's orientation.

        +
        SECURE_SSL

        Whether a driver is only capable of handling secure SSL certs. To request +that a driver accept insecure SSL certs by default, use +#ACCEPT_SSL_CERTS.

        +
        SUPPORTS_APPLICATION_CACHE

        Whether the driver supports manipulating the app cache.

        +
        SUPPORTS_CSS_SELECTORS

        Whether the driver supports locating elements with CSS selectors.

        +
        SUPPORTS_JAVASCRIPT

        Whether the browser supports JavaScript.

        +
        SUPPORTS_LOCATION_CONTEXT

        Whether the driver supports controlling the browser's location info.

        +
        TAKES_SCREENSHOT

        Whether the driver supports taking screenshots.

        +
        UNEXPECTED_ALERT_BEHAVIOR

        Defines how the driver should handle unexpected alerts. The value should +be one of "accept", "dismiss", or "ignore.

        +
        VERSION

        Defines the browser version.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_enum_CommandName.html b/docs/module_selenium-webdriver_enum_CommandName.html new file mode 100644 index 0000000..cea56af --- /dev/null +++ b/docs/module_selenium-webdriver_enum_CommandName.html @@ -0,0 +1,3 @@ +CommandName

        enum CommandName

        Typestring
        Alias for webdriver.CommandName

        Enumeration of predefined names command names that all command processors +will support.

        +

        Values and Descriptions

        ACCEPT_ALERT
        CLEAR_APP_CACHE
        CLEAR_ELEMENT
        CLEAR_LOCAL_STORAGE
        CLEAR_SESSION_STORAGE
        CLICK
        CLICK_ELEMENT
        CLOSE
        DELETE_ALL_COOKIES
        DESCRIBE_SESSION
        DISMISS_ALERT
        DOUBLE_CLICK
        ELEMENT_EQUALS
        EXECUTE_ASYNC_SCRIPT
        EXECUTE_SCRIPT
        EXECUTE_SQL
        FIND_CHILD_ELEMENT
        FIND_CHILD_ELEMENTS
        FIND_ELEMENT
        FIND_ELEMENTS
        GET
        GET_ACTIVE_ELEMENT
        GET_ALERT_TEXT
        GET_ALL_COOKIES
        GET_APP_CACHE
        GET_APP_CACHE_STATUS
        GET_AVAILABLE_LOG_TYPES
        GET_CURRENT_URL
        GET_CURRENT_WINDOW_HANDLE
        GET_ELEMENT_ATTRIBUTE
        GET_ELEMENT_LOCATION
        GET_ELEMENT_LOCATION_IN_VIEW
        GET_ELEMENT_SIZE
        GET_ELEMENT_TAG_NAME
        GET_ELEMENT_TEXT
        GET_ELEMENT_VALUE_OF_CSS_PROPERTY
        GET_LOCAL_STORAGE_ITEM
        GET_LOCAL_STORAGE_KEYS
        GET_LOCAL_STORAGE_SIZE
        GET_LOCATION
        GET_LOG
        GET_PAGE_SOURCE
        GET_SCREEN_ORIENTATION
        GET_SERVER_STATUS
        GET_SESSIONS
        GET_SESSION_LOGS
        GET_SESSION_STORAGE_ITEM
        GET_SESSION_STORAGE_KEYS
        GET_SESSION_STORAGE_SIZE
        GET_TITLE
        GET_WINDOW_HANDLES
        GET_WINDOW_POSITION
        GET_WINDOW_SIZE
        GO_BACK
        GO_FORWARD
        IMPLICITLY_WAIT
        IS_BROWSER_ONLINE
        IS_ELEMENT_DISPLAYED
        IS_ELEMENT_ENABLED
        IS_ELEMENT_SELECTED
        MAXIMIZE_WINDOW
        MOUSE_DOWN
        MOUSE_UP
        MOVE_TO
        NEW_SESSION
        QUIT
        REFRESH
        REMOVE_LOCAL_STORAGE_ITEM
        REMOVE_SESSION_STORAGE_ITEM
        SCREENSHOT
        SEND_KEYS_TO_ACTIVE_ELEMENT
        SEND_KEYS_TO_ELEMENT
        SET_ALERT_TEXT
        SET_BROWSER_ONLINE
        SET_LOCAL_STORAGE_ITEM
        SET_LOCATION
        SET_SCREEN_ORIENTATION
        SET_SCRIPT_TIMEOUT
        SET_SESSION_STORAGE_ITEM
        SET_TIMEOUT
        SET_WINDOW_POSITION
        SET_WINDOW_SIZE
        SUBMIT_ELEMENT
        SWITCH_TO_FRAME
        SWITCH_TO_WINDOW
        TOUCH_DOUBLE_TAP
        TOUCH_DOWN
        TOUCH_FLICK
        TOUCH_LONG_PRESS
        TOUCH_MOVE
        TOUCH_SCROLL
        TOUCH_SINGLE_TAP
        TOUCH_UP
        UPLOAD_FILE
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_enum_Key.html b/docs/module_selenium-webdriver_enum_Key.html new file mode 100644 index 0000000..18db2b6 --- /dev/null +++ b/docs/module_selenium-webdriver_enum_Key.html @@ -0,0 +1,12 @@ +Key

        enum Key

        Typestring
        Alias for webdriver.Key

        Representations of pressable keys that aren't text. These are stored in +the Unicode PUA (Private Use Area) code points, 0xE000-0xF8FF. Refer to +http://www.google.com.au/search?&q=unicode+pua&btnG=Search

        +

        Values and Descriptions

        ADD
        ALT
        ARROW_DOWN
        ARROW_LEFT
        ARROW_RIGHT
        ARROW_UP
        BACK_SPACE
        CANCEL
        CLEAR
        COMMAND
        CONTROL
        DECIMAL
        DELETE
        DIVIDE
        DOWN
        END
        ENTER
        EQUALS
        ESCAPE
        F1
        F10
        F11
        F12
        F2
        F3
        F4
        F5
        F6
        F7
        F8
        F9
        HELP
        HOME
        INSERT
        LEFT
        META
        MULTIPLY
        NULL
        NUMPAD0
        NUMPAD1
        NUMPAD2
        NUMPAD3
        NUMPAD4
        NUMPAD5
        NUMPAD6
        NUMPAD7
        NUMPAD8
        NUMPAD9
        PAGE_DOWN
        PAGE_UP
        PAUSE
        RETURN
        SEMICOLON
        SEPARATOR
        SHIFT
        SPACE
        SUBTRACT
        TAB
        UP

        Functions

        Key.chord(var_args)code »

        Simulate pressing many keys at once in a "chord". Takes a sequence of +webdriver.Keys or strings, appends each of the values to a string, +and adds the chord termination key (webdriver.Key.NULL) and returns +the resultant string.

        +

        Note: when the low-level webdriver key handlers see Keys.NULL, active +modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.

        +
        Parameters
        var_args...string

        The key sequence to concatenate.

        +
        Returns
        string

        The null-terminated key sequence.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_error.html b/docs/module_selenium-webdriver_error.html index dc4ba90..a8c05a9 100644 --- a/docs/module_selenium-webdriver_error.html +++ b/docs/module_selenium-webdriver_error.html @@ -1,4 +1,4 @@ -selenium-webdriver/error

        Module selenium-webdriver/error

        code »

        Classes

        Error
        Error extension that includes error status codes from the WebDriver wire - protocol: - http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes

        Enumerations

        ErrorCode
        Error codes from the WebDriver wire protocol: - http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
        Show:
        \ No newline at end of file +selenium-webdriver/error

        module selenium-webdriver/error

        Types

        Error

        Represents an error returned from a WebDriver command request.

        +
        ErrorCode

        Error codes from the Selenium WebDriver protocol: +https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_error_class_Error.html b/docs/module_selenium-webdriver_error_class_Error.html new file mode 100644 index 0000000..d8f3d1c --- /dev/null +++ b/docs/module_selenium-webdriver_error_class_Error.html @@ -0,0 +1,15 @@ +Error

        class Error

        Error
        +  └ Error
        Alias for bot.Error

        Represents an error returned from a WebDriver command request.

        +

        new Error(code, opt_message)

        Parameters
        codenumber

        The error's status code.

        +
        opt_messagestring=

        Optional error message.

        +

        Instance Methods

        toString()code »

        Returns
        string

        The string representation of this error.

        +

        Instance Properties

        codenumber

        This error's status code.

        +
        descriptionstring

        IE-only.

        +
        fileNamestring

        Mozilla-only

        +
        isAutomationErrorboolean

        Flag used for duck-typing when this code is embedded in a Firefox extension. +This is required since an Error thrown in one component and then reported +to another will fail instanceof checks in the second component.

        +
        lineNumbernumber

        Mozilla-only.

        +
        messagestring
        No description.
        namestring
        No description.
        sourceURL?

        Doesn't seem to exist, but closure/debug.js references it.

        +
        stackstring
        No description.
        statestring
        No description.

        Types

        Error.State

        Status strings enumerated in the W3C WebDriver protocol.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_error_enum_ErrorCode.html b/docs/module_selenium-webdriver_error_enum_ErrorCode.html new file mode 100644 index 0000000..55e62ec --- /dev/null +++ b/docs/module_selenium-webdriver_error_enum_ErrorCode.html @@ -0,0 +1,3 @@ +ErrorCode

        enum ErrorCode

        Typenumber
        Alias for bot.ErrorCode

        Error codes from the Selenium WebDriver protocol: +https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes

        +

        Values and Descriptions

        ELEMENT_NOT_SELECTABLE
        ELEMENT_NOT_VISIBLE
        IME_ENGINE_ACTIVATION_FAILED
        IME_NOT_AVAILABLE
        INVALID_ELEMENT_COORDINATES
        INVALID_ELEMENT_STATE
        INVALID_SELECTOR_ERROR
        INVALID_XPATH_SELECTOR
        INVALID_XPATH_SELECTOR_RETURN_TYPE
        JAVASCRIPT_ERROR
        METHOD_NOT_ALLOWED
        MOVE_TARGET_OUT_OF_BOUNDS
        NO_SUCH_ALERT
        NO_SUCH_ELEMENT
        NO_SUCH_FRAME
        NO_SUCH_WINDOW
        SCRIPT_TIMEOUT
        SESSION_NOT_CREATED
        SQL_DATABASE_ERROR
        STALE_ELEMENT_REFERENCE
        SUCCESS
        TIMEOUT
        UNEXPECTED_ALERT_OPEN
        UNKNOWN_COMMAND
        UNKNOWN_ERROR
        UNSUPPORTED_OPERATION
        XPATH_LOOKUP_ERROR
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_executors.html b/docs/module_selenium-webdriver_executors.html index 4cd5307..0f39862 100644 --- a/docs/module_selenium-webdriver_executors.html +++ b/docs/module_selenium-webdriver_executors.html @@ -1,3 +1,10 @@ -selenium-webdriver/executors

        Module selenium-webdriver/executors

        code »

        Various utilities for working with - webdriver.CommandExecutor implementations.

        Show:

        Functions

        Creates a command executor that uses WebDriver's JSON wire protocol.

        Parameters
        url: (string|!webdriver.promise.Promise.<string>)
        The server's URL, - or a promise that will resolve to that URL.
        \ No newline at end of file +selenium-webdriver/executors

        module selenium-webdriver/executors

        Various utilities for working with +webdriver.CommandExecutor implementations.

        +

        Functions

        createExecutor(url, opt_proxy)code »

        Creates a command executor that uses WebDriver's JSON wire protocol.

        +
        Parameters
        url(string|webdriver.promise.Promise<string>)

        The server's URL, +or a promise that will resolve to that URL.

        +
        opt_proxystring=

        (optional) The URL of the HTTP proxy for the +client to use.

        +

        Types

        DeferredExecutor

        Wraps a promised webdriver.CommandExecutor, ensuring no commands +are executed until the wrapped executor has been fully built.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_executors_class_DeferredExecutor.html b/docs/module_selenium-webdriver_executors_class_DeferredExecutor.html new file mode 100644 index 0000000..26ff73b --- /dev/null +++ b/docs/module_selenium-webdriver_executors_class_DeferredExecutor.html @@ -0,0 +1,12 @@ +DeferredExecutor

        class DeferredExecutor

        All implemented interfaces
        webdriver.CommandExecutor

        Wraps a promised webdriver.CommandExecutor, ensuring no commands +are executed until the wrapped executor has been fully built.

        +

        new DeferredExecutor(delegate)

        Parameters
        delegatewebdriver.promise.Promise<webdriver.CommandExecutor>

        The +promised delegate.

        +

        Instance Methods

        execute(command, callback)code »

        Executes the given command. If there is an error executing the +command, the provided callback will be invoked with the offending error. +Otherwise, the callback will be invoked with a null Error and non-null +bot.response.ResponseObject object.

        +

        Specified by: webdriver.CommandExecutor

        Parameters
        commandwebdriver.Command

        The command to execute.

        +
        callbackfunction(Error, {status: number, value: *}=): ?

        the function +to invoke when the command response is ready.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox.html b/docs/module_selenium-webdriver_firefox.html new file mode 100644 index 0000000..da78326 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox.html @@ -0,0 +1,67 @@ +selenium-webdriver/firefox

        module selenium-webdriver/firefox

        Defines the WebDriver client for Firefox. +Each FirefoxDriver instance will be created with an anonymous profile, +ensuring browser historys do not share session data (cookies, history, cache, +offline storage, etc.)

        +

        Customizing the Firefox Profile

        +

        The Profile class may be used to configure the browser profile used +with WebDriver, with functions to install additional +extensions, configure browser +preferences, and more. For example, you +may wish to include Firebug:

        +
        var firefox = require('selenium-webdriver/firefox');
        +
        +var profile = new firefox.Profile();
        +profile.addExtension('/path/to/firebug.xpi');
        +profile.setPreference('extensions.firebug.showChromeErrors', true);
        +
        +var options = new firefox.Options().setProfile(profile);
        +var driver = new firefox.Driver(options);
        +
        +

        The Profile class may also be used to configure WebDriver based on a +pre-existing browser profile:

        +
        var profile = new firefox.Profile(
        +    '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
        +var options = new firefox.Options().setProfile(profile);
        +var driver = new firefox.Driver(options);
        +
        +

        The FirefoxDriver will never modify a pre-existing profile; instead it will +create a copy for it to modify. By extension, there are certain browser +preferences that are required for WebDriver to function properly and they +will always be overwritten.

        +

        Using a Custom Firefox Binary

        +

        On Windows and OSX, the FirefoxDriver will search for Firefox in its +default installation location:

        +
        • Windows: C:\Program Files and C:\Program Files (x86).
        • Mac OS X: /Applications/Firefox.app
        +

        For Linux, Firefox will be located on the PATH: $(where firefox).

        +

        You can configure WebDriver to start use a custom Firefox installation with +the Binary class:

        +
        var firefox = require('selenium-webdriver/firefox');
        +var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin');
        +var options = new firefox.Options().setBinary(binary);
        +var driver = new firefox.Driver(options);
        +
        +

        Remote Testing

        +

        You may customize the Firefox binary and profile when running against a +remote Selenium server. Your custom profile will be packaged as a zip and +transfered to the remote host for use. The profile will be transferred +once for each new session. The performance impact should be minimal if +you've only configured a few extra browser preferences. If you have a large +profile with several extensions, you should consider installing it on the +remote host and defining its path via the Options class. Custom +binaries are never copied to remote machines and must be referenced by +installation path.

        +
        var options = new firefox.Options()
        +    .setProfile('/profile/path/on/remote/host')
        +    .setBinary('/install/dir/on/remote/host/firefox-bin');
        +
        +var driver = new (require('selenium-webdriver')).Builder()
        +    .forBrowser('firefox')
        +    .usingServer('http://127.0.0.1:4444/wd/hub')
        +    .setFirefoxOptions(options)
        +    .build();
        +
        +

        Types

        Binary

        Manages a Firefox subprocess configured for use with WebDriver.

        +
        Driver

        A WebDriver client for Firefox.

        +
        Options

        Configuration options for the FirefoxDriver.

        +
        Profile

        Models a Firefox proifle directory for use with the FirefoxDriver.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_binary.html b/docs/module_selenium-webdriver_firefox_binary.html new file mode 100644 index 0000000..e59f7fb --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_binary.html @@ -0,0 +1,4 @@ +selenium-webdriver/firefox/binary

        module selenium-webdriver/firefox/binary

        Manages Firefox binaries. This module is considered internal; +users should use selenium-webdriver/firefox.

        +

        Types

        Binary

        Manages a Firefox subprocess configured for use with WebDriver.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_binary_class_Binary.html b/docs/module_selenium-webdriver_firefox_binary_class_Binary.html new file mode 100644 index 0000000..96ba5d1 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_binary_class_Binary.html @@ -0,0 +1,21 @@ +Binary

        class Binary

        webdriver.Serializable
        +  └ Binary

        Manages a Firefox subprocess configured for use with WebDriver.

        +

        new Binary(opt_exe)

        Parameters
        opt_exestring=

        Path to the Firefox binary to use. If not +specified, will attempt to locate Firefox on the current system.

        +

        Instance Methods

        addArguments(var_args)code »

        Add arguments to the command line used to start Firefox.

        +
        Parameters
        var_args...(string|Array<string>)

        Either the arguments to add as +varargs, or the arguments as an array.

        +

        kill()code »

        Kills the managed Firefox process.

        +
        Returns
        webdriver.promise.Promise

        A promise for when the process has terminated.

        +

        launch(profile)code »

        Launches Firefox and eturns a promise that will be fulfilled when the process +terminates.

        +
        Parameters
        profilestring

        Path to the profile directory to use.

        +
        Returns
        webdriver.promise.Promise

        A promise for the process result.

        +
        Throws
        Error

        If this instance has already been started.

        +

        serialize()code »

        Returns a promise for the wire representation of this binary. Note: the +FirefoxDriver only supports passing the path to the binary executable over +the wire; all command line arguments and environment variables will be +discarded.

        +

        Overrides: webdriver.Serializable

        Returns
        webdriver.promise.Promise

        A promise for this binary's wire +representation.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_class_Binary.html b/docs/module_selenium-webdriver_firefox_class_Binary.html new file mode 100644 index 0000000..7050e61 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_class_Binary.html @@ -0,0 +1,21 @@ +Binary

        class Binary

        webdriver.Serializable
        +  └ Binary
        Alias for module(selenium-webdriver/firefox/binary).Binary

        Manages a Firefox subprocess configured for use with WebDriver.

        +

        new Binary(opt_exe)

        Parameters
        opt_exestring=

        Path to the Firefox binary to use. If not +specified, will attempt to locate Firefox on the current system.

        +

        Instance Methods

        addArguments(var_args)code »

        Add arguments to the command line used to start Firefox.

        +
        Parameters
        var_args...(string|Array<string>)

        Either the arguments to add as +varargs, or the arguments as an array.

        +

        kill()code »

        Kills the managed Firefox process.

        +
        Returns
        webdriver.promise.Promise

        A promise for when the process has terminated.

        +

        launch(profile)code »

        Launches Firefox and eturns a promise that will be fulfilled when the process +terminates.

        +
        Parameters
        profilestring

        Path to the profile directory to use.

        +
        Returns
        webdriver.promise.Promise

        A promise for the process result.

        +
        Throws
        Error

        If this instance has already been started.

        +

        serialize()code »

        Returns a promise for the wire representation of this binary. Note: the +FirefoxDriver only supports passing the path to the binary executable over +the wire; all command line arguments and environment variables will be +discarded.

        +

        Overrides: webdriver.Serializable

        Returns
        webdriver.promise.Promise

        A promise for this binary's wire +representation.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_class_Driver.html b/docs/module_selenium-webdriver_firefox_class_Driver.html new file mode 100644 index 0000000..b9d62c9 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_class_Driver.html @@ -0,0 +1,276 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        A WebDriver client for Firefox.

        +

        new Driver(opt_config, opt_flow)

        Parameters
        opt_config?(Capabilities|Object)=

        The +configuration options for this driver, specified as either an +Options or webdriver.Capabilities, or as a raw hash +object.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The flow to +schedule commands through. Defaults to the active flow object.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Overrides: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        This function is a no-op as file detectors are not supported by this +implementation.

        +

        Overrides: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_class_Options.html b/docs/module_selenium-webdriver_firefox_class_Options.html new file mode 100644 index 0000000..7dcd2b8 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_class_Options.html @@ -0,0 +1,19 @@ +Options

        class Options

        Configuration options for the FirefoxDriver.

        +

        new Options()

        Parameters
        None.

        Instance Methods

        setBinary(binary)code »

        Sets the binary to use. The binary may be specified as the path to a Firefox +executable, or as a Binary object.

        +
        Parameters
        binary(string|Binary)

        The binary to use.

        +
        Returns
        Options

        A self reference.

        +

        setLoggingPreferences(prefs)code »

        Sets the logging preferences for the new session.

        +
        Parameters
        prefswebdriver.logging.Preferences

        The logging preferences.

        +
        Returns
        Options

        A self reference.

        +

        setProfile(profile)code »

        Sets the profile to use. The profile may be specified as a +Profile object or as the path to an existing Firefox profile to use +as a template.

        +
        Parameters
        profile(string|Profile)

        The profile to use.

        +
        Returns
        Options

        A self reference.

        +

        setProxy(proxy)code »

        Sets the proxy to use.

        +
        Parameters
        proxyselenium-webdriver.ProxyConfig

        The proxy configuration to use.

        +
        Returns
        Options

        A self reference.

        +

        toCapabilities(opt_remote)code »

        Converts these options to a webdriver.Capabilities instance.

        +
        Parameters
        opt_remote?
        Returns
        webdriver.Capabilities

        A new capabilities object.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_class_Profile.html b/docs/module_selenium-webdriver_firefox_class_Profile.html new file mode 100644 index 0000000..6f954fc --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_class_Profile.html @@ -0,0 +1,50 @@ +Profile

        class Profile

        webdriver.Serializable
        +  └ Profile
        Alias for module(selenium-webdriver/firefox/profile).Profile

        Models a Firefox proifle directory for use with the FirefoxDriver. The +Proifle directory uses an in-memory model until #writeToDisk +is called.

        +

        new Profile(opt_dir)

        Parameters
        opt_dirstring=

        Path to an existing Firefox profile directory to +use a template for this profile. If not specified, a blank profile will +be used.

        +

        Instance Methods

        acceptUntrustedCerts()code »

        Returns
        boolean

        Whether the FirefoxDriver is configured to automatically +accept untrusted SSL certificates.

        +

        addExtension(extension)code »

        Registers an extension to be included with this profile.

        +
        Parameters
        extensionstring

        Path to the extension to include, as either an +unpacked extension directory or the path to a xpi file.

        +

        assumeUntrustedCertIssuer()code »

        Returns
        boolean

        Whether to assume untrusted certs come from untrusted +issuers.

        +

        encode()code »

        Encodes this profile as a zipped, base64 encoded directory.

        +
        Returns
        webdriver.promise.Promise

        A promise for the encoded profile.

        +

        getPort()code »

        Returns
        number

        The port this profile is currently configured to use, or +0 if the port will be selected at random when the profile is written +to disk.

        +

        getPreference(key)code »

        Returns the currently configured value of a profile preference. This does +not include any defaults defined in the profile's template directory user.js +file (if a template were specified on construction).

        +
        Parameters
        keystring

        The desired preference.

        +
        Returns
        (string|number|boolean|undefined)

        The current value of the +requested preference.

        +

        nativeEventsEnabled()code »

        Returns whether native events are enabled in this profile.

        +
        Returns
        boolean

        .

        +

        serialize()code »

        Encodes this profile as a zipped, base64 encoded directory.

        +

        Overrides: webdriver.Serializable

        Returns
        webdriver.promise.Promise

        A promise for the encoded profile.

        +

        setAcceptUntrustedCerts(value)code »

        Sets whether the FirefoxDriver should automatically accept untrusted SSL +certificates.

        +
        Parameters
        valueboolean

        .

        +

        setAssumeUntrustedCertIssuer(value)code »

        Sets whether to assume untrusted certificates come from untrusted issuers.

        +
        Parameters
        valueboolean

        .

        +

        setNativeEventsEnabled(enabled)code »

        Sets whether to use native events with this profile.

        +
        Parameters
        enabledboolean

        .

        +

        setPort(port)code »

        Sets the port to use for the WebDriver extension loaded by this profile.

        +
        Parameters
        portnumber

        The desired port, or 0 to use any free port.

        +

        setPreference(key, value)code »

        Sets a desired preference for this profile.

        +
        Parameters
        keystring

        The preference key.

        +
        value(string|number|boolean)

        The preference value.

        +
        Throws
        Error

        If attempting to set a frozen preference.

        +

        writeToDisk(opt_excludeWebDriverExt)code »

        Writes this profile to disk.

        +
        Parameters
        opt_excludeWebDriverExtboolean=

        Whether to exclude the WebDriver +extension from the generated profile. Used to reduce the size of an +encoded profile since the server will always install +the extension itself.

        +
        Returns
        webdriver.promise.Promise

        A promise for the path to the new +profile directory.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_extension.html b/docs/module_selenium-webdriver_firefox_extension.html new file mode 100644 index 0000000..a505e00 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_extension.html @@ -0,0 +1,8 @@ +selenium-webdriver/firefox/extension

        module selenium-webdriver/firefox/extension

        Utilities for working with Firefox extensions.

        +

        Functions

        install(extension, dir)code »

        Installs an extension to the given directory.

        +
        Parameters
        extensionstring

        Path to the extension to install, as either a xpi +file or a directory.

        +
        dirstring

        Path to the directory to install the extension in.

        +
        Returns
        webdriver.promise.Promise

        A promise for the add-on ID once +installed.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_profile.html b/docs/module_selenium-webdriver_firefox_profile.html new file mode 100644 index 0000000..9d07abb --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_profile.html @@ -0,0 +1,13 @@ +selenium-webdriver/firefox/profile

        module selenium-webdriver/firefox/profile

        Profile management module. This module is considered internal; +users should use selenium-webdriver/firefox.

        +

        Functions

        decode(data)code »

        Decodes a base64 encoded profile.

        +
        Parameters
        datastring

        The base64 encoded string.

        +
        Returns
        webdriver.promise.Promise

        A promise for the path to the decoded +profile directory.

        +

        loadUserPrefs(f)code »

        Parses a user.js file in a Firefox profile directory.

        +
        Parameters
        fstring

        Path to the file to parse.

        +
        Returns
        webdriver.promise.Promise

        A promise for the parsed preferences as +a JSON object. If the file does not exist, an empty object will be +returned.

        +

        Types

        Profile

        Models a Firefox proifle directory for use with the FirefoxDriver.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_firefox_profile_class_Profile.html b/docs/module_selenium-webdriver_firefox_profile_class_Profile.html new file mode 100644 index 0000000..46c45d6 --- /dev/null +++ b/docs/module_selenium-webdriver_firefox_profile_class_Profile.html @@ -0,0 +1,50 @@ +Profile

        class Profile

        webdriver.Serializable
        +  └ Profile

        Models a Firefox proifle directory for use with the FirefoxDriver. The +Proifle directory uses an in-memory model until #writeToDisk +is called.

        +

        new Profile(opt_dir)

        Parameters
        opt_dirstring=

        Path to an existing Firefox profile directory to +use a template for this profile. If not specified, a blank profile will +be used.

        +

        Instance Methods

        acceptUntrustedCerts()code »

        Returns
        boolean

        Whether the FirefoxDriver is configured to automatically +accept untrusted SSL certificates.

        +

        addExtension(extension)code »

        Registers an extension to be included with this profile.

        +
        Parameters
        extensionstring

        Path to the extension to include, as either an +unpacked extension directory or the path to a xpi file.

        +

        assumeUntrustedCertIssuer()code »

        Returns
        boolean

        Whether to assume untrusted certs come from untrusted +issuers.

        +

        encode()code »

        Encodes this profile as a zipped, base64 encoded directory.

        +
        Returns
        webdriver.promise.Promise

        A promise for the encoded profile.

        +

        getPort()code »

        Returns
        number

        The port this profile is currently configured to use, or +0 if the port will be selected at random when the profile is written +to disk.

        +

        getPreference(key)code »

        Returns the currently configured value of a profile preference. This does +not include any defaults defined in the profile's template directory user.js +file (if a template were specified on construction).

        +
        Parameters
        keystring

        The desired preference.

        +
        Returns
        (string|number|boolean|undefined)

        The current value of the +requested preference.

        +

        nativeEventsEnabled()code »

        Returns whether native events are enabled in this profile.

        +
        Returns
        boolean

        .

        +

        serialize()code »

        Encodes this profile as a zipped, base64 encoded directory.

        +

        Overrides: webdriver.Serializable

        Returns
        webdriver.promise.Promise

        A promise for the encoded profile.

        +

        setAcceptUntrustedCerts(value)code »

        Sets whether the FirefoxDriver should automatically accept untrusted SSL +certificates.

        +
        Parameters
        valueboolean

        .

        +

        setAssumeUntrustedCertIssuer(value)code »

        Sets whether to assume untrusted certificates come from untrusted issuers.

        +
        Parameters
        valueboolean

        .

        +

        setNativeEventsEnabled(enabled)code »

        Sets whether to use native events with this profile.

        +
        Parameters
        enabledboolean

        .

        +

        setPort(port)code »

        Sets the port to use for the WebDriver extension loaded by this profile.

        +
        Parameters
        portnumber

        The desired port, or 0 to use any free port.

        +

        setPreference(key, value)code »

        Sets a desired preference for this profile.

        +
        Parameters
        keystring

        The preference key.

        +
        value(string|number|boolean)

        The preference value.

        +
        Throws
        Error

        If attempting to set a frozen preference.

        +

        writeToDisk(opt_excludeWebDriverExt)code »

        Writes this profile to disk.

        +
        Parameters
        opt_excludeWebDriverExtboolean=

        Whether to exclude the WebDriver +extension from the generated profile. Used to reduce the size of an +encoded profile since the server will always install +the extension itself.

        +
        Returns
        webdriver.promise.Promise

        A promise for the path to the new +profile directory.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http.html b/docs/module_selenium-webdriver_http.html index a5b2f98..bd3de77 100644 --- a/docs/module_selenium-webdriver_http.html +++ b/docs/module_selenium-webdriver_http.html @@ -1,4 +1,9 @@ -selenium-webdriver/http

        Module selenium-webdriver/http

        code »

        Defines a the webdriver.http.Client for use with - NodeJS.

        Classes

        Executor
        A command executor that communicates with a server using the WebDriver - command protocol.
        HttpClient
        A webdriver.http.Client implementation using Node's built-in http - module.
        Request
        Describes a partial HTTP request.
        Response
        Represents a HTTP response.
        Show:
        \ No newline at end of file +selenium-webdriver/http

        module selenium-webdriver/http

        Defines the webdriver.http.Client for use with +NodeJS.

        +

        Types

        Executor

        A command executor that communicates with a server using the WebDriver +command protocol.

        +
        HttpClient

        A webdriver.http.Client implementation using Node's built-in http +module.

        +
        Request

        Describes a partial HTTP request.

        +
        Response

        Represents a HTTP response.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http_class_Executor.html b/docs/module_selenium-webdriver_http_class_Executor.html new file mode 100644 index 0000000..95f2c00 --- /dev/null +++ b/docs/module_selenium-webdriver_http_class_Executor.html @@ -0,0 +1,22 @@ +Executor

        class Executor

        Alias for webdriver.http.Executor
        All implemented interfaces
        webdriver.CommandExecutor

        A command executor that communicates with a server using the WebDriver +command protocol.

        +

        new Executor(client)

        Parameters
        clientwebdriver.http.Client

        The client to use when sending +requests to the server.

        +

        Instance Methods

        defineCommand(name, method, path)code »

        Defines a new command for use with this executor. When a command is sent, +the path will be preprocessed using the command's parameters; any +path segments prefixed with ":" will be replaced by the parameter of the +same name. For example, given "/person/:name" and the parameters +"{name: 'Bob'}", the final command path will be "/person/Bob".

        +
        Parameters
        namestring

        The command name.

        +
        methodstring

        The HTTP method to use when sending this command.

        +
        pathstring

        The path to send the command to, relative to +the WebDriver server's command root and of the form +"/path/:variable/segment".

        +

        execute(command, callback)code »

        Executes the given command. If there is an error executing the +command, the provided callback will be invoked with the offending error. +Otherwise, the callback will be invoked with a null Error and non-null +bot.response.ResponseObject object.

        +

        Specified by: webdriver.CommandExecutor

        Parameters
        commandwebdriver.Command

        The command to execute.

        +
        callbackfunction(Error, {status: number, value: *}=): ?

        the function +to invoke when the command response is ready.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http_class_HttpClient.html b/docs/module_selenium-webdriver_http_class_HttpClient.html index 370359b..e6bf828 100644 --- a/docs/module_selenium-webdriver_http_class_HttpClient.html +++ b/docs/module_selenium-webdriver_http_class_HttpClient.html @@ -1,2 +1,16 @@ -HttpClient

        Class HttpClient

        code »
        All implemented interfaces:
        webdriver.http.Client

        A webdriver.http.Client implementation using Node's built-in http - module.

        Constructor

        HttpClient ( serverUrl )
        Parameters
        serverUrl: string
        URL for the WebDriver server to send commands to.
        Show:

        Instance Methods

        code »send ( httpRequest, callback )
        Parameters
        httpRequest
        callback

        Instance Properties

        Base options for each request.

        \ No newline at end of file +HttpClient

        class HttpClient

        All implemented interfaces
        webdriver.http.Client

        A webdriver.http.Client implementation using Node's built-in http +module.

        +

        new HttpClient(serverUrl, opt_agent, opt_proxy)

        Parameters
        serverUrlstring

        URL for the WebDriver server to send commands to.

        +
        opt_agent?http.Agent=

        The agent to use for each request. +Defaults to http.globalAgent.

        +
        opt_proxystring=

        The proxy to use for the connection to the server. +Default is to use no proxy.

        +

        Instance Methods

        send(request, callback)code »

        Sends a request to the server. If an error occurs while sending the request, +such as a failure to connect to the server, the provided callback will be +invoked with a non-null Error describing the error. Otherwise, when +the server's response has been received, the callback will be invoked with a +null Error and non-null webdriver.http.Response object.

        +

        Specified by: webdriver.http.Client

        Parameters
        requestwebdriver.http.Request

        The request to send.

        +
        callbackfunction(Error, webdriver.http.Response=): ?

        the function to +invoke when the server's response is ready.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http_class_Request.html b/docs/module_selenium-webdriver_http_class_Request.html new file mode 100644 index 0000000..615b730 --- /dev/null +++ b/docs/module_selenium-webdriver_http_class_Request.html @@ -0,0 +1,12 @@ +Request

        class Request

        Alias for webdriver.http.Request

        Describes a partial HTTP request. This class is a "partial" request and only +defines the path on the server to send a request to. It is each +webdriver.http.Client's responsibility to build the full URL for the +final request.

        +

        new Request(method, path, opt_data)

        Parameters
        methodstring

        The HTTP method to use for the request.

        +
        pathstring

        Path on the server to send the request to.

        +
        opt_data?Object=

        This request's JSON data.

        +

        Instance Methods

        toString()code »

        Returns
        string

        Instance Properties

        dataObject

        This request's body.

        +
        headersObject<?, (string|number)>

        The headers to send with the request.

        +
        methodstring

        The HTTP method to use for the request.

        +
        pathstring

        The path on the server to send the request to.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http_class_Response.html b/docs/module_selenium-webdriver_http_class_Response.html new file mode 100644 index 0000000..54249a6 --- /dev/null +++ b/docs/module_selenium-webdriver_http_class_Response.html @@ -0,0 +1,13 @@ +Response

        class Response

        Alias for webdriver.http.Response

        Represents a HTTP response.

        +

        new Response(status, headers, body)

        Parameters
        statusnumber

        The response code.

        +
        headersObject<?, string>

        The response headers. All header +names will be converted to lowercase strings for consistent lookups.

        +
        bodystring

        The response body.

        +

        Instance Methods

        toString()code »

        Returns
        string

        Instance Properties

        bodystring

        The response body.

        +
        headersObject<?, string>

        The response body.

        +
        statusnumber

        The HTTP response code.

        +

        Static Functions

        Response.fromXmlHttpRequest(xhr)code »

        Builds a webdriver.http.Response from a XMLHttpRequest or +XDomainRequest response object.

        +
        Parameters
        xhr(XDomainRequest|XMLHttpRequest)

        The request to parse.

        +
        Returns
        webdriver.http.Response

        The parsed response.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_http_util.html b/docs/module_selenium-webdriver_http_util.html index e85d100..76dc809 100644 --- a/docs/module_selenium-webdriver_http_util.html +++ b/docs/module_selenium-webdriver_http_util.html @@ -1,5 +1,17 @@ -selenium-webdriver/http/util

        Module selenium-webdriver/http/util

        code »

        Various HTTP utilities.

        Show:

        Functions

        Queries a WebDriver server for its current status.

        Parameters
        url: string
        Base URL of the server to query.
        Returns
        A promise that resolves with - a hash of the server status.

        Waits for a WebDriver server to be healthy and accepting requests.

        Parameters
        url: string
        Base URL of the server to query.
        timeout: number
        How long to wait for the server.
        Returns
        A promise that will resolve when the - server is ready.

        Polls a URL with GET requests until it returns a 2xx response or the - timeout expires.

        Parameters
        url: string
        The URL to poll.
        timeout: number
        How long to wait, in milliseconds.
        Returns
        A promise that will resolve when the - URL responds with 2xx.
        \ No newline at end of file +selenium-webdriver/http/util

        module selenium-webdriver/http/util

        Various HTTP utilities.

        +

        Functions

        getStatus(url)code »

        Queries a WebDriver server for its current status.

        +
        Parameters
        urlstring

        Base URL of the server to query.

        +
        Returns
        webdriver.promise.Promise<Object>

        A promise that resolves with +a hash of the server status.

        +

        waitForServer(url, timeout)code »

        Waits for a WebDriver server to be healthy and accepting requests.

        +
        Parameters
        urlstring

        Base URL of the server to query.

        +
        timeoutnumber

        How long to wait for the server.

        +
        Returns
        webdriver.promise.Promise

        A promise that will resolve when the +server is ready.

        +

        waitForUrl(url, timeout)code »

        Polls a URL with GET requests until it returns a 2xx response or the +timeout expires.

        +
        Parameters
        urlstring

        The URL to poll.

        +
        timeoutnumber

        How long to wait, in milliseconds.

        +
        Returns
        webdriver.promise.Promise

        A promise that will resolve when the +URL responds with 2xx.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_ie.html b/docs/module_selenium-webdriver_ie.html new file mode 100644 index 0000000..315f73d --- /dev/null +++ b/docs/module_selenium-webdriver_ie.html @@ -0,0 +1,11 @@ +selenium-webdriver/ie

        module selenium-webdriver/ie

        Defines a WebDriver client for Microsoft's +Internet Explorer. Before using the IEDriver, you must download the latest +IEDriverServer +and place it on your +PATH. You must also apply +the system configuration outlined on the Selenium project +wiki

        +

        Types

        Driver

        A WebDriver client for Microsoft's Internet Explorer.

        +
        Level

        No description.

        +
        Options

        Class for managing IEDriver specific options.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_ie_class_Driver.html b/docs/module_selenium-webdriver_ie_class_Driver.html new file mode 100644 index 0000000..08c9bae --- /dev/null +++ b/docs/module_selenium-webdriver_ie_class_Driver.html @@ -0,0 +1,274 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        A WebDriver client for Microsoft's Internet Explorer.

        +

        new Driver(opt_config, opt_flow)

        Parameters
        opt_config?(Capabilities|Options)=

        The configuration +options.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow to use, or +null to use the currently active flow.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        This function is a no-op as file detectors are not supported by this +implementation.

        +

        Overrides: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_ie_class_Options.html b/docs/module_selenium-webdriver_ie_class_Options.html new file mode 100644 index 0000000..b250383 --- /dev/null +++ b/docs/module_selenium-webdriver_ie_class_Options.html @@ -0,0 +1,89 @@ +Options

        class Options

        Class for managing IEDriver specific options.

        +

        new Options()

        Parameters
        None.

        Instance Methods

        addArguments(var_args)code »

        Specifies command-line switches to use when launching Internet Explorer. +This is only valid when used with #forceCreateProcessApi.

        +
        Parameters
        var_args...(string|Array<string>)

        The arguments to add.

        +
        Returns
        Options

        A self reference.

        +

        browserAttachTimeout(timeout)code »

        Configures the timeout, in milliseconds, that the driver will attempt to +located and attach to a newly opened instance of Internet Explorer. The +default is zero, which indicates waiting indefinitely.

        +
        Parameters
        timeoutnumber

        How long to wait for IE.

        +
        Returns
        Options

        A self reference.

        +

        enableElementCacheCleanup(enable)code »

        Configures whether the driver should attempt to remove obsolete +WebElements from its internal cache on +page navigation (true by default). Disabling this option will cause the +driver to run with a larger memory footprint.

        +
        Parameters
        enableboolean

        Whether to enable element reference cleanup.

        +
        Returns
        Options

        A self reference.

        +

        enablePersistentHover(enable)code »

        Configures whether to enable persistent mouse hovering (true by default). +Persistent hovering is achieved by continuously firing mouse over events at +the last location the mouse cursor has been moved to.

        +
        Parameters
        enableboolean

        Whether to enable persistent hovering.

        +
        Returns
        Options

        A self reference.

        +

        ensureCleanSession(cleanSession)code »

        Configures whether to clear the cache, cookies, history, and saved form data +before starting the browser. Using this capability will clear session data +for all running instances of Internet Explorer, including those started +manually.

        +
        Parameters
        cleanSessionboolean

        Whether to clear all session data on startup.

        +
        Returns
        Options

        A self reference.

        +

        forceCreateProcessApi(force)code »

        Configures whether to launch Internet Explorer using the CreateProcess API. +If this option is not specified, IE is launched using IELaunchURL, if +available. For IE 8 and above, this option requires the TabProcGrowth +registry value to be set to 0.

        +
        Parameters
        forceboolean

        Whether to use the CreateProcess API.

        +
        Returns
        Options

        A self reference.

        +

        ignoreZoomSetting(ignore)code »

        Indicates whether to skip the check that the browser's zoom level is set to +100%.

        +
        Parameters
        ignore?
        Returns
        Options

        ore Whether to ignore the browser's zoom level settings.

        +

        initialBrowserUrl(url)code »

        Sets the initial URL loaded when IE starts. This is intended to be used with +#ignoreProtectedModeSettings to allow the user to initialize IE in +the proper Protected Mode zone. Setting this option may cause browser +instability or flaky and unresponsive code. Only "best effort" support is +provided when using this option.

        +
        Parameters
        urlstring

        The initial browser URL.

        +
        Returns
        Options

        A self reference.

        +

        introduceFlakinessByIgnoringProtectedModeSettings(ignoreSettings)code »

        Whether to disable the protected mode settings check when the session is +created. Disbling this setting may lead to significant instability as the +browser may become unresponsive/hang. Only "best effort" support is provided +when using this capability.

        +

        For more information, refer to the IEDriver's +required system configuration.

        +
        Parameters
        ignoreSettingsboolean

        Whether to ignore protected mode settings.

        +
        Returns
        Options

        A self reference.

        +

        requireWindowFocus(require)code »

        Configures whether to require the IE window to have input focus before +performing any user interactions (i.e. mouse or keyboard events). This +option is disabled by default, but delivers much more accurate interaction +events when enabled.

        +
        Parameters
        requireboolean

        Whether to require window focus.

        +
        Returns
        Options

        A self reference.

        +

        setExtractPath(path)code »

        Sets the path of the temporary data directory to use.

        +
        Parameters
        pathstring

        The log file path.

        +
        Returns
        Options

        A self reference.

        +

        setHost(host)code »

        Sets the IP address of the driver's host adapter.

        +
        Parameters
        hoststring

        The IP address to use.

        +
        Returns
        Options

        A self reference.

        +

        setLogFile(path)code »

        Sets the path to the log file the driver should log to.

        +
        Parameters
        pathstring

        The log file path.

        +
        Returns
        Options

        A self reference.

        +

        setLogLevel(level)code »

        Sets the IEDriverServer's logging level.

        +
        Parameters
        levelstring

        The logging level.

        +
        Returns
        Options

        A self reference.

        +

        setProxy(proxy)code »

        Sets the proxy settings for the new session.

        +
        Parameters
        proxyselenium-webdriver.ProxyConfig

        The proxy configuration to use.

        +
        Returns
        Options

        A self reference.

        +

        silent(silent)code »

        Sets whether the driver should start in silent mode.

        +
        Parameters
        silentboolean

        Whether to run in silent mode.

        +
        Returns
        Options

        A self reference.

        +

        toCapabilities(opt_capabilities)code »

        Converts this options instance to a webdriver.Capabilities object.

        +
        Parameters
        opt_capabilities?Capabilities=

        The capabilities to merge +these options into, if any.

        +
        Returns
        webdriver.Capabilities

        The capabilities.

        +

        usePerProcessProxy(enable)code »

        Configures whether proxies should be configured on a per-process basis. If +not set, setting a proxy will configure the system +proxy. The default behavior is to use the system proxy.

        +
        Parameters
        enableboolean

        Whether to enable per-process proxy settings.

        +
        Returns
        Options

        A self reference.

        +

        Static Functions

        Options.fromCapabilities(capabilities)code »

        Extracts the IEDriver specific options from the given capabilities +object.

        +
        Parameters
        capabilitiesCapabilities

        The capabilities object.

        +
        Returns
        Options

        The IEDriver options.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_ie_enum_Level.html b/docs/module_selenium-webdriver_ie_enum_Level.html new file mode 100644 index 0000000..4c5bd53 --- /dev/null +++ b/docs/module_selenium-webdriver_ie_enum_Level.html @@ -0,0 +1 @@ +Level
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_io.html b/docs/module_selenium-webdriver_io.html index aabdf7f..78f6b5e 100644 --- a/docs/module_selenium-webdriver_io.html +++ b/docs/module_selenium-webdriver_io.html @@ -1,4 +1,37 @@ -selenium-webdriver/io

        Module selenium-webdriver/io

        code »
        Show:

        Functions

        code »findInPath ( file, opt_checkCwd )?string

        Searches the PATH environment variable for the given file.

        Parameters
        file: string
        The file to locate on the PATH.
        opt_checkCwd: boolean=
        Whether to always start with the search with - the current working directory, regardless of whether it is explicitly - listed on the PATH.
        Returns
        Path to the located file, or null if it could - not be found.
        \ No newline at end of file +selenium-webdriver/io

        module selenium-webdriver/io

        Functions

        copy(src, dst)code »

        Copies one file to another.

        +
        Parameters
        srcstring

        The source file.

        +
        dststring

        The destination file.

        +
        Returns
        webdriver.promise.Promise

        A promise for the copied file's path.

        +

        copyDir(src, dst, opt_exclude)code »

        Recursively copies the contents of one directory to another.

        +
        Parameters
        srcstring

        The source directory to copy.

        +
        dststring

        The directory to copy into.

        +
        opt_exclude?(RegEx|function(string): boolean)=

        An exclusion filter +as either a regex or predicate function. All files matching this filter +will not be copied.

        +
        Returns
        webdriver.promise.Promise

        A promise for the destination +directory's path once all files have been copied.

        +

        exists(path)code »

        Tests if a file path exists.

        +
        Parameters
        pathstring

        The path to test.

        +
        Returns
        webdriver.promise.Promise

        A promise for whether the file exists.

        +

        findInPath(file, opt_checkCwd)code »

        Searches the PATH environment variable for the given file.

        +
        Parameters
        filestring

        The file to locate on the PATH.

        +
        opt_checkCwdboolean=

        Whether to always start with the search with +the current working directory, regardless of whether it is explicitly +listed on the PATH.

        +
        Returns
        ?string

        Path to the located file, or null if it could +not be found.

        +

        rmDir(path)code »

        Recursively removes a directory and all of its contents. This is equivalent +to rm -rf on a POSIX system.

        +
        Parameters
        pathstring

        Path to the directory to remove.

        +
        Returns
        webdriver.promise.Promise

        A promise to be resolved when the operation has +completed.

        +

        tmpDir()code »

        Returns
        webdriver.promise.Promise

        A promise for the path to a temporary +directory.

        +

        tmpFile(opt_options)code »

        Parameters
        opt_options{postfix: string}=

        Temporary file options.

        +
        Returns
        webdriver.promise.Promise

        A promise for the path to a temporary +file.

        +

        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_io_exec.html b/docs/module_selenium-webdriver_io_exec.html new file mode 100644 index 0000000..db2facd --- /dev/null +++ b/docs/module_selenium-webdriver_io_exec.html @@ -0,0 +1,6 @@ +selenium-webdriver/io/exec
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_By.html b/docs/module_selenium-webdriver_namespace_By.html new file mode 100644 index 0000000..e01e60b --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_By.html @@ -0,0 +1,56 @@ +By

        namespace By

        Alias for webdriver.By

        A collection of factory functions for creating webdriver.Locator +instances.

        +

        Functions

        className(className)code »

        Locates elements that have a specific class name. The returned locator +is equivalent to searching for elements with the CSS selector ".clazz".

        +
        Parameters
        classNamestring

        The class name to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        css(selector)code »

        Locates elements using a CSS selector. For browsers that do not support +CSS selectors, WebDriver implementations may return an +invalid selector error. An +implementation may, however, emulate the CSS selector API.

        +
        Parameters
        selectorstring

        The CSS selector to use.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        id(id)code »

        Locates an element by its ID.

        +
        Parameters
        idstring

        The ID to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        js(script, var_args)code »

        Locates an elements by evaluating a +JavaScript expression. +The result of this expression must be an element or list of elements.

        +
        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        function(webdriver.WebDriver): webdriver.promise.Promise

        A new, +JavaScript-based locator function.

        +

        linkText(text)code »

        Locates link elements whose visible +text matches the given string.

        +
        Parameters
        textstring

        The link text to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        name(name)code »

        Locates elements whose name attribute has the given value.

        +
        Parameters
        namestring

        The name attribute to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        partialLinkText(text)code »

        Locates link elements whose visible +text contains the given substring.

        +
        Parameters
        textstring

        The substring to check for in a link's visible text.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        tagName(text)code »

        Locates elements with a given tag name. The returned locator is +equivalent to using the +getElementsByTagName +DOM function.

        +
        Parameters
        textstring

        The substring to check for in a link's visible text.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        xpath(xpath)code »

        Locates elements matching a XPath selector. Care should be taken when +using an XPath selector with a webdriver.WebElement as WebDriver +will respect the context in the specified in the selector. For example, +given the selector "//div", WebDriver will search from the +document root regardless of whether the locator was used with a +WebElement.

        +
        Parameters
        xpathstring

        The XPath selector to use.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        Type Definitions

        By.Hash({className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        Short-hand expressions for the primary element locator strategies. +For example the following two statements are equivalent:

        +
        var e1 = driver.findElement(webdriver.By.id('foo'));
        +var e2 = driver.findElement({id: 'foo'});
        +
        +

        Care should be taken when using JavaScript minifiers (such as the +Closure compiler), as locator hashes will always be parsed using +the un-obfuscated properties listed.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_error.html b/docs/module_selenium-webdriver_namespace_error.html new file mode 100644 index 0000000..0ce22a3 --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_error.html @@ -0,0 +1,4 @@ +error
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_logging.html b/docs/module_selenium-webdriver_namespace_logging.html new file mode 100644 index 0000000..20df9d6 --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_logging.html @@ -0,0 +1,18 @@ +logging

        namespace logging

        Alias for webdriver.logging

        Functions

        addConsoleHandler(opt_logger)code »

        Adds the console handler to the given logger. The console handler will log +all messages using the JavaScript Console API.

        +
        Parameters
        opt_logger?webdriver.logging.Logger=

        The logger to add the handler to; defaults +to the root logger.

        +

        getLevel(arg0)code »

        Parameters
        arg0(number|string)

        getLogger(arg0)code »

        Parameters
        arg0string=

        installConsoleHandler()code »

        Installs the console log handler on the root logger.

        +

        removeConsoleHandler(opt_logger)code »

        Removes the console log handler from the given logger.

        +
        Parameters
        opt_logger?webdriver.logging.Logger=

        The logger to remove the handler from; defaults +to the root logger.

        +

        Types

        Entry

        A single log entry recorded by a WebDriver component, such as a remote +WebDriver server.

        +
        Level

        The Level class defines a set of standard logging levels that +can be used to control logging output.

        +
        LogRecord

        LogRecord objects are used to pass logging requests between +the logging framework and individual log Handlers.

        +
        Logger

        The Logger is an object used for logging debug messages.

        +
        Preferences

        Describes the log preferences for a WebDriver session.

        +
        Type

        No description.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_promise.html b/docs/module_selenium-webdriver_namespace_promise.html new file mode 100644 index 0000000..7537593 --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_promise.html @@ -0,0 +1,162 @@ +promise

        namespace promise

        Alias for webdriver.promise

        Functions

        <T> all(arr)code »

        Given an array of promises, will return a promise that will be fulfilled +with the fulfillment values of the input array's values. If any of the +input array's promises are rejected, the returned promise will be rejected +with the same reason.

        +
        Parameters
        arrArray<(T|webdriver.promise.Promise<T>)>

        An array of +promises to wait on.

        +
        Returns
        webdriver.promise.Promise<Array<T>>

        A promise that is +fulfilled with an array containing the fulfilled values of the +input array, or rejected with the same reason as the first +rejected value.

        +

        asap(value, callback, opt_errback)code »

        Invokes the appropriate callback function as soon as a promised +value is resolved. This function is similar to +webdriver.promise.when, except it does not return a new promise.

        +
        Parameters
        value*

        The value to observe.

        +
        callbackFunction

        The function to call when the value is +resolved successfully.

        +
        opt_errback?Function=

        The function to call when the value is +rejected.

        +

        captureStackTrace(name, msg, topFn)code »

        Generates an error to capture the current stack trace.

        +
        Parameters
        namestring

        Error name for this stack trace.

        +
        msgstring

        Message to record.

        +
        topFnFunction

        The function that should appear at the top of the +stack; only applicable in V8.

        +
        Returns
        Error

        The generated error.

        +

        checkedNodeCall(fn, var_args)code »

        Wraps a function that expects a node-style callback as its final +argument. This callback expects two arguments: an error value (which will be +null if the call succeeded), and the success value as the second argument. +The callback will the resolve or reject the returned promise, based on its arguments.

        +
        Parameters
        fnFunction

        The function to wrap.

        +
        var_args...?

        The arguments to apply to the function, excluding the +final callback.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved with the +result of the provided function's callback.

        +

        consume(generatorFn, opt_self, var_args)code »

        Consumes a GeneratorFunction. Each time the generator yields a +promise, this function will wait for it to be fulfilled before feeding the +fulfilled value back into next. Likewise, if a yielded promise is +rejected, the rejection error will be passed to throw.

        +

        Example 1: the Fibonacci Sequence.

        +
        promise.consume(function* fibonacci() {
        +  var n1 = 1, n2 = 1;
        +  for (var i = 0; i < 4; ++i) {
        +    var tmp = yield n1 + n2;
        +    n1 = n2;
        +    n2 = tmp;
        +  }
        +  return n1 + n2;
        +}).then(function(result) {
        +  console.log(result);  // 13
        +});
        +
        +

        Example 2: a generator that throws.

        +
        promise.consume(function* () {
        +  yield promise.delayed(250).then(function() {
        +    throw Error('boom');
        +  });
        +}).thenCatch(function(e) {
        +  console.log(e.toString());  // Error: boom
        +});
        +
        +
        Parameters
        generatorFnFunction

        The generator function to execute.

        +
        opt_self?Object=

        The object to use as "this" when invoking the +initial generator.

        +
        var_args...*

        Any arguments to pass to the initial generator.

        +
        Returns
        webdriver.promise.Promise<?>

        A promise that will resolve to the +generator's final result.

        +
        Throws
        TypeError

        If the given function is not a generator.

        +

        controlFlow()code »

        Returns
        webdriver.promise.ControlFlow

        The currently active control flow.

        +

        createFlow(callback)code »

        Creates a new control flow. The provided callback will be invoked as the +first task within the new flow, with the flow as its sole argument. Returns +a promise that resolves to the callback result.

        +
        Parameters
        callbackfunction(webdriver.promise.ControlFlow): ?

        The entry point +to the newly created flow.

        +
        Returns
        webdriver.promise.Promise

        A promise that resolves to the callback +result.

        +

        <T> defer()code »

        Creates a new deferred object.

        +
        Returns
        webdriver.promise.Deferred<T>

        The new deferred object.

        +

        delayed(ms)code »

        Creates a promise that will be resolved at a set time in the future.

        +
        Parameters
        msnumber

        The amount of time, in milliseconds, to wait before +resolving the promise.

        +
        Returns
        webdriver.promise.Promise

        The promise.

        +

        <TYPE, SELF> filter(arr, fn, opt_self)code »

        Calls a function for each element in an array, and if the function returns +true adds the element to a new array.

        +

        If the return value of the filter function is a promise, this function +will wait for it to be fulfilled before determining whether to insert the +element into the new array.

        +

        If the filter function throws or returns a rejected promise, the promise +returned by this function will be rejected with the same reason. Only the +first failure will be reported; all subsequent errors will be silently +ignored.

        +
        Parameters
        arr(Array<TYPE>|webdriver.promise.Promise<Array<TYPE>>)

        The +array to iterator over, or a promise that will resolve to said array.

        +
        fnfunction(this: SELF, TYPE, number, Array<TYPE>): ?(boolean|webdriver.promise.Promise<boolean>)

        The function +to call for each element in the array.

        +
        opt_self?SELF=

        The object to be used as the value of 'this' within +fn.

        +

        <T> fulfilled(opt_value)code »

        Creates a promise that has been resolved with the given value.

        +
        Parameters
        opt_value?T=

        The resolved value.

        +
        Returns
        webdriver.promise.Promise<T>

        The resolved promise.

        +

        fullyResolved(value)code »

        Returns a promise that will be resolved with the input value in a +fully-resolved state. If the value is an array, each element will be fully +resolved. Likewise, if the value is an object, all keys will be fully +resolved. In both cases, all nested arrays and objects will also be +fully resolved. All fields are resolved in place; the returned promise will +resolve on value and not a copy.

        +

        Warning: This function makes no checks against objects that contain +cyclical references:

        +
        var value = {};
        +value['self'] = value;
        +promise.fullyResolved(value);  // Stack overflow.
        +
        +
        Parameters
        value*

        The value to fully resolve.

        +
        Returns
        webdriver.promise.Promise

        A promise for a fully resolved version +of the input value.

        +

        isGenerator(fn)code »

        Tests is a function is a generator.

        +
        Parameters
        fnFunction

        The function to test.

        +
        Returns
        boolean

        Whether the function is a generator.

        +

        isPromise(value)code »

        Determines whether a value should be treated as a promise. +Any object whose "then" property is a function will be considered a promise.

        +
        Parameters
        value*

        The value to test.

        +
        Returns
        boolean

        Whether the value is a promise.

        +

        <TYPE, SELF> map(arr, fn, opt_self)code »

        Calls a function for each element in an array and inserts the result into a +new array, which is used as the fulfillment value of the promise returned +by this function.

        +

        If the return value of the mapping function is a promise, this function +will wait for it to be fulfilled before inserting it into the new array.

        +

        If the mapping function throws or returns a rejected promise, the +promise returned by this function will be rejected with the same reason. +Only the first failure will be reported; all subsequent errors will be +silently ignored.

        +
        Parameters
        arr(Array<TYPE>|webdriver.promise.Promise<Array<TYPE>>)

        The +array to iterator over, or a promise that will resolve to said array.

        +
        fnfunction(this: SELF, TYPE, number, Array<TYPE>): ?

        The +function to call for each element in the array. This function should +expect three arguments (the element, the index, and the array itself.

        +
        opt_self?SELF=

        The object to be used as the value of 'this' within +fn.

        +

        <T> rejected(opt_reason)code »

        Creates a promise that has been rejected with the given reason.

        +
        Parameters
        opt_reason*=

        The rejection reason; may be any value, but is +usually an Error or a string.

        +
        Returns
        webdriver.promise.Promise<T>

        The rejected promise.

        +

        setDefaultFlow(flow)code »

        Changes the default flow to use when no others are active.

        +
        Parameters
        flowwebdriver.promise.ControlFlow

        The new default flow.

        +
        Throws
        Error

        If the default flow is not currently active.

        +

        when(value, opt_callback, opt_errback)code »

        Registers an observer on a promised value, returning a new promise +that will be resolved when the value is. If value is not a promise, +then the return promise will be immediately resolved.

        +
        Parameters
        value*

        The value to observe.

        +
        opt_callback?Function=

        The function to call when the value is +resolved successfully.

        +
        opt_errback?Function=

        The function to call when the value is +rejected.

        +
        Returns
        webdriver.promise.Promise

        A new promise.

        +

        Compiler Constants

        LONG_STACK_TRACESboolean

        Whether to append traces of then to rejection +errors.

        +

        Types

        CancellationError

        Error used when the computation of a promise is cancelled.

        +
        ControlFlow

        Handles the execution of scheduled tasks, each of which may be an +asynchronous operation.

        +
        Deferred

        Represents a value that will be resolved at some point in the future.

        +
        Promise

        Represents the eventual value of a completed operation.

        +
        Thenable

        Thenable is a promise-like object with a then method which may be +used to schedule callbacks on a promised value.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_stacktrace.html b/docs/module_selenium-webdriver_namespace_stacktrace.html new file mode 100644 index 0000000..c947732 --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_stacktrace.html @@ -0,0 +1,17 @@ +stacktrace

        namespace stacktrace

        Alias for webdriver.stacktrace

        Functions

        format(error)code »

        Formats an error's stack trace.

        +
        Parameters
        errorError

        The error to format.

        +
        Returns
        Error

        The formatted error.

        +

        get()code »

        Gets the native stack trace if available otherwise follows the call chain. +The generated trace will exclude all frames up to and including the call to +this function.

        +
        Returns
        Array<webdriver.stacktrace.Frame>

        The frames of the stack trace.

        +

        getStack(error)code »

        Get an error's stack trace with the error string trimmed. +V8 prepends the string representation of an error to its stack trace. +This function trims the string so that the stack trace can be parsed +consistently with the other JS engines.

        +
        Parameters
        errorError

        The error.

        +
        Returns
        string

        The stack trace string.

        +

        Properties

        BROWSER_SUPPORTEDboolean

        Whether the current browser supports stack traces.

        +

        Types

        Frame

        Class representing one stack frame.

        +
        Snapshot

        Stores a snapshot of the stack trace at the time this instance was created.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_namespace_until.html b/docs/module_selenium-webdriver_namespace_until.html new file mode 100644 index 0000000..c42c0fb --- /dev/null +++ b/docs/module_selenium-webdriver_namespace_until.html @@ -0,0 +1,82 @@ +until

        namespace until

        Alias for webdriver.until

        Functions

        ableToSwitchToFrame(frame)code »

        Creates a condition that will wait until the input driver is able to switch +to the designated frame. The target frame may be specified as

        +
        1. a numeric index into +window.frames +for the currently selected frame.
        2. a webdriver.WebElement, which must reference a FRAME or IFRAME +element on the current page.
        3. a locator which may be used to first locate a FRAME or IFRAME on the +current page before attempting to switch to it.
        +

        Upon successful resolution of this condition, the driver will be left +focused on the new frame.

        +
        Parameters
        frame(number|webdriver.WebElement|webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The frame identifier.

        +
        Returns
        webdriver.until.Condition<boolean>

        A new condition.

        +

        alertIsPresent()code »

        Creates a condition that waits for an alert to be opened. Upon success, the +returned promise will be fulfilled with the handle for the opened alert.

        +
        Returns
        webdriver.until.Condition<webdriver.Alert>

        The new condition.

        +

        elementIsDisabled(element)code »

        Creates a condition that will wait for the given element to be disabled.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsEnabled(element)code »

        Creates a condition that will wait for the given element to be enabled.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsNotSelected(element)code »

        Creates a condition that will wait for the given element to be deselected.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsNotVisible(element)code »

        Creates a condition that will wait for the given element to be in the DOM, +yet not visible to the user.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsSelected(element)code »

        Creates a condition that will wait for the given element to be selected.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsVisible(element)code »

        Creates a condition that will wait for the given element to become visible.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementLocated(locator)code »

        Creates a condition that will loop until an element is +found with the given locator.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +to use.

        +

        elementTextContains(element, substr)code »

        Creates a condition that will wait for the given element's +visible text to contain the given +substring.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        substrstring

        The substring to search for.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementTextIs(element, text)code »

        Creates a condition that will wait for the given element's +visible text to match the given +text exactly.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        textstring

        The expected text.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementTextMatches(element, regex)code »

        Creates a condition that will wait for the given element's +visible text to match a regular +expression.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        regexRegExp

        The regular expression to test against.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementsLocated(locator)code »

        Creates a condition that will loop until at least one element is +found with the given locator.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +to use.

        +

        stalenessOf(element)code »

        Creates a condition that will wait for the given element to become stale. An +element is considered stale once it is removed from the DOM, or a new page +has loaded.

        +
        Parameters
        elementwebdriver.WebElement

        The element that should become stale.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleContains(substr)code »

        Creates a condition that will wait for the current page's title to contain +the given substring.

        +
        Parameters
        substrstring

        The substring that should be present in the page +title.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleIs(title)code »

        Creates a condition that will wait for the current page's title to match the +given value.

        +
        Parameters
        titlestring

        The expected page title.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleMatches(regex)code »

        Creates a condition that will wait for the current page's title to match the +given regular expression.

        +
        Parameters
        regexRegExp

        The regular expression to test against.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        Types

        Condition

        Defines a condition to

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_net.html b/docs/module_selenium-webdriver_net.html index 128d586..bef67ce 100644 --- a/docs/module_selenium-webdriver_net.html +++ b/docs/module_selenium-webdriver_net.html @@ -1 +1,7 @@ -selenium-webdriver/net

        Module selenium-webdriver/net

        code »
        Show:

        Functions

        code »getAddress ( opt_family )string

        Retrieves the external IP address for this host.

        Parameters
        opt_family: string=
        The IP family to retrieve. Defaults to "IPv4".
        Returns
        The IP address or undefined if not available.

        Retrieves a loopback address for this machine.

        Parameters
        opt_family: string=
        The IP family to retrieve. Defaults to "IPv4".
        Returns
        The IP address or undefined if not available.
        \ No newline at end of file +selenium-webdriver/net

        module selenium-webdriver/net

        Functions

        getAddress(opt_family)code »

        Retrieves the external IP address for this host.

        +
        Parameters
        opt_familystring=

        The IP family to retrieve. Defaults to "IPv4".

        +
        Returns
        string

        The IP address or undefined if not available.

        +

        getLoopbackAddress(opt_family)code »

        Retrieves a loopback address for this machine.

        +
        Parameters
        opt_familystring=

        The IP family to retrieve. Defaults to "IPv4".

        +
        Returns
        string

        The IP address or undefined if not available.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_net_portprober.html b/docs/module_selenium-webdriver_net_portprober.html index 0ae96a6..ba82679 100644 --- a/docs/module_selenium-webdriver_net_portprober.html +++ b/docs/module_selenium-webdriver_net_portprober.html @@ -1,6 +1,12 @@ -selenium-webdriver/net/portprober

        Module selenium-webdriver/net/portprober

        code »
        Show:

        Functions

        Parameters
        opt_host: string=
        The bound host to test the port against. - Defaults to INADDR_ANY.
        Returns
        A promise that will resolve - to a free port. If a port cannot be found, the promise will be - rejected.

        Tests if a port is free.

        Parameters
        port: number
        The port to test.
        opt_host: string=
        The bound host to test the port against. - Defaults to INADDR_ANY.
        Returns
        A promise that will resolve - with whether the port is free.
        \ No newline at end of file +selenium-webdriver/net/portprober

        module selenium-webdriver/net/portprober

        Functions

        findFreePort(opt_host)code »

        Parameters
        opt_hoststring=

        The bound host to test the port against. +Defaults to INADDR_ANY.

        +
        Returns
        webdriver.promise.Promise<number>

        A promise that will resolve +to a free port. If a port cannot be found, the promise will be +rejected.

        +

        isFree(port, opt_host)code »

        Tests if a port is free.

        +
        Parameters
        portnumber

        The port to test.

        +
        opt_hoststring=

        The bound host to test the port against. +Defaults to INADDR_ANY.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the port is free.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_opera.html b/docs/module_selenium-webdriver_opera.html new file mode 100644 index 0000000..0a3a33b --- /dev/null +++ b/docs/module_selenium-webdriver_opera.html @@ -0,0 +1,62 @@ +selenium-webdriver/opera

        module selenium-webdriver/opera

        Defines a WebDriver client for the +Opera web browser (v26+). Before using this module, you must download the +latest OperaDriver +release and +ensure it can be found on your system +PATH.

        +

        There are three primary classes exported by this module:

        +
        1. +

          ServiceBuilder: configures the +remote.DriverService +that manages the +OperaDriver +child process.

          +
        2. +

          Options: defines configuration options for each new Opera +session, such as which proxy to use, +what extensions to install, or +what command-line switches to use when +starting the browser.

          +
        3. +

          Driver: the WebDriver client; each new instance will control +a unique browser session with a clean user profile (unless otherwise +configured through the Options class).

          +
        +

        By default, every Opera session will use a single driver service, which is +started the first time a Driver instance is created and terminated +when this process exits. The default service will inherit its environment +from the current process and direct all output to /dev/null. You may obtain +a handle to this default service using +getDefaultService() and change its configuration +with setDefaultService().

        +

        You may also create a Driver with its own driver service. This is +useful if you need to capture the server's log output for a specific session:

        +
        var opera = require('selenium-webdriver/opera');
        +
        +var service = new opera.ServiceBuilder()
        +    .loggingTo('/my/log/file.txt')
        +    .enableVerboseLogging()
        +    .build();
        +
        +var options = new opera.Options();
        +// configure browser options ...
        +
        +var driver = new opera.Driver(options, service);
        +
        +

        Users should only instantiate the Driver class directly when they +need a custom driver service configuration (as shown above). For normal +operation, users should start Opera using the +selenium-webdriver.Builder.

        +

        Functions

        getDefaultService()code »

        Returns the default OperaDriver service. If such a service has not been +configured, one will be constructed using the default configuration for +a OperaDriver executable found on the system PATH.

        +
        Returns
        DriverService

        The default OperaDriver service.

        +

        setDefaultService(service)code »

        Sets the default service to use for new OperaDriver instances.

        +
        Parameters
        serviceDriverService

        The service to use.

        +
        Throws
        Error

        If the default service is currently running.

        +

        Types

        Driver

        Creates a new WebDriver client for Opera.

        +
        Options

        Class for managing OperaDriver specific options.

        +
        ServiceBuilder

        Creates remote.DriverService instances that manages an +OperaDriver +server in a child process.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_opera_class_Driver.html b/docs/module_selenium-webdriver_opera_class_Driver.html new file mode 100644 index 0000000..a559e32 --- /dev/null +++ b/docs/module_selenium-webdriver_opera_class_Driver.html @@ -0,0 +1,276 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        Creates a new WebDriver client for Opera.

        +

        new Driver(opt_config, opt_service, opt_flow)

        Parameters
        opt_config?(Capabilities|Options)=

        The configuration +options.

        +
        opt_service?DriverService=

        The session to use; will use +the default service by default.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow to use, or +null to use the currently active flow.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        This function is a no-op as file detectors are not supported by this +implementation.

        +

        Overrides: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_opera_class_Options.html b/docs/module_selenium-webdriver_opera_class_Options.html new file mode 100644 index 0000000..c325dbd --- /dev/null +++ b/docs/module_selenium-webdriver_opera_class_Options.html @@ -0,0 +1,44 @@ +Options

        class Options

        webdriver.Serializable
        +  └ Options

        Class for managing OperaDriver specific options.

        +

        new Options()

        Parameters
        None.

        Instance Methods

        addArguments(var_args)code »

        Add additional command line arguments to use when launching the Opera +browser. Each argument may be specified with or without the "--" prefix +(e.g. "--foo" and "foo"). Arguments with an associated value should be +delimited by an "=": "foo=bar".

        +
        Parameters
        var_args...(string|Array<string>)

        The arguments to add.

        +
        Returns
        Options

        A self reference.

        +

        addExtensions(var_args)code »

        Add additional extensions to install when launching Opera. Each extension +should be specified as the path to the packed CRX file, or a Buffer for an +extension.

        +
        Parameters
        var_args...(string|Buffer|Array<(string|Buffer)>)

        The +extensions to add.

        +
        Returns
        Options

        A self reference.

        +

        serialize()code »

        Converts this instance to its JSON wire protocol representation. Note this +function is an implementation not intended for general use.

        +

        Overrides: webdriver.Serializable

        Returns
        (T|IThenable<T>)
        +

        , +localState: (Object|undefined), +logPath: (string|undefined), +prefs: (Object|undefined)}} The JSON wire protocol representation +of this instance.

        +
        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences for the new session.

        +
        Parameters
        prefswebdriver.logging.Preferences

        The logging preferences.

        +
        Returns
        Options

        A self reference.

        +

        setOperaBinaryPath(path)code »

        Sets the path to the Opera binary to use. On Mac OS X, this path should +reference the actual Opera executable, not just the application binary. The +binary path be absolute or relative to the operadriver server executable, but +it must exist on the machine that will launch Opera.

        +
        Parameters
        pathstring

        The path to the Opera binary to use.

        +
        Returns
        Options

        A self reference.

        +

        setProxy(proxy)code »

        Sets the proxy settings for the new session.

        +
        Parameters
        proxyselenium-webdriver.ProxyConfig

        The proxy configuration to use.

        +
        Returns
        Options

        A self reference.

        +

        toCapabilities(opt_capabilities)code »

        Converts this options instance to a webdriver.Capabilities object.

        +
        Parameters
        opt_capabilities?Capabilities=

        The capabilities to merge +these options into, if any.

        +
        Returns
        webdriver.Capabilities

        The capabilities.

        +

        Static Functions

        Options.fromCapabilities(capabilities)code »

        Extracts the OperaDriver specific options from the given capabilities +object.

        +
        Parameters
        capabilitiesCapabilities

        The capabilities object.

        +
        Returns
        Options

        The OperaDriver options.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_opera_class_ServiceBuilder.html b/docs/module_selenium-webdriver_opera_class_ServiceBuilder.html new file mode 100644 index 0000000..12b209b --- /dev/null +++ b/docs/module_selenium-webdriver_opera_class_ServiceBuilder.html @@ -0,0 +1,35 @@ +ServiceBuilder

        class ServiceBuilder

        Creates remote.DriverService instances that manages an +OperaDriver +server in a child process.

        +

        new ServiceBuilder(opt_exe)

        Parameters
        opt_exestring=

        Path to the server executable to use. If omitted, +the builder will attempt to locate the operadriver on the current +PATH.

        +
        Throws
        Error

        If provided executable does not exist, or the operadriver +cannot be found on the PATH.

        +

        Instance Methods

        build()code »

        Creates a new DriverService using this instance's current configuration.

        +
        Returns
        DriverService

        A new driver service using this instance's +current configuration.

        +
        Throws
        Error

        If the driver exectuable was not specified and a default +could not be found on the current PATH.

        +

        enableVerboseLogging()code »

        Enables verbose logging.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        loggingTo(path)code »

        Sets the path of the log file the driver should log to. If a log file is +not specified, the driver will log to stderr.

        +
        Parameters
        pathstring

        Path of the log file to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        setStdio(config)code »

        Defines the stdio configuration for the driver service. See +child_process.spawn for more information.

        +
        Parameters
        config(string|Array<?(string|number|Stream)>)

        The +configuration to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        silent()code »

        Silence sthe drivers output.

        +
        Returns
        ServiceBuilder

        A self reference.

        +

        usingPort(port)code »

        Sets the port to start the OperaDriver on.

        +
        Parameters
        portnumber

        The port to use, or 0 for any free port.

        +
        Returns
        ServiceBuilder

        A self reference.

        +
        Throws
        Error

        If the port is invalid.

        +

        withEnvironment(env)code »

        Defines the environment to start the server under. This settings will be +inherited by every browser session started by the server.

        +
        Parameters
        envObject<string, string>

        The environment to use.

        +
        Returns
        ServiceBuilder

        A self reference.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_phantomjs.html b/docs/module_selenium-webdriver_phantomjs.html index a4fd791..a43f7d7 100644 --- a/docs/module_selenium-webdriver_phantomjs.html +++ b/docs/module_selenium-webdriver_phantomjs.html @@ -1 +1,2 @@ -selenium-webdriver/phantomjs

        Module selenium-webdriver/phantomjs

        code »
        Show:

        Functions

        code »createDriver ( opt_capabilities )!webdriver.WebDriver

        Creates a new PhantomJS WebDriver client.

        Parameters
        opt_capabilities: webdriver.Capabilities=
        The desired capabilities.
        Returns
        A new WebDriver instance.
        \ No newline at end of file +selenium-webdriver/phantomjs

        module selenium-webdriver/phantomjs

        Types

        Driver

        Creates a new WebDriver client for PhantomJS.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_phantomjs_class_Driver.html b/docs/module_selenium-webdriver_phantomjs_class_Driver.html new file mode 100644 index 0000000..69b5e42 --- /dev/null +++ b/docs/module_selenium-webdriver_phantomjs_class_Driver.html @@ -0,0 +1,299 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        Creates a new WebDriver client for PhantomJS.

        +

        new Driver(opt_capabilities, opt_flow)

        Parameters
        opt_capabilities?Capabilities=

        The desired capabilities.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow to use, or +null to use the currently active flow.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executePhantomJS(script, var_args)code »

        Executes a PhantomJS fragment. This method is similar to +#executeScript, except it exposes the +PhantomJS API to the injected +script.

        +

        The injected script will execute in the context of PhantomJS's +page variable. If a page has not been loaded before calling this +method, one will be created.

        +

        Be sure to wrap callback definitions in a try/catch block, as failures +may cause future WebDriver calls to fail.

        +

        Certain callbacks are used by GhostDriver (the PhantomJS WebDriver +implementation) and overriding these may cause the script to fail. It is +recommended that you check for existing callbacks before defining your own. +

        +

        As with #executeScript, the injected script may be defined as +a string for an anonymous function body (e.g. "return 123;"), or as a +function. If a function is provided, it will be decompiled to its original +source. Note that injecting functions is provided as a convenience to +simplify defining complex scripts. Care must be taken that the function +only references variables that will be defined in the page's scope and +that the function does not override Function.prototype.toString +(overriding toString() will interfere with how the function is +decompiled.

        +
        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise

        A promise that resolve to the +script's return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        This function is a no-op as file detectors are not supported by this +implementation.

        +

        Overrides: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_proxy.html b/docs/module_selenium-webdriver_proxy.html index 67b9e60..ab77a0f 100644 --- a/docs/module_selenium-webdriver_proxy.html +++ b/docs/module_selenium-webdriver_proxy.html @@ -1,24 +1,28 @@ -selenium-webdriver/proxy

        Module selenium-webdriver/proxy

        code »

        Defines functions for configuring a webdriver proxy: -

        
        - var webdriver = require('selenium-webdriver'),
        -     proxy = require('selenium-webdriver/proxy');
        +selenium-webdriver/proxy

        module selenium-webdriver/proxy

        Defines functions for configuring a webdriver proxy:

        +
        var webdriver = require('selenium-webdriver'),
        +    proxy = require('selenium-webdriver/proxy');
         
        - var driver = new webdriver.Builder()
        -     .withCapabilities(webdriver.Capabilities.chrome())
        -     .setProxy(proxy.manual({http: 'host:1234'}))
        -     .build();
        - 
        Show:

        Type Definitions

        code »ProxyConfig : ({proxyType: string}|{proxyType: string, proxyAutoconfigUrl: string}|{proxyType: string, ftpProxy: string, httpProxy: string, sslProxy: string, noProxy: string})
        Proxy configuration object, as defined by the WebDriver wire protocol.

        Functions

        code »direct ( )!ProxyConfig

        Configures WebDriver to bypass all browser proxies.

        Returns
        A new proxy configuration object.
        code »manual ( options )!ProxyConfig

        Manually configures the browser proxy. The following options are - supported: -

          -
        • ftp: Proxy host to use for FTP requests -
        • http: Proxy host to use for HTTP requests -
        • https: Proxy host to use for HTTPS requests -
        • bypass: A list of hosts requests should directly connect to, - bypassing any other proxies for that request. May be specified as a - comma separated string, or a list of strings. -
        - - Behavior is undefined for FTP, HTTP, and HTTPS requests if the - corresponding key is omitted from the configuration options.
        Parameters
        options: {ftp: (string|undefined), http: (string|undefined), https: (string|undefined), bypass: (string|!Array.<string>|undefined)}
        Proxy - configuration options.
        Returns
        A new proxy configuration object.
        code »pac ( url )!ProxyConfig

        Configures WebDriver to configure the browser proxy using the PAC file at - the given URL.

        Parameters
        url: string
        URL for the PAC proxy to use.
        Returns
        A new proxy configuration object.
        code »system ( )!ProxyConfig

        Configures WebDriver to use the current system's proxy.

        Returns
        A new proxy configuration object.
        \ No newline at end of file +var driver = new webdriver.Builder() + .withCapabilities(webdriver.Capabilities.chrome()) + .setProxy(proxy.manual({http: 'host:1234'})) + .build(); + +

        Functions

        direct()code »

        Configures WebDriver to bypass all browser proxies.

        +
        Returns
        {proxyType: string}

        A new proxy configuration object.

        +

        manual(options)code »

        Manually configures the browser proxy. The following options are +supported:

        +
        • ftp: Proxy host to use for FTP requests
        • http: Proxy host to use for HTTP requests
        • https: Proxy host to use for HTTPS requests
        • bypass: A list of hosts requests should directly connect to, +bypassing any other proxies for that request. May be specified as a +comma separated string, or a list of strings.
        +

        Behavior is undefined for FTP, HTTP, and HTTPS requests if the +corresponding key is omitted from the configuration options.

        +
        Parameters
        options{bypass: (string|Array<string>|undefined), ftp: (string|undefined), http: (string|undefined), https: (string|undefined)}

        Proxy +configuration options.

        +
        Returns
        {proxyType: string}

        A new proxy configuration object.

        +

        pac(url)code »

        Configures WebDriver to configure the browser proxy using the PAC file at +the given URL.

        +
        Parameters
        urlstring

        URL for the PAC proxy to use.

        +
        Returns
        {proxyType: string}

        A new proxy configuration object.

        +

        system()code »

        Configures WebDriver to use the current system's proxy.

        +
        Returns
        {proxyType: string}

        A new proxy configuration object.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_remote.html b/docs/module_selenium-webdriver_remote.html index 5b260c2..73a0e92 100644 --- a/docs/module_selenium-webdriver_remote.html +++ b/docs/module_selenium-webdriver_remote.html @@ -1,16 +1,8 @@ -selenium-webdriver/remote

        Module selenium-webdriver/remote

        code »

        Classes

        DriverService
        Manages the life and death of a native executable WebDriver server.
        SeleniumServer
        Manages the life and death of the Selenium standalone server.
        Show:

        Type Definitions

        Configuration options for a DriverService instance. -
          -
        • port - The port to start the server on (must be > 0). If the - port is provided as a promise, the service will wait for the promise to - resolve before starting. -
        • args - The arguments to pass to the service. If a promise is - provided, the service will wait for it to resolve before starting. -
        • path - The base path on the server for the WebDriver wire - protocol (e.g. '/wd/hub'). Defaults to '/'. -
        • env - The environment variables that should be visible to the - server process. Defaults to inheriting the current process's - environment. -
        • stdio - IO configuration for the spawned server process. For - more information, refer to the documentation of - child_process.spawn. -
        \ No newline at end of file +selenium-webdriver/remote
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_remote_class_DriverService.html b/docs/module_selenium-webdriver_remote_class_DriverService.html index df1f193..d5c56e7 100644 --- a/docs/module_selenium-webdriver_remote_class_DriverService.html +++ b/docs/module_selenium-webdriver_remote_class_DriverService.html @@ -1,19 +1,32 @@ -DriverService

        Class DriverService

        code »

        Manages the life and death of a native executable WebDriver server. - -

        It is expected that the driver server implements the - WebDriver - Wire Protocol. Furthermore, the managed server should support multiple - concurrent sessions, so that this class may be reused for multiple clients.

        Constructor

        DriverService ( executable, options )
        Parameters
        executable: string
        Path to the executable to run.
        options: !ServiceOptions
        Configuration options for the service.
        Show:

        Instance Methods

        Returns
        A promise that resolves to - the server's address.
        Throws
        Error
        If the server has not been started.
        Returns
        Whether the underlying service process is running.

        Stops the service if it is not currently running. This function will kill - the server immediately. To synchronize with the active control flow, use - #stop().

        Returns
        A promise that will be resolved when - the server has been stopped.

        Starts the server if it is not already running.

        Parameters
        opt_timeoutMs: number=
        How long to wait, in milliseconds, for the - server to start accepting requests. Defaults to 30 seconds.
        Returns
        A promise that will resolve - to the server's base URL when it has started accepting requests. If the - timeout expires before the server has started, the promise will be - rejected.

        Schedules a task in the current control flow to stop the server if it is - currently running.

        Returns
        A promise that will be resolved when - the server has been stopped.

        Instance Properties

        Promise that resolves to the server's address or null if the server has not - been started.

        code »process_ : child_process.ChildProcess

        Promise that tracks the status of shutting down the server, or null if the - server is not currently shutting down.

        Static Properties

        The default amount of time, in milliseconds, to wait for the server to - start.

        \ No newline at end of file +DriverService

        class DriverService

        Manages the life and death of a native executable WebDriver server.

        +

        It is expected that the driver server implements the +https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol. +Furthermore, the managed server should support multiple concurrent sessions, +so that this class may be reused for multiple clients.

        +

        new DriverService(executable, options)

        Parameters
        executablestring

        Path to the executable to run.

        +
        options{args: (Array<string>|webdriver.promise.Promise), env: (Object<string, string>|undefined), path: (string|undefined), port: (number|webdriver.promise.Promise), stdio: (string|Array<?(string|number|Stream)>|undefined)}

        Configuration options for the service.

        +

        Instance Methods

        address()code »

        Returns
        webdriver.promise.Promise

        A promise that resolves to +the server's address.

        +
        Throws
        Error

        If the server has not been started.

        +

        isRunning()code »

        Returns whether the underlying process is still running. This does not take +into account whether the process is in the process of shutting down.

        +
        Returns
        boolean

        Whether the underlying service process is running.

        +

        kill()code »

        Stops the service if it is not currently running. This function will kill +the server immediately. To synchronize with the active control flow, use +#stop().

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved when +the server has been stopped.

        +

        start(opt_timeoutMs)code »

        Starts the server if it is not already running.

        +
        Parameters
        opt_timeoutMsnumber=

        How long to wait, in milliseconds, for the +server to start accepting requests. Defaults to 30 seconds.

        +
        Returns
        webdriver.promise.Promise

        A promise that will resolve +to the server's base URL when it has started accepting requests. If the +timeout expires before the server has started, the promise will be +rejected.

        +

        stop()code »

        Schedules a task in the current control flow to stop the server if it is +currently running.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved when +the server has been stopped.

        +

        Static Properties

        DriverService.DEFAULT_START_TIMEOUT_MSnumber

        The default amount of time, in milliseconds, to wait for the server to +start.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_remote_class_FileDetector.html b/docs/module_selenium-webdriver_remote_class_FileDetector.html new file mode 100644 index 0000000..0bb166f --- /dev/null +++ b/docs/module_selenium-webdriver_remote_class_FileDetector.html @@ -0,0 +1,22 @@ +FileDetector

        class FileDetector

        final
        webdriver.FileDetector
        +  └ FileDetector

        A webdriver.FileDetector that may be used when running +against a remote +Selenium server.

        +

        When a file path on the local machine running this script is entered with +WebElement#sendKeys, this file detector +will transfer the specified file to the Selenium server's host; the sendKeys +command will be updated to use the transfered file's path.

        +

        Note: This class depends on a non-standard command supported on the +Java Selenium server. The file detector will fail if used with a server that +only supports standard WebDriver commands (such as the ChromeDriver).

        +

        new FileDetector()

        Parameters
        None.

        Instance Methods

        handleFile(driver, path)code »

        Handles the file specified by the given path, preparing it for use with +the current browser. If the path does not refer to a valid file, it will +be returned unchanged, otherwisee a path suitable for use with the current +browser will be returned.

        +

        This default implementation is a no-op. Subtypes may override this +function for custom tailored file handling.

        +

        Overrides: webdriver.FileDetector

        Parameters
        driverwebdriver.WebDriver

        The driver for the current browser.

        +
        pathstring

        The path to process.

        +
        Returns
        webdriver.promise.Promise<string>

        A promise for the processed +file path.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_remote_class_SeleniumServer.html b/docs/module_selenium-webdriver_remote_class_SeleniumServer.html index 3d496b8..9576f9c 100644 --- a/docs/module_selenium-webdriver_remote_class_SeleniumServer.html +++ b/docs/module_selenium-webdriver_remote_class_SeleniumServer.html @@ -1,31 +1,40 @@ -SeleniumServer

        Class SeleniumServer

        code »
        DriverService
        -  └ SeleniumServer

        Manages the life and death of the Selenium standalone server. The server - may be obtained from http://selenium-release.storage.googleapis.com/index.html.

        Constructor

        SeleniumServer ( jar, options )
        Parameters
        jar: string
        Path to the Selenium server jar.
        options: !SeleniumServer.Options
        Configuration options for the - server.
        Throws
        Error
        If an invalid port is specified.
        Show:

        Type Definitions

        Options for the Selenium server: -
          -
        • port - The port to start the server on (must be > 0). If the - port is provided as a promise, the service will wait for the promise to - resolve before starting. -
        • args - The arguments to pass to the service. If a promise is - provided, the service will wait for it to resolve before starting. -
        • jvmArgs - The arguments to pass to the JVM. If a promise is - provided, the service will wait for it to resolve before starting. -
        • env - The environment variables that should be visible to the - server process. Defaults to inheriting the current process's - environment. -
        • stdio - IO configuration for the spawned server process. For - more information, refer to the documentation of - child_process.spawn. -

        Instance Methods

        Returns
        A promise that resolves to - the server's address.
        Throws
        Error
        If the server has not been started.
        Returns
        Whether the underlying service process is running.

        Stops the service if it is not currently running. This function will kill - the server immediately. To synchronize with the active control flow, use - #stop().

        Returns
        A promise that will be resolved when - the server has been stopped.

        Starts the server if it is not already running.

        Parameters
        opt_timeoutMs: number=
        How long to wait, in milliseconds, for the - server to start accepting requests. Defaults to 30 seconds.
        Returns
        A promise that will resolve - to the server's base URL when it has started accepting requests. If the - timeout expires before the server has started, the promise will be - rejected.

        Schedules a task in the current control flow to stop the server if it is - currently running.

        Returns
        A promise that will be resolved when - the server has been stopped.

        Instance Properties

        Promise that resolves to the server's address or null if the server has not - been started.

        code »process_ : child_process.ChildProcess

        Promise that tracks the status of shutting down the server, or null if the - server is not currently shutting down.

        Static Properties

        \ No newline at end of file +SeleniumServer

        class SeleniumServer

        DriverService
        +  └ SeleniumServer

        Manages the life and death of the + +standalone Selenium server.

        +

        new SeleniumServer(jar, opt_options)

        Parameters
        jarstring

        Path to the Selenium server jar.

        +
        opt_options{args: (Array<string>|webdriver.promise.Promise), env: (Object<string, string>|undefined), jvmArgs: (Array<string>|webdriver.promise.Promise|undefined), port: (number|webdriver.promise.Promise), stdio: (string|Array<?(string|number|Stream)>|undefined)}=

        Configuration options for the +server.

        +
        Throws
        Error

        If the path to the Selenium jar is not specified or if an +invalid port is specified.

        +

        Instance Methods

        address()code »

        Defined by: DriverService

        Returns
        webdriver.promise.Promise

        A promise that resolves to +the server's address.

        +
        Throws
        Error

        If the server has not been started.

        +

        isRunning()code »

        Returns whether the underlying process is still running. This does not take +into account whether the process is in the process of shutting down.

        +

        Defined by: DriverService

        Returns
        boolean

        Whether the underlying service process is running.

        +

        kill()code »

        Stops the service if it is not currently running. This function will kill +the server immediately. To synchronize with the active control flow, use +#stop().

        +

        Defined by: DriverService

        Returns
        webdriver.promise.Promise

        A promise that will be resolved when +the server has been stopped.

        +

        start(opt_timeoutMs)code »

        Starts the server if it is not already running.

        +

        Defined by: DriverService

        Parameters
        opt_timeoutMsnumber=

        How long to wait, in milliseconds, for the +server to start accepting requests. Defaults to 30 seconds.

        +
        Returns
        webdriver.promise.Promise

        A promise that will resolve +to the server's base URL when it has started accepting requests. If the +timeout expires before the server has started, the promise will be +rejected.

        +

        stop()code »

        Schedules a task in the current control flow to stop the server if it is +currently running.

        +

        Defined by: DriverService

        Returns
        webdriver.promise.Promise

        A promise that will be resolved when +the server has been stopped.

        +

        Type Definitions

        SeleniumServer.Options{args: (Array<string>|webdriver.promise.Promise), env: (Object<string, string>|undefined), jvmArgs: (Array<string>|webdriver.promise.Promise|undefined), port: (number|webdriver.promise.Promise), stdio: (string|Array<?(string|number|Stream)>|undefined)}

        Options for the Selenium server:

        +
        • port - The port to start the server on (must be > 0). If the port is +provided as a promise, the service will wait for the promise to resolve +before starting.
        • args - The arguments to pass to the service. If a promise is provided, +the service will wait for it to resolve before starting.
        • jvmArgs - The arguments to pass to the JVM. If a promise is provided, +the service will wait for it to resolve before starting.
        • env - The environment variables that should be visible to the server +process. Defaults to inheriting the current process's environment.
        • stdio - IO configuration for the spawned server process. For more +information, refer to the documentation of child_process.spawn.
        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_safari.html b/docs/module_selenium-webdriver_safari.html new file mode 100644 index 0000000..a4c1235 --- /dev/null +++ b/docs/module_selenium-webdriver_safari.html @@ -0,0 +1,9 @@ +selenium-webdriver/safari

        module selenium-webdriver/safari

        Defines a WebDriver client for Safari. Before using this +module, you must install the +latest version +of the SafariDriver browser extension; using Safari for normal browsing is +not recommended once the extension has been installed. You can, and should, +disable the extension when the browser is not being used with WebDriver.

        +

        Types

        Driver

        A WebDriver client for Safari.

        +
        Options

        Configuration options specific to the SafariDriver.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_safari_class_Driver.html b/docs/module_selenium-webdriver_safari_class_Driver.html new file mode 100644 index 0000000..4ca43e0 --- /dev/null +++ b/docs/module_selenium-webdriver_safari_class_Driver.html @@ -0,0 +1,279 @@ +Driver

        class Driver

        webdriver.WebDriver
        +  └ Driver

        A WebDriver client for Safari. This class should never be instantiated +directly; instead, use the selenium-webdriver.Builder:

        +
        var driver = new Builder()
        +    .forBrowser('safari')
        +    .build();
        +
        +

        new Driver(opt_config, opt_flow)

        Parameters
        opt_config?(Options|Capabilities)=

        The configuration +options for the new session.

        +
        opt_flow?webdriver.promise.ControlFlow=

        The control flow to create +the driver under.

        +

        Instance Methods

        actions()code »

        Creates a new action sequence using this driver. The sequence will not be +scheduled for execution until webdriver.ActionSequence#perform is +called. Example:

        +
        driver.actions().
        +    mouseDown(element1).
        +    mouseMove(element2).
        +    mouseUp().
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.ActionSequence

        A new action sequence for this instance.

        +

        <T> call(fn, opt_scope, var_args)code »

        Schedules a command to execute a custom function.

        +

        Defined by: webdriver.WebDriver

        Parameters
        fnfunction(...?): (T|webdriver.promise.Promise<T>)

        The function to +execute.

        +
        opt_scope?Object=

        The object in whose scope to execute the function.

        +
        var_args...*

        Any arguments to pass to the function.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved' +with the function's result.

        +

        close()code »

        Schedules a command to close the current window.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when this command has completed.

        +

        controlFlow()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.ControlFlow

        The control flow used by this +instance.

        +

        <T> executeAsyncScript(script, var_args)code »

        Schedules a command to execute asynchronous JavaScript in the context of the +currently selected frame or window. The script fragment will be executed as +the body of an anonymous function. If the script is provided as a function +object, that function will be converted to a string for injection into the +target window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        Unlike executing synchronous JavaScript with #executeScript, +scripts executed with this function must explicitly signal they are finished +by invoking the provided callback. This callback will always be injected +into the executed function as the last argument, and thus may be referenced +with arguments[arguments.length - 1]. The following steps will be +taken for resolving this functions return value against the first argument +to the script's callback function:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Example #1: Performing a sleep that is synchronized with the currently +selected window:

        +
        var start = new Date().getTime();
        +driver.executeAsyncScript(
        +    'window.setTimeout(arguments[arguments.length - 1], 500);').
        +    then(function() {
        +      console.log(
        +          'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        +    });
        +
        +

        Example #2: Synchronizing a test with an AJAX application:

        +
        var button = driver.findElement(By.id('compose-button'));
        +button.click();
        +driver.executeAsyncScript(
        +    'var callback = arguments[arguments.length - 1];' +
        +    'mailClient.getComposeWindowWidget().onload(callback);');
        +driver.switchTo().frame('composeWidget');
        +driver.findElement(By.id('to')).sendKeys('dog@example.com');
        +
        +

        Example #3: Injecting a XMLHttpRequest and waiting for the result. In +this example, the inject script is specified with a function literal. When +using this format, the function is converted to a string for injection, so it +should not reference any symbols not defined in the scope of the page under +test.

        +
        driver.executeAsyncScript(function() {
        +  var callback = arguments[arguments.length - 1];
        +  var xhr = new XMLHttpRequest();
        +  xhr.open("GET", "/resource/data.json", true);
        +  xhr.onreadystatechange = function() {
        +    if (xhr.readyState == 4) {
        +      callback(xhr.responseText);
        +    }
        +  }
        +  xhr.send('');
        +}).then(function(str) {
        +  console.log(JSON.parse(str)['food']);
        +});
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        <T> executeScript(script, var_args)code »

        Schedules a command to execute JavaScript in the context of the currently +selected frame or window. The script fragment will be executed as the body +of an anonymous function. If the script is provided as a function object, +that function will be converted to a string for injection into the target +window.

        +

        Any arguments provided in addition to the script will be included as script +arguments and may be referenced using the arguments object. +Arguments may be a boolean, number, string, or webdriver.WebElement. +Arrays and objects may also be used as script arguments as long as each item +adheres to the types previously mentioned.

        +

        The script may refer to any variables accessible from the current window. +Furthermore, the script will execute in the window's context, thus +document may be used to refer to the current document. Any local +variables will not be available once the script has finished executing, +though global variables will persist.

        +

        If the script has a return value (i.e. if the script contains a return +statement), then the following steps will be taken for resolving this +functions return value:

        +
        • For a HTML element, the value will resolve to a +webdriver.WebElement
        • Null and undefined return values will resolve to null
        • Booleans, numbers, and strings will resolve as is
        • Functions will resolve to their string representation
        • For arrays and objects, each member item will be converted according to +the rules above
        +

        Defined by: webdriver.WebDriver

        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will resolve to the +scripts return value.

        +

        findElement(locator)code »

        Schedule a command to find an element on the page. If the element cannot be +found, a bot.ErrorCode.NO_SUCH_ELEMENT result will be returned +by the driver. Unlike other commands, this error cannot be suppressed. In +other words, scheduling a command to find an element doubles as an assert +that the element is present on the page. To test whether an element is +present on the page, use #isElementPresent instead.

        +

        The search criteria for an element may be defined using one of the +factories in the webdriver.By namespace, or as a short-hand +webdriver.By.Hash object. For example, the following two statements +are equivalent:

        +
        var e1 = driver.findElement(By.id('foo'));
        +var e2 = driver.findElement({id:'foo'});
        +
        +

        You may also provide a custom locator function, which takes as input +this WebDriver instance and returns a webdriver.WebElement, or a +promise that will resolve to a WebElement. For example, to find the first +visible link on a page, you could write:

        +
        var link = driver.findElement(firstVisibleLink);
        +
        +function firstVisibleLink(driver) {
        +  var links = driver.findElements(By.tagName('a'));
        +  return webdriver.promise.filter(links, function(link) {
        +    return links.isDisplayed();
        +  }).then(function(visibleLinks) {
        +    return visibleLinks[0];
        +  });
        +}
        +
        +

        When running in the browser, a WebDriver cannot manipulate DOM elements +directly; it may do so only through a webdriver.WebElement reference. +This function may be used to generate a WebElement from a DOM element. A +reference to the DOM element will be stored in a known location and this +driver will attempt to retrieve it through #executeScript. If the +element cannot be found (eg, it belongs to a different document than the +one this instance is currently focused on), a +bot.ErrorCode.NO_SUCH_ELEMENT error will be returned.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The +locator to use.

        +
        Returns
        webdriver.WebElement

        A WebElement that can be used to issue +commands against the located element. If the element is not found, the +element will be invalidated and all scheduled commands aborted.

        +

        findElements(locator)code »

        Schedule a command to search for multiple elements on the page.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +strategy to use when searching for the element.

        +
        Returns
        webdriver.promise.Promise<Array<webdriver.WebElement>>

        A +promise that will resolve to an array of WebElements.

        +

        get(url)code »

        Schedules a command to navigate to the given URL.

        +

        Defined by: webdriver.WebDriver

        Parameters
        urlstring

        The fully qualified URL to open.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the document has finished loading.

        +

        getAllWindowHandles()code »

        Schedules a command to retrieve the current list of available window handles.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<Array<string>>

        A promise that will +be resolved with an array of window handles.

        +

        getCapabilities()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Capabilities>

        A promise +that will resolve with the this instance's capabilities.

        +

        getCurrentUrl()code »

        Schedules a command to retrieve the URL of the current page.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current URL.

        +

        getPageSource()code »

        Schedules a command to retrieve the current page's source. The page source +returned is a representation of the underlying DOM: do not expect it to be +formatted or escaped in the same way as the response sent from the web +server.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page source.

        +

        getSession()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<webdriver.Session>

        A promise for this +client's session.

        +

        getTitle()code »

        Schedules a command to retrieve the current page's title.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current page's title.

        +

        getWindowHandle()code »

        Schedules a command to retrieve they current window handle.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved with the current window handle.

        +

        isElementPresent(locatorOrElement)code »

        Schedules a command to test if an element is present on the page.

        +

        If given a DOM element, this function will check if it belongs to the +document the driver is currently focused on. Otherwise, the function will +test if at least one element can be found with the given search criteria.

        +

        Defined by: webdriver.WebDriver

        Parameters
        locatorOrElement(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator to use, or the actual +DOM element to be located by the server.

        +
        Returns
        webdriver.promise.Promise<boolean>

        A promise that will resolve +with whether the element is present on the page.

        +

        manage()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.Options

        The options interface for this +instance.

        +


        quit()code »

        Schedules a command to quit the current session. After calling quit, this +instance will be invalidated and may no longer be used to issue commands +against the browser.

        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the command has completed.

        +

        <T> schedule(command, description)code »

        Schedules a webdriver.Command to be executed by this driver's +webdriver.CommandExecutor.

        +

        Defined by: webdriver.WebDriver

        Parameters
        commandwebdriver.Command

        The command to schedule.

        +
        descriptionstring

        A description of the command for debugging.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be resolved +with the command result.

        +

        setFileDetector(detector)code »

        Sets the file detector that should be +used with this instance.

        +

        Defined by: webdriver.WebDriver

        Parameters
        detectorwebdriver.FileDetector

        The detector to use or null.

        +

        sleep(ms)code »

        Schedules a command to make the driver sleep for the given amount of time.

        +

        Defined by: webdriver.WebDriver

        Parameters
        msnumber

        The amount of time, in milliseconds, to sleep.

        +
        Returns
        webdriver.promise.Promise<undefined>

        A promise that will be resolved +when the sleep has finished.

        +

        switchTo()code »

        Defined by: webdriver.WebDriver

        Returns
        webdriver.WebDriver.TargetLocator

        The target locator interface for +this instance.

        +

        takeScreenshot()code »

        Schedule a command to take a screenshot. The driver makes a best effort to +return a screenshot of the following, in order of preference:

        +
        1. Entire page +
        2. Current window +
        3. Visible portion of the current frame +
        4. The screenshot of the entire display containing the browser +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.promise.Promise<string>

        A promise that will be +resolved to the screenshot as a base-64 encoded PNG.

        +

        touchActions()code »

        Creates a new touch sequence using this driver. The sequence will not be +scheduled for execution until webdriver.TouchSequence#perform is +called. Example:

        +
        driver.touchActions().
        +    tap(element1).
        +    doubleTap(element2).
        +    perform();
        +
        +

        Defined by: webdriver.WebDriver

        Returns
        webdriver.TouchSequence

        A new touch sequence for this instance.

        +

        <T> wait(condition, opt_timeout, opt_message)code »

        Schedules a command to wait for a condition to hold. The condition may be +specified by a webdriver.until.Condition, as a custom function, or +as a webdriver.promise.Promise.

        +

        For a webdriver.until.Condition or function, the wait will repeatedly +evaluate the condition until it returns a truthy value. If any errors occur +while evaluating the condition, they will be allowed to propagate. In the +event a condition returns a promise, the +polling loop will wait for it to be resolved and use the resolved value for +whether the condition has been satisified. Note the resolution time for +a promise is factored into whether a wait has timed out.

        +

        Example: waiting up to 10 seconds for an element to be present and visible +on the page.

        +
        var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        +button.click();
        +
        +

        This function may also be used to block the command flow on the resolution +of a promise. When given a promise, the +command will simply wait for its resolution before completing. A timeout may +be provided to fail the command if the promise does not resolve before the +timeout expires.

        +

        Example: Suppose you have a function, startTestServer, that returns a +promise for when a server is ready for requests. You can block a WebDriver +client on this promise with:

        +
        var started = startTestServer();
        +driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        +driver.get(getServerUrl());
        +
        +

        Defined by: webdriver.WebDriver

        Parameters
        condition(webdriver.promise.Promise<T>|webdriver.until.Condition<T>|function(webdriver.WebDriver): T)

        The condition to +wait on, defined as a promise, condition object, or a function to +evaluate as a condition.

        +
        opt_timeoutnumber=

        How long to wait for the condition to be true.

        +
        opt_messagestring=

        An optional message to use if the wait times +out.

        +
        Returns
        webdriver.promise.Promise<T>

        A promise that will be fulfilled +with the first truthy value returned by the condition function, or +rejected if the condition times out.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_safari_class_Options.html b/docs/module_selenium-webdriver_safari_class_Options.html new file mode 100644 index 0000000..d725006 --- /dev/null +++ b/docs/module_selenium-webdriver_safari_class_Options.html @@ -0,0 +1,23 @@ +Options

        class Options

        webdriver.Serializable
        +  └ Options

        Configuration options specific to the SafariDriver.

        +

        new Options()

        Parameters
        None.

        Instance Methods

        serialize()code »

        Converts this instance to its JSON wire protocol representation. Note this +function is an implementation detail not intended for general use.

        +

        Overrides: webdriver.Serializable

        Returns
        Object<string, *>

        The JSON wire protocol representation of this +instance.

        +

        setCleanSession(clean)code »

        Sets whether to force Safari to start with a clean session. Enabling this +option will cause all global browser data to be deleted.

        +
        Parameters
        cleanboolean

        Whether to make sure the session has no cookies, +cache entries, local storage, or databases.

        +
        Returns
        Options

        A self reference.

        +

        setLoggingPrefs(prefs)code »

        Sets the logging preferences for the new session.

        +
        Parameters
        prefswebdriver.logging.Preferences

        The logging preferences.

        +
        Returns
        Options

        A self reference.

        +

        toCapabilities(opt_capabilities)code »

        Converts this options instance to a webdriver.Capabilities object.

        +
        Parameters
        opt_capabilities?Capabilities=

        The capabilities to merge +these options into, if any.

        +
        Returns
        webdriver.Capabilities

        The capabilities.

        +

        Static Functions

        Options.fromCapabilities(capabilities)code »

        Extracts the SafariDriver specific options from the given capabilities +object.

        +
        Parameters
        capabilitiesCapabilities

        The capabilities object.

        +
        Returns
        Options

        The ChromeDriver options.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_testing.html b/docs/module_selenium-webdriver_testing.html index 336a5bb..bb12638 100644 --- a/docs/module_selenium-webdriver_testing.html +++ b/docs/module_selenium-webdriver_testing.html @@ -1,72 +1,74 @@ -selenium-webdriver/testing

        Module selenium-webdriver/testing

        code »

        Provides wrappers around the following global functions from - Mocha's BDD interface: -

          -
        • after -
        • afterEach -
        • before -
        • beforeEach -
        • it -
        • it.only -
        • it.skip -
        • xit -
        +selenium-webdriver/testing

        module selenium-webdriver/testing

        Provides wrappers around the following global functions from +Mocha's BDD interface:

        +
        • after
        • afterEach
        • before
        • beforeEach
        • it
        • it.only
        • it.skip
        • xit
        +

        The provided wrappers leverage the webdriver.promise.ControlFlow +to simplify writing asynchronous tests:

        +
        var By = require('selenium-webdriver').By,
        +    until = require('selenium-webdriver').until,
        +    firefox = require('selenium-webdriver/firefox'),
        +    test = require('selenium-webdriver/testing');
         
        - 

        The provided wrappers leverage the webdriver.promise.ControlFlow to - simplify writing asynchronous tests: -

        
        - var webdriver = require('selenium-webdriver'),
        -     remote = require('selenium-webdriver/remote'),
        -     test = require('selenium-webdriver/testing');
        +test.describe('Google Search', function() {
        +  var driver;
         
        - test.describe('Google Search', function() {
        -   var driver, server;
        +  test.before(function() {
        +    driver = new firefox.Driver();
        +  });
         
        -   test.before(function() {
        -     server = new remote.SeleniumServer({
        -       jar: 'path/to/selenium-server-standalone.jar'
        -     });
        -     server.start();
        +  test.after(function() {
        +    driver.quit();
        +  });
         
        -     driver = new webdriver.Builder().
        -         withCapabilities({'browserName': 'firefox'}).
        -         usingServer(server.address()).
        -         build();
        -   });
        +  test.it('should append query to title', function() {
        +    driver.get('http://www.google.com/ncr');
        +    driver.findElement(By.name('q')).sendKeys('webdriver');
        +    driver.findElement(By.name('btnG')).click();
        +    driver.wait(until.titleIs('webdriver - Google Search'), 1000);
        +  });
        +});
        +
        +

        You may conditionally suppress a test function using the exported +"ignore" function. If the provided predicate returns true, the attached +test case will be skipped:

        +
        test.ignore(maybe()).it('is flaky', function() {
        +  if (Math.random() < 0.5) throw Error();
        +});
         
        -   test.after(function() {
        -     driver.quit();
        -     server.stop();
        -   });
        -
        -   test.it('should append query to title', function() {
        -     driver.get('http://www.google.com');
        -     driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
        -     driver.findElement(webdriver.By.name('btnG')).click();
        -     driver.wait(function() {
        -       return driver.getTitle().then(function(title) {
        -         return 'webdriver - Google Search' === title;
        -       });
        -     }, 1000, 'Waiting for title to update');
        -   });
        - });
        - 
        - -

        You may conditionally suppress a test function using the exported - "ignore" function. If the provided predicate returns true, the attached - test case will be skipped: -

        
        -   test.ignore(maybe()).it('is flaky', function() {
        -     if (Math.random() < 0.5) throw Error();
        -   });
        -
        -   function maybe() { return Math.random() < 0.5; }
        - 
        Show:

        Functions

        Register a function to call after the current suite finishes.

        Parameters
        fn: function()
        .

        Register a function to call after each test in a suite.

        Parameters
        fn: function()
        .

        Register a function to call before the current suite starts.

        Parameters
        fn: function()
        .

        Register a function to call before each test in a suite.

        Parameters
        fn: function()
        .
        code »describe ( name, fn )

        Registers a new test suite.

        Parameters
        name: string
        The suite name.
        fn: function()=
        The suite function, or undefined to define - a pending test suite.
        code »ignore ( predicateFn )!Object

        Ignores the test chained to this function if the provided predicate returns - true.

        Parameters
        predicateFn: function(): boolean
        A predicate to call to determine - if the test should be suppressed. This function MUST be synchronous.
        Returns
        An object with wrapped versions of #it() and - #describe() that ignore tests as indicated by the predicate.
        code »iit ( name, fn )

        An alias for #it() that flags the test as the only one that should - be run within the current suite.

        Parameters
        name: string
        The test name.
        fn: function()=
        The test function, or undefined to define - a pending test case.
        code »it ( name, fn )

        Add a test to the current suite.

        Parameters
        name: string
        The test name.
        fn: function()=
        The test function, or undefined to define - a pending test case.
        code »xdescribe ( name, fn )

        Defines a suppressed test suite.

        Parameters
        name: string
        The suite name.
        fn: function()=
        The suite function, or undefined to define - a pending test suite.
        code »xit ( name, fn )

        Adds a test to the current suite while suppressing it so it is not run.

        Parameters
        name: string
        The test name.
        fn: function()=
        The test function, or undefined to define - a pending test case.
        \ No newline at end of file +function maybe() { return Math.random() < 0.5; } + +

        Functions

        after(fn)code »

        Register a function to call after the current suite finishes.

        +
        Parameters
        fnfunction(): ?

        .

        +

        afterEach(fn)code »

        Register a function to call after each test in a suite.

        +
        Parameters
        fnfunction(): ?

        .

        +

        before(fn)code »

        Register a function to call before the current suite starts.

        +
        Parameters
        fnfunction(): ?

        .

        +

        beforeEach(fn)code »

        Register a function to call before each test in a suite.

        +
        Parameters
        fnfunction(): ?

        .

        +

        describe(name, fn)code »

        Registers a new test suite.

        +
        Parameters
        namestring

        The suite name.

        +
        fnfunction(): ?=

        The suite function, or undefined to define +a pending test suite.

        +

        ignore(predicateFn)code »

        Ignores the test chained to this function if the provided predicate returns +true.

        +
        Parameters
        predicateFnfunction(): boolean

        A predicate to call to determine +if the test should be suppressed. This function MUST be synchronous.

        +
        Returns
        Object

        An object with wrapped versions of #it() and +#describe() that ignore tests as indicated by the predicate.

        +

        iit(name, fn)code »

        An alias for #it() that flags the test as the only one that should +be run within the current suite.

        +
        Parameters
        namestring

        The test name.

        +
        fnfunction(): ?=

        The test function, or undefined to define +a pending test case.

        +

        it(name, fn)code »

        Add a test to the current suite.

        +
        Parameters
        namestring

        The test name.

        +
        fnfunction(): ?=

        The test function, or undefined to define +a pending test case.

        +

        xdescribe(name, fn)code »

        Defines a suppressed test suite.

        +
        Parameters
        namestring

        The suite name.

        +
        fnfunction(): ?=

        The suite function, or undefined to define +a pending test suite.

        +

        xit(name, fn)code »

        Adds a test to the current suite while suppressing it so it is not run.

        +
        Parameters
        namestring

        The test name.

        +
        fnfunction(): ?=

        The test function, or undefined to define +a pending test case.

        +
        \ No newline at end of file diff --git a/docs/module_selenium-webdriver_testing_assert.html b/docs/module_selenium-webdriver_testing_assert.html index d214b1f..d36cbdb 100644 --- a/docs/module_selenium-webdriver_testing_assert.html +++ b/docs/module_selenium-webdriver_testing_assert.html @@ -1,19 +1,9 @@ -selenium-webdriver/testing/assert

        Module selenium-webdriver/testing/assert

        code »

        Defines a library that simplifies writing assertions against - promised values. - -

        -
        - NOTE: This module is considered experimental and is subject to - change, or removal, at any time! -
        -
        - - Sample usage: -
        
        - var driver = new webdriver.Builder().build();
        - driver.get('http://www.google.com');
        -
        - assert(driver.getTitle()).equalTo('Google');
        - 

        Main

        assert ( value )!webdriver.testing.Assertion
        Parameters
        value: *
        The value to perform an assertion on.
        Returns
        The new assertion.
        Show:

        Functions

        code »register ( name, matcherTemplate )

        Registers a new assertion to expose from the - webdriver.testing.Assertion prototype.

        Parameters
        name: string
        The assertion name.
        matcherTemplate: (function(new: goog.labs.testing.Matcher, *)|{matches: function(*): boolean, describe: function(): string})
        Either the - matcher constructor to use, or an object literal defining a matcher.
        \ No newline at end of file +selenium-webdriver/testing/assert

        module selenium-webdriver/testing/assert

        Creates a new assertion.

        +

        assert(value)

        Parameters
        value*

        The value to perform an assertion on.

        +
        Returns
        webdriver.testing.Assertion

        The new assertion.

        +

        Functions

        register(name, matcherTemplate)code »

        Registers a new assertion to expose from the +webdriver.testing.Assertion prototype.

        +
        Parameters
        namestring

        The assertion name.

        +
        matcherTemplate(function(new: goog.labs.testing.Matcher, *): ?|{describe: function(): string, matches: function(*): boolean})

        Either the +matcher constructor to use, or an object literal defining a matcher.

        +
        \ No newline at end of file diff --git a/docs/namespace_PRIMITIVE_EQUALITY_PREDICATES.html b/docs/namespace_PRIMITIVE_EQUALITY_PREDICATES.html new file mode 100644 index 0000000..55bf9cd --- /dev/null +++ b/docs/namespace_PRIMITIVE_EQUALITY_PREDICATES.html @@ -0,0 +1 @@ +PRIMITIVE_EQUALITY_PREDICATES

        Namespace PRIMITIVE_EQUALITY_PREDICATES

        code »
        Show:

        Global Functions

        Parameters
        var1
        var2
        Parameters
        date1
        date2
        Parameters
        var1
        var2
        Parameters
        var1
        var2
        Parameters
        var1
        var2
        Parameters
        var1
        var2
        \ No newline at end of file diff --git a/docs/namespace_bot.html b/docs/namespace_bot.html index b3d27b1..994ded3 100644 --- a/docs/namespace_bot.html +++ b/docs/namespace_bot.html @@ -1,4 +1,4 @@ -bot

        Namespace bot

        code »

        Classes

        bot.Error
        Error extension that includes error status codes from the WebDriver wire - protocol: - http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes

        Enumerations

        bot.ErrorCode
        Error codes from the WebDriver wire protocol: - http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
        Show:
        \ No newline at end of file +bot

        namespace bot

        Types

        Error

        Represents an error returned from a WebDriver command request.

        +
        ErrorCode

        Error codes from the Selenium WebDriver protocol: +https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes

        +
        \ No newline at end of file diff --git a/docs/namespace_bot_json.html b/docs/namespace_bot_json.html index fa0a57e..54d65fa 100644 --- a/docs/namespace_bot_json.html +++ b/docs/namespace_bot_json.html @@ -1,4 +1,20 @@ -bot.json

        Namespace bot.json

        code »
        Show:

        Global Functions

        code »bot.json.parse ( jsonStr )*

        Parses a JSON string and returns the result.

        Parameters
        jsonStr: string
        The string to parse.
        Returns
        The JSON object.
        Throws
        Error
        If the input string is an invalid JSON string.
        code »bot.json.stringify ( jsonObj, opt_replacer )string

        Converts a JSON object to its string representation.

        Parameters
        jsonObj: *
        The input object.
        opt_replacer: ?(function(string, *): *)=
        A replacer function called - for each (key, value) pair that determines how the value should be - serialized. By default, this just returns the value and allows default - serialization to kick in.
        Returns
        A JSON string representation of the input object.

        Global Properties

        Whether the current browser supports the native JSON interface.

        Compiler Constants

        \ No newline at end of file +bot.json

        namespace bot.json

        Functions

        parse(jsonStr)code »

        Parses a JSON string and returns the result.

        +
        Parameters
        jsonStrstring

        The string to parse.

        +
        Returns

        The JSON object.

        +
        Throws
        Error

        If the input string is an invalid JSON string.

        +

        stringify(jsonObj, opt_replacer)code »

        Converts a JSON object to its string representation.

        +
        Parameters
        jsonObj*

        The input object.

        +
        opt_replacer?function(string, *): *=

        A replacer function called +for each (key, value) pair that determines how the value should be +serialized. By default, this just returns the value and allows default +serialization to kick in.

        +
        Returns
        string

        A JSON string representation of the input object.

        +

        Compiler Constants

        NATIVE_JSONboolean

        NATIVE_JSON indicates whether the code should rely on the +native JSON functions, if available.

        +

        The JSON functions can be defined by external libraries like Prototype +and setting this flag to false forces the use of Closure's goog.json +implementation. +

        If your JavaScript can be loaded by a third_party site and you are wary +about relying on the native functions, specify +"--define bot.json.NATIVE_JSON=false" to the Closure compiler. +

        \ No newline at end of file diff --git a/docs/namespace_bot_response.html b/docs/namespace_bot_response.html index 3525f53..73e7453 100644 --- a/docs/namespace_bot_response.html +++ b/docs/namespace_bot_response.html @@ -1,5 +1,18 @@ -bot.response

        Namespace bot.response

        code »
        Show:

        Type Definitions

        code »bot.response.ResponseObject : {status: bot.ErrorCode, value: (*|{message: string})}
        Type definition for a response object, as defined by the JSON wire protocol.

        Global Functions

        Checks that a response object does not specify an error as defined by the - WebDriver wire protocol. If the response object defines an error, it will - be thrown. Otherwise, the response will be returned as is.

        Parameters
        responseObj: !bot.response.ResponseObject
        The response object to - check.
        Returns
        The checked response object.
        Throws
        bot.Error
        If the response describes an error.

        Converts an error value into its JSON representation as defined by the - WebDriver wire protocol.

        Parameters
        error: (bot.Error|Error|*)
        The error value to convert.
        Returns
        The new response object.

        Creates a new success response object with the provided value.

        Parameters
        value: *
        The response value.
        Returns
        The new response object.
        Parameters
        value: *
        The value to test.
        Returns
        Whether the given value is a response object.
        \ No newline at end of file +bot.response

        namespace bot.response

        Functions

        checkResponse(responseObj)code »

        Checks that a response object does not specify an error as defined by the +WebDriver wire protocol. If the response object defines an error, it will +be thrown. Otherwise, the response will be returned as is.

        +
        Parameters
        responseObj{status: number, value: *}

        The response object to +check.

        +
        Returns
        {status: number, value: *}

        The checked response object.

        +
        Throws
        bot.Error

        If the response describes an error.

        +

        createErrorResponse(error)code »

        Converts an error value into its JSON representation as defined by the +WebDriver wire protocol.

        +
        Parameters
        error*

        The error value to convert.

        +
        Returns
        {status: number, value: *}

        The new response object.

        +

        createResponse(value)code »

        Creates a new success response object with the provided value.

        +
        Parameters
        value*

        The response value.

        +
        Returns
        {status: number, value: *}

        The new response object.

        +

        isResponseObject(value)code »

        Parameters
        value*

        The value to test.

        +
        Returns
        boolean

        Whether the given value is a response object.

        +

        Type Definitions

        response.ResponseObject{status: number, value: *}

        Type definition for a response object, as defined by the JSON wire protocol.

        +
        \ No newline at end of file diff --git a/docs/namespace_bot_userAgent.html b/docs/namespace_bot_userAgent.html index cde2533..0a884fb 100644 --- a/docs/namespace_bot_userAgent.html +++ b/docs/namespace_bot_userAgent.html @@ -1,21 +1,33 @@ -bot.userAgent

        Namespace bot.userAgent

        code »
        Show:

        Global Functions

        Whether the rendering engine version of the current browser is equal to or - greater than the given version. This implementation differs from - goog.userAgent.isVersion in the following ways: -

          -
        1. in a Firefox extension, tests the engine version through the XUL version - comparator service, because no window.navigator object is available -
        2. in IE, compares the given version to the current documentMode -
        Parameters
        version: (string|number)
        The version number to check.
        Returns
        Whether the browser engine version is the same or higher - than the given version.

        Whether the product version of the current browser is equal to or greater - than the given version. This implementation differs from - goog.userAgent.product.isVersion in the following ways: -

          -
        1. in a Firefox extension, tests the product version through the XUL version - comparator service, because no window.navigator object is available -
        2. on Android, always compares to the version to the OS version -
        Parameters
        version: (string|number)
        The version number to check.
        Returns
        Whether the browser product version is the same or higher - than the given version.

        Global Properties

        Whether the current browser is Android pre-gingerbread.

        Whether the current browser is Android pre-icecreamsandwich

        Android Operating System Version.

        Whether we are in a Firefox extension.

        When we are in a Firefox extension, this is a function that accepts a version - and returns whether the version of Gecko we are on is the same or higher - than the given version. When we are not in a Firefox extension, this is null.

        When we are in a Firefox extension, this is a function that accepts a version - and returns whether the version of Firefox we are on is the same or higher - than the given version. When we are not in a Firefox extension, this is null.

        Whether the current document is IE in IE10 (or newer) standards mode.

        Whether the current document is IE in IE9 (or newer) standards mode.

        Whether the current document is IE in a documentMode older than 10.

        Whether the current document is IE in a documentMode older than 8.

        Whether the current document is IE in a documentMode older than 9.

        Whether we are on IOS.

        Whether we are on a mobile browser.

        Whether the current browser is Safari 6.

        Whether the current browser is Windows Phone.

        \ No newline at end of file +bot.userAgent

        namespace bot.userAgent

        Functions

        isEngineVersion(version)code »

        Whether the rendering engine version of the current browser is equal to or +greater than the given version. This implementation differs from +goog.userAgent.isVersion in the following ways:

        +
        1. in a Firefox extension, tests the engine version through the XUL version + comparator service, because no window.navigator object is available +
        2. in IE, compares the given version to the current documentMode +
        +
        Parameters
        version(string|number)

        The version number to check.

        +
        Returns
        boolean

        Whether the browser engine version is the same or higher +than the given version.

        +

        isProductVersion(version)code »

        Whether the product version of the current browser is equal to or greater +than the given version. This implementation differs from +goog.userAgent.product.isVersion in the following ways:

        +
        1. in a Firefox extension, tests the product version through the XUL version + comparator service, because no window.navigator object is available +
        2. on Android, always compares to the version to the OS version +
        +
        Parameters
        version(string|number)

        The version number to check.

        +
        Returns
        boolean

        Whether the browser product version is the same or higher +than the given version.

        +

        Properties

        ANDROID_PRE_GINGERBREADboolean

        Whether the current browser is Android pre-gingerbread.

        +
        ANDROID_PRE_ICECREAMSANDWICHboolean

        Whether the current browser is Android pre-icecreamsandwich

        +
        FIREFOX_EXTENSIONboolean

        Whether we are in a Firefox extension.

        +
        IE_DOC_10boolean

        Whether the current document is IE in IE10 (or newer) standards mode.

        +
        IE_DOC_9boolean

        Whether the current document is IE in IE9 (or newer) standards mode.

        +
        IE_DOC_PRE10boolean

        Whether the current document is IE in a documentMode older than 10.

        +
        IE_DOC_PRE8boolean

        Whether the current document is IE in a documentMode older than 8.

        +
        IE_DOC_PRE9boolean

        Whether the current document is IE in a documentMode older than 9.

        +
        IOSboolean

        Whether we are on IOS.

        +
        MOBILEboolean

        Whether we are on a mobile browser.

        +
        SAFARI_6boolean

        Whether the current browser is Safari 6.

        +
        WINDOWS_PHONEboolean

        Whether the current browser is Windows Phone.

        +
        \ No newline at end of file diff --git a/docs/namespace_goog.html b/docs/namespace_goog.html index 8c7b435..4d1beec 100644 --- a/docs/namespace_goog.html +++ b/docs/namespace_goog.html @@ -1,57 +1,53 @@ -goog

        Namespace goog

        code »

        Base namespace for the Closure library. Checks to see goog is - already defined in the current scope before assigning to prevent - clobbering if base.js is loaded more than once.

        Classes

        goog.Uri
        This class contains setters and getters for the parts of the URI.
        Show:

        Global Functions

        When defining a class Foo with an abstract method bar(), you can do: - +goog

        Namespace goog

        code »

        Base namespace for the Closure library. Checks to see goog is already + defined in the current scope before assigning to prevent clobbering if + base.js is loaded more than once.

        Classes

        goog.Disposable
        Class that provides the basic implementation for disposable objects.
        goog.Uri
        This class contains setters and getters for the parts of the URI.
        Show:

        Global Functions

        When defining a class Foo with an abstract method bar(), you can do: Foo.prototype.bar = goog.abstractMethod - Now if a subclass of Foo fails to override bar(), an error - will be thrown when bar() is invoked. + Now if a subclass of Foo fails to override bar(), an error will be thrown + when bar() is invoked. - Note: This does not take the name of the function to override as - an argument because that would make it more difficult to obfuscate - our JavaScript code.

        Throws
        Error
        when invoked to indicate the method should be - overridden.
        code »goog.addDependency ( relPath, provides, requires )

        Adds a dependency from a file to the files it requires.

        Parameters
        relPath: string
        The path to the js file.
        provides: Array
        An array of strings with the names of the objects + Note: This does not take the name of the function to override as an argument + because that would make it more difficult to obfuscate our JavaScript code.
        Throws
        Error
        when invoked to indicate the method should be overridden.
        code »goog.addDependency ( relPath, provides, requires, opt_isModule )

        Adds a dependency from a file to the files it requires.

        Parameters
        relPath: string
        The path to the js file.
        provides: Array
        An array of strings with the names of the objects this file provides.
        requires: Array
        An array of strings with the names of the objects - this file requires.

        Adds a getInstance static method that always return the same instance - object.

        Parameters
        ctor: !Function
        The constructor for the class to add the static - method to.
        code »goog.base ( me, opt_methodName, var_args )*

        Call up to the superclass. + this file requires.

        opt_isModule: boolean=
        Whether this dependency must be loaded as + a module as declared by goog.module.

        Adds a getInstance static method that always returns the same + instance object.

        Parameters
        ctor: !Function
        The constructor for the class to add the static + method to.
        code »goog.base ( me, opt_methodName, var_args )*

        Call up to the superclass. If this is called from a constructor, then this calls the superclass - contructor with arguments 1-N. + constructor with arguments 1-N. - If this is called from a prototype method, then you must pass - the name of the method as the second argument to this function. If - you do not, you will get a runtime error. This calls the superclass' - method with arguments 2-N. + If this is called from a prototype method, then you must pass the name of the + method as the second argument to this function. If you do not, you will get a + runtime error. This calls the superclass' method with arguments 2-N. - This function only works if you use goog.inherits to express - inheritance relationships between your classes. + This function only works if you use goog.inherits to express inheritance + relationships between your classes. - This function is a compiler primitive. At compile-time, the - compiler will do macro expansion to remove a lot of - the extra overhead that this function introduces. The compiler - will also enforce a lot of the assumptions that this function - makes, and treat it as a compiler error if you break them.

        Parameters
        me: !Object
        Should always be "this".
        opt_methodName: *=
        The method name if calling a super method.
        var_args: ...*
        The rest of the arguments.
        Returns
        The return value of the superclass method.
        code »<T> goog.bind ( fn, selfObj, var_args )!Function

        Partially applies this function to a particular 'this object' and zero or + This function is a compiler primitive. At compile-time, the compiler will do + macro expansion to remove a lot of the extra overhead that this function + introduces. The compiler will also enforce a lot of the assumptions that this + function makes, and treat it as a compiler error if you break them.

        Parameters
        me: !Object
        Should always be "this".
        opt_methodName: *=
        The method name if calling a super method.
        var_args: ...*
        The rest of the arguments.
        Returns
        The return value of the superclass method.
        code »<T> goog.bind ( fn, selfObj, var_args )!Function

        Partially applies this function to a particular 'this object' and zero or more arguments. The result is a new function with some arguments of the first - function pre-filled and the value of |this| 'pre-specified'.

        + function pre-filled and the value of this 'pre-specified'. - Remaining arguments specified at call-time are appended to the pre- - specified ones.

        + Remaining arguments specified at call-time are appended to the pre-specified + ones. - Also see: #partial.

        + Also see: #partial. Usage:

        var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
        - barMethBound('arg3', 'arg4');
        Parameters
        fn: ?function(this: T, ...)
        A function to partially apply.
        selfObj: T
        Specifies the object which |this| should - point to when the function is run.
        var_args: ...*
        Additional arguments that are partially - applied to the function.
        Returns
        A partially-applied form of the function bind() was - invoked as a method of.
        code »goog.bindJs_ ( fn, selfObj, var_args )!Function

        A pure-JS implementation of goog.bind.

        Parameters
        fn: Function
        A function to partially apply.
        selfObj: (Object|undefined)
        Specifies the object which |this| should - point to when the function is run.
        var_args: ...*
        Additional arguments that are partially - applied to the function.
        Returns
        A partially-applied form of the function bind() was - invoked as a method of.
        code »goog.bindNative_ ( fn, selfObj, var_args )!Function

        A native implementation of goog.bind.

        Parameters
        fn: Function
        A function to partially apply.
        selfObj: (Object|undefined)
        Specifies the object which |this| should - point to when the function is run.
        var_args: ...*
        Additional arguments that are partially - applied to the function.
        Returns
        A partially-applied form of the function bind() was - invoked as a method of.
        Deprecated: goog.cloneObject is unsafe. Prefer the goog.object methods.

        Clones a value. The input may be an Object, Array, or basic type. Objects and + barMethBound('arg3', 'arg4');

        Parameters
        fn: ?function(this: T, ...)
        A function to partially apply.
        selfObj: T
        Specifies the object which this should point to when the + function is run.
        var_args: ...*
        Additional arguments that are partially applied to the + function.
        Returns
        A partially-applied form of the function bind() was + invoked as a method of.
        code »goog.bindJs_ ( fn, selfObj, var_args )!Function

        A pure-JS implementation of goog.bind.

        Parameters
        fn: Function
        A function to partially apply.
        selfObj: (Object|undefined)
        Specifies the object which this should + point to when the function is run.
        var_args: ...*
        Additional arguments that are partially applied to the + function.
        Returns
        A partially-applied form of the function bind() was + invoked as a method of.
        code »goog.bindNative_ ( fn, selfObj, var_args )!Function

        A native implementation of goog.bind.

        Parameters
        fn: Function
        A function to partially apply.
        selfObj: (Object|undefined)
        Specifies the object which this should + point to when the function is run.
        var_args: ...*
        Additional arguments that are partially applied to the + function.
        Returns
        A partially-applied form of the function bind() was + invoked as a method of.
        Deprecated: goog.cloneObject is unsafe. Prefer the goog.object methods.

        Clones a value. The input may be an Object, Array, or basic type. Objects and arrays will be cloned recursively. WARNINGS: @@ -59,60 +55,83 @@ refer to themselves will cause infinite recursion. goog.cloneObject is unaware of unique identifiers, and copies - UIDs created by getUid into cloned results.

        Parameters
        obj: *
        The value to clone.
        Returns
        A clone of the input value.
        code »goog.define ( name, defaultValue )

        Defines a named value. In uncompiled mode, the value is retreived from - CLOSURE_DEFINES if the object is defined and has the property specified, - and otherwise used the defined defaultValue. When compiled, the default - can be overridden using compiler command-line options.

        Parameters
        name: string
        The distinguished name to provide.
        defaultValue
        code »goog.exportPath_ ( name, opt_object, opt_objectToExportTo )

        Builds an object structure for the provided namespace path, - ensuring that names that already exist are not overwritten. For - example: + UIDs created by getUid into cloned results.

        Parameters
        obj: *
        The value to clone.
        Returns
        A clone of the input value.
        code »goog.define ( name, defaultValue )

        Defines a named value. In uncompiled mode, the value is retreived from + CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + has the property specified, and otherwise used the defined defaultValue. + When compiled, the default can be overridden using compiler command-line + options.

        Parameters
        name: string
        The distinguished name to provide.
        defaultValue
        code »goog.defineClass ( superClass, def )!Function

        Creates a restricted form of a Closure "class": + - from the compiler's perspective, the instance returned from the + constructor is sealed (no new properties may be added). This enables + better checks. + - the compiler will rewrite this definition to a form that is optimal + for type checking and optimization (initially this will be a more + traditional form).

        Parameters
        superClass: Function
        The superclass, Object or null.
        def: goog.defineClass.ClassDescriptor
        An object literal describing the + the class. It may have the following properties: + "constructor": the constructor function + "statics": an object literal containing methods to add to the constructor + as "static" methods or a function that will receive the constructor + function as its only parameter to which static properties can + be added. + all other properties are added to the prototype.
        Returns
        The class constructor.

        Calls dispose on the argument if it supports it. If obj is not an + object with a dispose() method, this is a no-op.

        Parameters
        obj: *
        The object to dispose of.

        Calls dispose on each member of the list that supports it. (If the + member is an ArrayLike, then goog.disposeAll() will be called + recursively on each of its members.) If the member is not an object with a + dispose() method, then it is ignored.

        Parameters
        var_args: ...*
        The list.
        code »goog.exportPath_ ( name, opt_object, opt_objectToExportTo )

        Builds an object structure for the provided namespace path, ensuring that + names that already exist are not overwritten. For example: "a.b.c" -> a = {};a.b={};a.b.c={}; Used by goog.provide and goog.exportSymbol.

        Parameters
        name: string
        name of the object that this file defines.
        opt_object: *=
        the object to expose at the end of the path.
        opt_objectToExportTo: Object=
        The object to add the path to; default - is |goog.global|.
        code »goog.exportProperty ( object, publicName, symbol )

        Exports a property unobfuscated into the object's namespace. + is |goog.global|.

        code »goog.exportProperty ( object, publicName, symbol )

        Exports a property unobfuscated into the object's namespace. ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction); - ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);

        Parameters
        object: Object
        Object whose static property is being exported.
        publicName: string
        Unobfuscated name to export.
        symbol: *
        Object the name should point to.
        code »goog.exportSymbol ( publicPath, object, opt_objectToExportTo )

        Exposes an unobfuscated global namespace path for the given object. - Note that fields of the exported object *will* be obfuscated, - unless they are exported in turn via this function or - goog.exportProperty + ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);

        Parameters
        object: Object
        Object whose static property is being exported.
        publicName: string
        Unobfuscated name to export.
        symbol: *
        Object the name should point to.
        code »goog.exportSymbol ( publicPath, object, opt_objectToExportTo )

        Exposes an unobfuscated global namespace path for the given object. + Note that fields of the exported object *will* be obfuscated, unless they are + exported in turn via this function or goog.exportProperty. -

        Also handy for making public items that are defined in anonymous - closures. + Also handy for making public items that are defined in anonymous closures. ex. goog.exportSymbol('public.path.Foo', Foo); - ex. goog.exportSymbol('public.path.Foo.staticFunction', - Foo.staticFunction); + ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction); public.path.Foo.staticFunction(); ex. goog.exportSymbol('public.path.Foo.prototype.myMethod', Foo.prototype.myMethod); new public.path.Foo().myMethod();

        Parameters
        publicPath: string
        Unobfuscated name to export.
        object: *
        Object the name should point to.
        opt_objectToExportTo: Object=
        The object to add the path to; default - is |goog.global|.

        Tries to detect the base path of the base.js script that bootstraps Closure

        code »goog.getCssName ( className, opt_modifier )string

        Handles strings that are intended to be used as CSS class names. + is goog.global.

        Tries to detect the base path of base.js script that bootstraps Closure.

        Forward declares a symbol. This is an indication to the compiler that the + symbol may be used in the source yet is not required and may not be provided + in compilation. + + The most common usage of forward declaration is code that takes a type as a + function parameter but does not need to require it. By forward declaring + instead of requiring, no hard dependency is made, and (if not required + elsewhere) the namespace may never be required and thus, not be pulled + into the JavaScript binary. If it is required elsewhere, it will be type + checked as normal.

        Parameters
        name: string
        The namespace to forward declare in the form of + "goog.package.part".
        code »goog.getCssName ( className, opt_modifier )string

        Handles strings that are intended to be used as CSS class names. This function works in tandem with @see goog.setCssNameMapping. - Without any mapping set, the arguments are simple joined with a - hyphen and passed through unaltered. + Without any mapping set, the arguments are simple joined with a hyphen and + passed through unaltered. - When there is a mapping, there are two possible styles in which - these mappings are used. In the BY_PART style, each part (i.e. in - between hyphens) of the passed in css name is rewritten according - to the map. In the BY_WHOLE style, the full css name is looked up in - the map directly. If a rewrite is not specified by the map, the - compiler will output a warning. + When there is a mapping, there are two possible styles in which these + mappings are used. In the BY_PART style, each part (i.e. in between hyphens) + of the passed in css name is rewritten according to the map. In the BY_WHOLE + style, the full css name is looked up in the map directly. If a rewrite is + not specified by the map, the compiler will output a warning. - When the mapping is passed to the compiler, it will replace calls - to goog.getCssName with the strings from the mapping, e.g. + When the mapping is passed to the compiler, it will replace calls to + goog.getCssName with the strings from the mapping, e.g. var x = goog.getCssName('foo'); var y = goog.getCssName(this.baseClass, 'active'); becomes: var x= 'foo'; var y = this.baseClass + '-active'; - If one argument is passed it will be processed, if two are passed - only the modifier will be processed, as it is assumed the first - argument was generated as a result of calling goog.getCssName.

        Parameters
        className: string
        The class name.
        opt_modifier: string=
        A modifier to be appended to the class name.
        Returns
        The class name or the concatenation of the class name and - the modifier.
        Deprecated: Use goog.getUid instead.

        Adds a hash code field to an object. The hash code is unique for the - given object.

        Parameters
        obj: Object
        The object to get the hash code for.
        Returns
        The hash code for the object.
        code »goog.getMsg ( str, opt_values )string

        Gets a localized message. + If one argument is passed it will be processed, if two are passed only the + modifier will be processed, as it is assumed the first argument was generated + as a result of calling goog.getCssName.

        Parameters
        className: string
        The class name.
        opt_modifier: string=
        A modifier to be appended to the class name.
        Returns
        The class name or the concatenation of the class name and + the modifier.
        Deprecated: Use goog.getUid instead.

        Adds a hash code field to an object. The hash code is unique for the + given object.

        Parameters
        obj: Object
        The object to get the hash code for.
        Returns
        The hash code for the object.
        code »goog.getMsg ( str, opt_values )string

        Gets a localized message. This function is a compiler primitive. If you give the compiler a localized message bundle, it will replace the string at compile-time with a localized @@ -121,87 +140,95 @@ Messages must be initialized in the form: var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'}); -

        Parameters
        str: string
        Translatable string, places holders in the form {$foo}.
        opt_values: Object=
        Map of place holder name to value.
        Returns
        message with placeholders filled.

        Gets a localized message. If the message does not have a translation, gives a +

        Parameters
        str: string
        Translatable string, places holders in the form {$foo}.
        opt_values: Object=
        Map of place holder name to value.
        Returns
        message with placeholders filled.

        Gets a localized message. If the message does not have a translation, gives a fallback message. This is useful when introducing a new message that has not yet been translated into all languages. - This function is a compiler primtive. Must be used in the form: + This function is a compiler primitive. Must be used in the form: var x = goog.getMsgWithFallback(MSG_A, MSG_B); - where MSG_A and MSG_B were initialized with goog.getMsg.

        Parameters
        a: string
        The preferred message.
        b: string
        The fallback message.
        Returns
        The best translated message.
        code »goog.getObjectByName ( name, opt_obj )

        Returns an object based on its fully qualified external name. If you are - using a compilation pass that renames property names beware that using this - function will not find renamed properties.

        Parameters
        name: string
        The fully qualified name.
        opt_obj: Object=
        The object within which to look; default is - |goog.global|.
        Returns
        The value (object or primitive) or, if not found, null.

        Looks at the dependency rules and tries to determine the script file that - fulfills a particular rule.

        Parameters
        rule: string
        In the form goog.namespace.Class or project.script.
        Returns
        Url corresponding to the rule, or null.

        Gets a unique ID for an object. This mutates the object so that further - calls with the same object as a parameter returns the same value. The unique - ID is guaranteed to be unique across the current session amongst objects that - are passed into getUid. There is no guarantee that the ID is unique - or consistent across sessions. It is unsafe to generate unique ID for - function prototypes.

        Parameters
        obj: Object
        The object to get the unique ID for.
        Returns
        The unique ID for the object.

        Evals javascript in the global scope. In IE this uses execScript, other + where MSG_A and MSG_B were initialized with goog.getMsg.

        Parameters
        a: string
        The preferred message.
        b: string
        The fallback message.
        Returns
        The best translated message.
        code »goog.getObjectByName ( name, opt_obj )

        Returns an object based on its fully qualified external name. The object + is not found if null or undefined. If you are using a compilation pass that + renames property names beware that using this function will not find renamed + properties.

        Parameters
        name: string
        The fully qualified name.
        opt_obj: Object=
        The object within which to look; default is + |goog.global|.
        Returns
        The value (object or primitive) or, if not found, null.

        Looks at the dependency rules and tries to determine the script file that + fulfills a particular rule.

        Parameters
        rule: string
        In the form goog.namespace.Class or project.script.
        Returns
        Url corresponding to the rule, or null.

        Gets a unique ID for an object. This mutates the object so that further calls + with the same object as a parameter returns the same value. The unique ID is + guaranteed to be unique across the current session amongst objects that are + passed into getUid. There is no guarantee that the ID is unique or + consistent across sessions. It is unsafe to generate unique ID for function + prototypes.

        Parameters
        obj: Object
        The object to get the unique ID for.
        Returns
        The unique ID for the object.

        Evals JavaScript in the global scope. In IE this uses execScript, other browsers use goog.global.eval. If goog.global.eval does not evaluate in the global scope (for example, in Safari), appends a script tag instead. - Throws an exception if neither execScript or eval is defined.

        Parameters
        script: string
        JavaScript string.
        code »goog.globalize ( obj, opt_global )
        Deprecated: Properties may be explicitly exported to the global scope, but - this should no longer be done in bulk.

        Globalizes a whole namespace, such as goog or goog.lang.

        Parameters
        obj: Object
        The namespace to globalize.
        opt_global: Object=
        The object to add the properties to.
        code »goog.identityFunction ( opt_returnValue, var_args )
        Deprecated: Use goog.functions.identity instead.

        The identity function. Returns its first argument.

        Parameters
        opt_returnValue: *=
        The single value that will be returned.
        var_args: ...*
        Optional trailing arguments. These are ignored.
        Returns
        The first argument. We can't know the type -- just pass it along - without type.

        Imports a script if, and only if, that script hasn't already been imported. - (Must be called at execution time)

        Parameters
        src: string
        Script source.

        Tries to detect whether is in the context of an HTML document.

        Returns
        True if it looks like HTML document.
        code »goog.inherits ( childCtor, parentCtor )

        Inherit the prototype methods from one constructor into another. + Throws an exception if neither execScript or eval is defined.

        Parameters
        script: string
        JavaScript string.
        code »goog.globalize ( obj, opt_global )
        Deprecated: Properties may be explicitly exported to the global scope, but + this should no longer be done in bulk.

        Globalizes a whole namespace, such as goog or goog.lang.

        Parameters
        obj: Object
        The namespace to globalize.
        opt_global: Object=
        The object to add the properties to.

        Whether the given object is alreay assigned a unique ID. + + This does not modify the object.

        Parameters
        obj: Object
        The object to check.
        Returns
        Whether there an assigned unique id for the object.
        code »goog.identityFunction ( opt_returnValue, var_args )
        Deprecated: Use goog.functions.identity instead.

        The identity function. Returns its first argument.

        Parameters
        opt_returnValue: *=
        The single value that will be returned.
        var_args: ...*
        Optional trailing arguments. These are ignored.
        Returns
        The first argument. We can't know the type -- just pass it along + without type.

        Given a URL initiate retrieval and execution of the module.

        Parameters
        src: string
        Script source URL.
        code »goog.importScript_ ( src, opt_sourceText )

        Imports a script if, and only if, that script hasn't already been imported. + (Must be called at execution time)

        Parameters
        src: string
        Script source.
        opt_sourceText: string=
        The optionally source text to evaluate

        Tries to detect whether is in the context of an HTML document.

        Returns
        True if it looks like HTML document.
        code »goog.inherits ( childCtor, parentCtor )

        Inherit the prototype methods from one constructor into another. Usage:

          function ParentClass(a, b) { }
        - ParentClass.prototype.foo = function(a) { }
        + ParentClass.prototype.foo = function(a) { };
         
          function ChildClass(a, b, c) {
        -   goog.base(this, a, b);
        +   ChildClass.base(this, 'constructor', a, b);
          }
          goog.inherits(ChildClass, ParentClass);
         
          var child = new ChildClass('a', 'b', 'see');
        - child.foo(); // works
        - 
        - - In addition, a superclass' implementation of a method can be invoked - as follows: - -
        - ChildClass.prototype.foo = function(a) {
        -   ChildClass.superClass_.foo.call(this, a);
        -   // other code
        - };
        - 
        Parameters
        childCtor: Function
        Child class.
        parentCtor: Function
        Parent class.

        Returns true if the specified value is an array

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is an array.

        Returns true if the object looks like an array. To qualify as array like + child.foo(); // This works. +

        Parameters
        childCtor: Function
        Child class.
        parentCtor: Function
        Parent class.

        Returns true if the specified value is an array.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is an array.

        Returns true if the object looks like an array. To qualify as array like the value needs to be either a NodeList or an object with a Number length - property.

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is an array.

        Returns true if the specified value is a boolean

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is boolean.

        Returns true if the object looks like a Date. To qualify as Date-like - the value needs to be an object and have a getFullYear() function.

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is a like a Date.

        Returns true if the specified value is not |undefined|. + property.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is an array.

        Returns true if the specified value is a boolean.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is boolean.

        Returns true if the object looks like a Date. To qualify as Date-like the + value needs to be an object and have a getFullYear() function.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is a like a Date.

        Returns true if the specified value is not undefined. WARNING: Do not use this to test if an object has a property. Use the in - operator instead. Additionally, this function assumes that the global - undefined variable has not been redefined.

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is defined.

        Returns true if the specified value is defined and not null

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is defined and not null.

        Returns true if the specified value is a function

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is a function.

        Returns true if the specified value is |null|

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is null.

        Returns true if the specified value is a number

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is a number.

        Returns true if the specified value is an object. This includes arrays - and functions.

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is an object.

        Check if the given name has been goog.provided. This will return false for - names that are available only as implicit namespaces.

        Parameters
        name: string
        name of the object to look for.
        Returns
        Whether the name has been provided.

        Returns true if the specified value is a string

        Parameters
        val: *
        Variable to test.
        Returns
        Whether variable is a string.
        code »goog.mixin ( target, source )

        Copies all the members of a source object to a target object. This method + operator instead.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is defined.

        Returns true if the specified value is defined and not null.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is defined and not null.

        Returns true if the specified value is a function.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is a function.
        Returns
        Whether a goog.module is currently being initialized.

        Returns true if the specified value is null.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is null.

        Returns true if the specified value is a number.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is a number.

        Returns true if the specified value is an object. This includes arrays and + functions.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is an object.

        Check if the given name has been goog.provided. This will return false for + names that are available only as implicit namespaces.

        Parameters
        name: string
        name of the object to look for.
        Returns
        Whether the name has been provided.

        Returns true if the specified value is a string.

        Parameters
        val: ?
        Variable to test.
        Returns
        Whether variable is a string.
        Parameters
        moduleDef: (function(?): ?|string)
        The module definition.

        Load any deferred goog.module loads.

        Parameters
        msg
        code »goog.mixin ( target, source )

        Copies all the members of a source object to a target object. This method does not work on all browsers for all objects that contain keys such as - toString or hasOwnProperty. Use goog.object.extend for this purpose.

        Parameters
        target: Object
        Target.
        source: Object
        Source.
        Returns
        An integer value representing the number of milliseconds - between midnight, January 1, 1970 and the current time.

        Null function used for default values of callbacks, etc.

        Returns
        Nothing.
        code »goog.partial ( fn, var_args )!Function

        Like bind(), except that a 'this object' is not required. Useful when the + toString or hasOwnProperty. Use goog.object.extend for this purpose.

        Parameters
        target: Object
        Target.
        source: Object
        Source.

        goog.module serves two purposes: + - marks a file that must be loaded as a module + - reserves a namespace (it can not also be goog.provided) + and has three requirements: + - goog.module may not be used in the same file as goog.provide. + - goog.module must be the first statement in the file. + - only one goog.module is allowed per file. + When a goog.module annotated file is loaded, it is loaded enclosed in + a strict function closure. This means that: + - any variable declared in a goog.module file are private to the file, + not global. Although the compiler is expected to inline the module. + - The code must obey all the rules of "strict" JavaScript. + - the file will be marked as "use strict" + + NOTE: unlike goog.provide, goog.module does not declare any symbols by + itself.

        Parameters
        name: string
        Namespace provided by this file in the form + "goog.package.part", is expected but not required.
        Returns
        An integer value representing the number of milliseconds + between midnight, January 1, 1970 and the current time.

        Null function used for default values of callbacks, etc.

        Returns
        Nothing.
        code »goog.onScriptLoad_ ( script, scriptIndex )boolean

        A readystatechange handler for legacy IE

        Parameters
        script
        scriptIndex
        code »goog.partial ( fn, var_args )!Function

        Like bind(), except that a 'this object' is not required. Useful when the target function is already bound. Usage: var g = partial(f, arg1, arg2); - g(arg3, arg4);

        Parameters
        fn: Function
        A function to partially apply.
        var_args: ...*
        Additional arguments that are partially - applied to fn.
        Returns
        A partially-applied form of the function bind() was - invoked as a method of.

        Creates object stubs for a namespace. The presence of one or more + g(arg3, arg4);

        Parameters
        fn: Function
        A function to partially apply.
        var_args: ...*
        Additional arguments that are partially applied to fn.
        Returns
        A partially-applied form of the function bind() was + invoked as a method of.

        Creates object stubs for a namespace. The presence of one or more goog.provide() calls indicate that the file defines the given - objects/namespaces. Build tools also scan for provide/require statements + objects/namespaces. Provided objects must not be null or undefined. + Build tools also scan for provide/require statements to discern dependencies, build dependency files (see deps.js), etc.

        Parameters
        name: string
        Namespace provided by this file in the form - "goog.package.part".
        Deprecated: Use goog.removeUid instead.

        Removes the hash code field from an object.

        Parameters
        obj: Object
        The object to remove the field from.

        Removes the unique ID from an object. This is useful if the object was + "goog.package.part".

        Deprecated: Use goog.removeUid instead.

        Removes the hash code field from an object.

        Parameters
        obj: Object
        The object to remove the field from.

        Removes the unique ID from an object. This is useful if the object was previously mutated using goog.getUid in which case the mutation is - undone.

        Parameters
        obj: Object
        The object to remove the unique ID field from.

        Implements a system for the dynamic resolution of dependencies - that works in parallel with the BUILD system. Note that all calls - to goog.require will be stripped by the JSCompiler when the - --closure_pass option is used.

        Parameters
        name: string
        Namespace to include (as was given in goog.provide()) - in the form "goog.package.part".

        Allow for aliasing within scope functions. This function exists for - uncompiled code - in compiled code the calls will be inlined and the - aliases applied. In uncompiled code the function is simply run since the - aliases as written are valid JavaScript.

        Parameters
        fn: function()
        Function to call. This function can contain aliases + undone.
        Parameters
        obj: Object
        The object to remove the unique ID field from.

        Implements a system for the dynamic resolution of dependencies that works in + parallel with the BUILD system. Note that all calls to goog.require will be + stripped by the JSCompiler when the --closure_pass option is used.

        Parameters
        name: string
        Namespace to include (as was given in goog.provide()) in + the form "goog.package.part".
        Returns
        If called within a goog.module file, the associated namespace or + module otherwise null.

        Retrieve and execute a module.

        Parameters
        src: string
        Script source URL.

        Allow for aliasing within scope functions. This function exists for + uncompiled code - in compiled code the calls will be inlined and the aliases + applied. In uncompiled code the function is simply run since the aliases as + written are valid JavaScript.

        Parameters
        fn: function()
        Function to call. This function can contain aliases to namespaces (e.g. "var dom = goog.dom") or classes - (e.g. "var Timer = goog.Timer").
        code »goog.setCssNameMapping ( mapping, opt_style )

        Sets the map to check when returning a value from goog.getCssName(). Example: + (e.g. "var Timer = goog.Timer").

        code »goog.setCssNameMapping ( mapping, opt_style )

        Sets the map to check when returning a value from goog.getCssName(). Example:

          goog.setCssNameMapping({
            "goog": "a",
        @@ -217,24 +244,30 @@
          --closure_pass flag is set.
        Parameters
        mapping: !Object
        A map of strings to strings where keys are possible arguments to goog.getCssName() and values are the corresponding values that should be returned.
        opt_style: string=
        The style of css name mapping. There are two valid - options: 'BY_PART', and 'BY_WHOLE'.
        code »goog.setTestOnly ( opt_message )

        Marks that the current file should only be used for testing, and never for + options: 'BY_PART', and 'BY_WHOLE'.

        code »goog.setTestOnly ( opt_message )

        Marks that the current file should only be used for testing, and never for live code in production. - In the case of unit tests, the message may optionally be an exact - namespace for the test (e.g. 'goog.stringTest'). The linter will then - ignore the extra provide (if not explicitly defined in the code).

        Parameters
        opt_message: string=
        Optional message to add to the error that's - raised when used in production code.

        This is a "fixed" version of the typeof operator. It differs from the typeof - operator in such a way that null returns 'null' and arrays return 'array'.

        Parameters
        value: *
        The value to get the type of.
        Returns
        The name of the type.

        The default implementation of the import function. Writes a script tag to - import the script.

        Parameters
        src: string
        The script source.
        Returns
        True if the script was imported, false otherwise.

        Resolves dependencies based on the dependencies added using addDependency - and calls importScript_ in the correct order.

        Global Properties

        True if goog.dependencies_ is available.

        Name for unique ID property. Initialized in a way to help avoid collisions - with other closure javascript on the same page.

        Path for included scripts

        Optional obfuscation style for CSS class names. Should be set to either - 'BY_WHOLE' or 'BY_PART' if defined.

        Optional map of CSS class names to obfuscated names used with - goog.getCssName().

        This object is used to keep track of dependencies and other data that is - used for loading scripts

        Indicates whether or not we can call 'eval' directly to eval code in the + In the case of unit tests, the message may optionally be an exact namespace + for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + provide (if not explicitly defined in the code).

        Parameters
        opt_message: string=
        Optional message to add to the error that's + raised when used in production code.

        Sealing classes breaks the older idiom of assigning properties on the + prototype rather than in the constructor. As such, goog.defineClass + must not seal subclasses of these old-style classes until they are fixed. + Until then, this marks a class as "broken", instructing defineClass + not to seal subclasses.

        Parameters
        ctr: !Function
        The legacy constructor to tag as unsealable.

        This is a "fixed" version of the typeof operator. It differs from the typeof + operator in such a way that null returns 'null' and arrays return 'array'.

        Parameters
        value: *
        The value to get the type of.
        Returns
        The name of the type.
        code »goog.wrapModule_ ( srcUrl, scriptText )string

        Return an appropriate module text. Suitable to insert into + a script tag (that is unescaped).

        Parameters
        srcUrl
        scriptText
        code »goog.writeScriptTag_ ( src, opt_sourceText )boolean

        The default implementation of the import function. Writes a script tag to + import the script.

        Parameters
        src: string
        The script url.
        opt_sourceText: string=
        The optionally source text to evaluate
        Returns
        True if the script was imported, false otherwise.

        Resolves dependencies based on the dependencies added using addDependency + and calls importScript_ in the correct order.

        Global Properties

        True if goog.dependencies_ is available.

        Name for unique ID property. Initialized in a way to help avoid collisions + with other closure JavaScript on the same page.

        Name for unsealable tag property.

        Path for included scripts.

        Optional obfuscation style for CSS class names. Should be set to either + 'BY_WHOLE' or 'BY_PART' if defined.

        Optional map of CSS class names to obfuscated names used with + goog.getCssName().

        This object is used to keep track of dependencies and other data that is + used for loading scripts.

        Indicates whether or not we can call 'eval' directly to eval code in the global scope. Set to a Boolean by the first call to goog.globalEval (which - empirically tests whether eval works for globals). @see goog.globalEval

        code »goog.global : global this

        Reference to the global context. In most cases this will be 'window'.

        Namespaces implicitly defined by goog.provide. For example, - goog.provide('goog.events.Event') implicitly declares - that 'goog' and 'goog.events' must be namespaces.

        Object used to keep track of urls that have already been added. This - record allows the prevention of circular dependencies.

        All singleton classes that have been instantiated, for testing. Don't read + empirically tests whether eval works for globals). @see goog.globalEval

        code »goog.global : global this

        Reference to the global context. In most cases this will be 'window'.

        Namespaces implicitly defined by goog.provide. For example, + goog.provide('goog.events.Event') implicitly declares that 'goog' and + 'goog.events' must be namespaces.

        Object used to keep track of urls that have already been added. This record + allows the prevention of circular dependencies.

        All singleton classes that have been instantiated, for testing. Don't read it directly, use the goog.testing.singleton module. The compiler - removes this variable if unused.

        Compiler Constants

        \ No newline at end of file + removes this variable if unused.

        The registry of initialized modules: + the module identifier to module exports map.

        code »goog.moduleLoaderState_ : ({moduleName: (string|undefined), exportTestMethods: boolean}|null)

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_array.html b/docs/namespace_goog_array.html index 1c8f008..ae5a09b 100644 --- a/docs/namespace_goog_array.html +++ b/docs/namespace_goog_array.html @@ -1,12 +1,13 @@ -goog.array

        Namespace goog.array

        code »
        Show:

        Type Definitions

        Global Functions

        code »<T> goog.array.binaryInsert ( array, value, opt_compareFn )boolean

        Inserts a value into a sorted array. The array is not modified if the - value is already present.

        Parameters
        array: Array.<T>
        The array to modify.
        value: T
        The object to insert.
        opt_compareFn: ?function(T, T): number=
        Optional comparison function by - which the - array is ordered. Should take 2 arguments to compare, and return a - negative number, zero, or a positive number depending on whether the - first argument is less than, equal to, or greater than the second.
        Returns
        True if an element was inserted.
        code »goog.array.binaryRemove ( array, value, opt_compareFn )boolean

        Removes a value from a sorted array.

        Parameters
        array: Array
        The array to modify.
        value: *
        The object to remove.
        opt_compareFn: Function=
        Optional comparison function by which the - array is ordered. Should take 2 arguments to compare, and return a - negative number, zero, or a positive number depending on whether the - first argument is less than, equal to, or greater than the second.
        Returns
        True if an element was removed.
        code »goog.array.binarySearch ( arr, target, opt_compareFn )number

        Searches the specified array for the specified target using the binary +goog.array

        Namespace goog.array

        code »
        Show:

        Type Definitions

        Global Functions

        code »<VALUE> goog.array.binaryInsert ( array, value, opt_compareFn )boolean

        Inserts a value into a sorted array. The array is not modified if the + value is already present.

        Parameters
        array: (Array.<VALUE>|goog.array.ArrayLike)
        The array to modify.
        value: VALUE
        The object to insert.
        opt_compareFn: function(VALUE, VALUE): number=
        Optional comparison + function by which the array is ordered. Should take 2 arguments to + compare, and return a negative number, zero, or a positive number + depending on whether the first argument is less than, equal to, or + greater than the second.
        Returns
        True if an element was inserted.
        code »<VALUE> goog.array.binaryRemove ( array, value, opt_compareFn )boolean

        Removes a value from a sorted array.

        Parameters
        array: (!Array.<VALUE>|!goog.array.ArrayLike)
        The array to modify.
        value: VALUE
        The object to remove.
        opt_compareFn: function(VALUE, VALUE): number=
        Optional comparison + function by which the array is ordered. Should take 2 arguments to + compare, and return a negative number, zero, or a positive number + depending on whether the first argument is less than, equal to, or + greater than the second.
        Returns
        True if an element was removed.
        code »<TARGET, VALUE> goog.array.binarySearch ( arr, target, opt_compareFn )number

        Searches the specified array for the specified target using the binary search algorithm. If no opt_compareFn is specified, elements are compared using goog.array.defaultCompare, which compares the elements using the built in < and > operators. This will produce the expected @@ -16,13 +17,14 @@ If the array contains multiple instances of the specified target value, any of these instances may be found. - Runtime: O(log n)

        Parameters
        arr: goog.array.ArrayLike
        The array to be searched.
        target: *
        The sought value.
        opt_compareFn: Function=
        Optional comparison function by which the - array is ordered. Should take 2 arguments to compare, and return a - negative number, zero, or a positive number depending on whether the - first argument is less than, equal to, or greater than the second.
        Returns
        Lowest index of the target value if found, otherwise + Runtime: O(log n)
        Parameters
        arr: (Array.<VALUE>|goog.array.ArrayLike)
        The array to be searched.
        target: TARGET
        The sought value.
        opt_compareFn: function(TARGET, VALUE): number=
        Optional comparison + function by which the array is ordered. Should take 2 arguments to + compare, and return a negative number, zero, or a positive number + depending on whether the first argument is less than, equal to, or + greater than the second.
        Returns
        Lowest index of the target value if found, otherwise (-(insertion point) - 1). The insertion point is where the value should be inserted into arr to preserve the sorted property. Return value >= 0 - iff target is found.
        code »goog.array.binarySearch_ ( arr, compareFn, isEvaluator, opt_target, opt_selfObj )number

        Implementation of a binary search algorithm which knows how to use both + iff target is found.

        code »<THIS, VALUE, TARGET> goog.array.binarySearch_ ( arr, compareFn, isEvaluator, opt_target, opt_selfObj )number

        Implementation of a binary search algorithm which knows how to use both comparison functions and evaluators. If an evaluator is provided, will call the evaluator with the given optional data object, conforming to the interface defined in binarySelect. Otherwise, if a comparison function is @@ -31,40 +33,44 @@ This implementation purposefully does not use goog.bind or goog.partial for performance reasons. - Runtime: O(log n)

        Parameters
        arr: goog.array.ArrayLike
        The array to be searched.
        compareFn: Function
        Either an evaluator or a comparison function, - as defined by binarySearch and binarySelect above.
        isEvaluator: boolean
        Whether the function is an evaluator or a - comparison function.
        opt_target: *=
        If the function is a comparison function, then this is - the target to binary search for.
        opt_selfObj: Object=
        If the function is an evaluator, this is an + Runtime: O(log n)
        Parameters
        arr: (Array.<VALUE>|goog.array.ArrayLike)
        The array to be searched.
        compareFn: (function(TARGET, VALUE): number|function(this: THIS, VALUE, number, ?): number)
        Either an + evaluator or a comparison function, as defined by binarySearch + and binarySelect above.
        isEvaluator: boolean
        Whether the function is an evaluator or a + comparison function.
        opt_target: TARGET=
        If the function is a comparison function, then + this is the target to binary search for.
        opt_selfObj: THIS=
        If the function is an evaluator, this is an optional this object for the evaluator.
        Returns
        Lowest index of the target value if found, otherwise (-(insertion point) - 1). The insertion point is where the value should be inserted into arr to preserve the sorted property. Return value >= 0 - iff target is found.
        code »goog.array.binarySelect ( arr, evaluator, opt_obj )number

        Selects an index in the specified array using the binary search algorithm. + iff target is found.

        code »<THIS, VALUE> goog.array.binarySelect ( arr, evaluator, opt_obj )number

        Selects an index in the specified array using the binary search algorithm. The evaluator receives an element and determines whether the desired index is before, at, or after it. The evaluator must be consistent (formally, goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign) must be monotonically non-increasing). - Runtime: O(log n)

        Parameters
        arr: goog.array.ArrayLike
        The array to be searched.
        evaluator: Function
        Evaluator function that receives 3 arguments - (the element, the index and the array). Should return a negative number, - zero, or a positive number depending on whether the desired index is - before, at, or after the element passed to it.
        opt_obj: Object=
        The object to be used as the value of 'this' + Runtime: O(log n)
        Parameters
        arr: (Array.<VALUE>|goog.array.ArrayLike)
        The array to be searched.
        evaluator: function(this: THIS, VALUE, number, ?): number
        Evaluator function that receives 3 arguments (the element, the index and + the array). Should return a negative number, zero, or a positive number + depending on whether the desired index is before, at, or after the + element passed to it.
        opt_obj: THIS=
        The object to be used as the value of 'this' within evaluator.
        Returns
        Index of the leftmost element matched by the evaluator, if such exists; otherwise (-(insertion point) - 1). The insertion point is the index of the first element for which the evaluator returns negative, or arr.length if no such element exists. The return value is non-negative - iff a match is found.
        code »<T, S> goog.array.bucket ( array, sorter, opt_obj )!Object

        Splits an array into disjoint buckets according to a splitting function.

        Parameters
        array: Array.<T>
        The array.
        sorter: function(this: S, T, number, Array.<T>): ?
        Function to call for + iff a match is found.
        code »<T, S> goog.array.bucket ( array, sorter, opt_obj )!Object

        Splits an array into disjoint buckets according to a splitting function.

        Parameters
        array: Array.<T>
        The array.
        sorter: function(this: S, T, number, Array.<T>): ?
        Function to call for every element. This takes 3 arguments (the element, the index and the array) and must return a valid object key (a string, number, etc), or undefined, if that object should not be placed in a bucket.
        opt_obj: S=
        The object to be used as the value of 'this' within sorter.
        Returns
        An object, with keys being all of the unique return values of sorter, and values being arrays containing the items for - which the splitter returned that key.

        Clears the array.

        Parameters
        arr: goog.array.ArrayLike
        Array or array like object to clear.

        Does a shallow copy of an array.

        Parameters
        arr: goog.array.ArrayLike
        Array or array-like object to clone.
        Returns
        Clone of the input array.
        code »goog.array.compare ( arr1, arr2, opt_equalsFn )boolean
        Deprecated: Use goog.array.equals.
        code »goog.array.compare3 ( arr1, arr2, opt_compareFn )number

        3-way array compare function.

        Parameters
        arr1: !goog.array.ArrayLike
        The first array to compare.
        arr2: !goog.array.ArrayLike
        The second array to compare.
        opt_compareFn: ?function(?, ?): number=
        Optional comparison function - by which the array is to be ordered. Should take 2 arguments to compare, - and return a negative number, zero, or a positive number depending on - whether the first argument is less than, equal to, or greater than the - second.
        Returns
        Negative number, zero, or a positive number depending on + which the splitter returned that key.

        Clears the array.

        Parameters
        arr: goog.array.ArrayLike
        Array or array like object to clear.
        code »<T> goog.array.clone ( arr )!Array.<T>

        Does a shallow copy of an array.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array-like object to + clone.
        Returns
        Clone of the input array.
        code »<VALUE> goog.array.compare3 ( arr1, arr2, opt_compareFn )number

        3-way array compare function.

        Parameters
        arr1: (!Array.<VALUE>|!goog.array.ArrayLike)
        The first array to + compare.
        arr2: (!Array.<VALUE>|!goog.array.ArrayLike)
        The second array to + compare.
        opt_compareFn: function(VALUE, VALUE): number=
        Optional comparison + function by which the array is to be ordered. Should take 2 arguments to + compare, and return a negative number, zero, or a positive number + depending on whether the first argument is less than, equal to, or + greater than the second.
        Returns
        Negative number, zero, or a positive number depending on whether the first argument is less than, equal to, or greater than the - second.

        Returns a new array that is the result of joining the arguments. If arrays + second.

        Returns a new array that is the result of joining the arguments. If arrays are passed then their items are added, however, if non-arrays are passed they will be added to the return array as is. @@ -85,18 +91,18 @@ Internally goog.array should use this, so that all methods will continue to work on these broken array objects.

        Parameters
        var_args: ...*
        Items to concatenate. Arrays will have each item - added, while primitives and objects will be added as is.
        Returns
        The new resultant array.

        Whether the array contains the given object.

        Parameters
        arr: goog.array.ArrayLike
        The array to test for the presence of the - element.
        obj: *
        The object for which to test.
        Returns
        true if obj is present.
        code »<T, S> goog.array.count ( arr, f, opt_obj )number

        Counts the array elements that fulfill the predicate, i.e. for which the + added, while primitives and objects will be added as is.Returns

        The new resultant array.

        Whether the array contains the given object.

        Parameters
        arr: goog.array.ArrayLike
        The array to test for the presence of the + element.
        obj: *
        The object for which to test.
        Returns
        true if obj is present.
        code »<T, S> goog.array.count ( arr, f, opt_obj )number

        Counts the array elements that fulfill the predicate, i.e. for which the callback function returns true. Skips holes in the array.

        Parameters
        arr: !(Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: function(this: S, T, number, ?): boolean
        The function to call for - every element. Takes 3 arguments (the element, the index and the array).
        opt_obj: S=
        The object to be used as the value of 'this' within f.
        Returns
        The number of the matching elements.

        Compares its two arguments for order, using the built in < and > - operators.

        Parameters
        a: *
        The first object to be compared.
        b: *
        The second object to be compared.
        Returns
        A negative number, zero, or a positive number as the first - argument is less than, equal to, or greater than the second.

        Compares its two arguments for equality, using the built in === operator.

        Parameters
        a: *
        The first object to compare.
        b: *
        The second object to compare.
        Returns
        True if the two arguments are equal, false otherwise.
        code »goog.array.equals ( arr1, arr2, opt_equalsFn )boolean

        Compares two arrays for equality. Two arrays are considered equal if they + every element. Takes 3 arguments (the element, the index and the array).

        opt_obj: S=
        The object to be used as the value of 'this' within f.Returns
        The number of the matching elements.

        Compares its two arguments for order, using the built in < and > + operators.

        Parameters
        a: VALUE
        The first object to be compared.
        b: VALUE
        The second object to be compared.
        Returns
        A negative number, zero, or a positive number as the first + argument is less than, equal to, or greater than the second.

        Compares its two arguments for equality, using the built in === operator.

        Parameters
        a: *
        The first object to compare.
        b: *
        The second object to compare.
        Returns
        True if the two arguments are equal, false otherwise.
        code »goog.array.equals ( arr1, arr2, opt_equalsFn )boolean

        Compares two arrays for equality. Two arrays are considered equal if they have the same length and their corresponding elements are equal according to the comparison function.

        Parameters
        arr1: goog.array.ArrayLike
        The first array to compare.
        arr2: goog.array.ArrayLike
        The second array to compare.
        opt_equalsFn: Function=
        Optional comparison function. Should take 2 arguments to compare, and return true if the arguments are equal. Defaults to goog.array.defaultCompareEquality which - compares the elements using the built-in '===' operator.
        Returns
        Whether the two arrays are equal.
        code »<T, S> goog.array.every ( arr, f, opt_obj )boolean

        Call f for each element of an array. If all calls return true, every() + compares the elements using the built-in '===' operator.Returns

        Whether the two arrays are equal.
        code »<T, S> goog.array.every ( arr, f, opt_obj )boolean

        Call f for each element of an array. If all calls return true, every() returns true. If any call returns false, every() returns false and does not continue to check the remaining elements. @@ -104,7 +110,7 @@ like object over which to iterate.

        f: ?function(this: S, T, number, ?): boolean
        The function to call for for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.Returns
        false if any element fails the test.
        code »goog.array.extend ( arr1, var_args )

        Extends an array with another array, element, or "array like" object. + within f.Returns

        false if any element fails the test.
        code »<VALUE> goog.array.extend ( arr1, var_args )

        Extends an array with another array, element, or "array like" object. This function operates 'in-place', it does not create a new Array. Example: @@ -112,7 +118,8 @@ goog.array.extend(a, [0, 1]); a; // [0, 1] goog.array.extend(a, 2); - a; // [0, 1, 2]

        Parameters
        arr1: Array
        The array to modify.
        var_args: ...*
        The elements or arrays of elements to add to arr1.
        code »<T, S> goog.array.filter ( arr, f, opt_obj )!Array

        Calls a function for each element in an array, and if the function returns + a; // [0, 1, 2]

        Parameters
        arr1: Array.<VALUE>
        The array to modify.
        var_args: ...(Array.<VALUE>|VALUE)
        The elements or arrays of elements + to add to arr1.
        code »<T, S> goog.array.filter ( arr, f, opt_obj )!Array.<T>

        Calls a function for each element in an array, and if the function returns true adds the element to a new array. See http://tinyurl.com/developer-mozilla-org-array-filter

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array @@ -121,65 +128,70 @@ takes 3 arguments (the element, the index and the array) and must return a Boolean. If the return value is true the element is added to the result array. If it is false the element is not included.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.
        Returns
        a new array in which only elements that passed the test are - present.
        code »<T, S> goog.array.find ( arr, f, opt_obj )T

        Search an array for the first element that satisfies a given condition and + within f.Returns

        a new array in which only elements that passed the test + are present.
        code »<T, S> goog.array.find ( arr, f, opt_obj )?T

        Search an array for the first element that satisfies a given condition and return that element.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: S=
        An optional "this" context for the function.
        Returns
        The first array element that passes the test, or null if no - element is found.
        code »<T, S> goog.array.findIndex ( arr, f, opt_obj )number

        Search an array for the first element that satisfies a given condition and + element is found.

        code »<T, S> goog.array.findIndex ( arr, f, opt_obj )number

        Search an array for the first element that satisfies a given condition and return its index.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: S=
        An optional "this" context for the function.
        Returns
        The index of the first array element that passes the test, - or -1 if no element is found.
        code »<T, S> goog.array.findIndexRight ( arr, f, opt_obj )number

        Search an array (in reverse order) for the last element that satisfies a + or -1 if no element is found.

        code »<T, S> goog.array.findIndexRight ( arr, f, opt_obj )number

        Search an array (in reverse order) for the last element that satisfies a given condition and return its index.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: Object=
        An optional "this" context for the function.
        Returns
        The index of the last array element that passes the test, - or -1 if no element is found.
        code »<T, S> goog.array.findRight ( arr, f, opt_obj )T

        Search an array (in reverse order) for the last element that satisfies a + or -1 if no element is found.

        code »<T, S> goog.array.findRight ( arr, f, opt_obj )?T

        Search an array (in reverse order) for the last element that satisfies a given condition and return that element.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: S=
        An optional "this" context for the function.
        Returns
        The last array element that passes the test, or null if no - element is found.

        Returns an array consisting of every argument with all arrays - expanded in-place recursively.

        Parameters
        var_args: ...*
        The values to flatten.
        Returns
        An array containing the flattened values.
        code »<T, S> goog.array.forEach ( arr, f, opt_obj )

        Calls a function for each element in an array. Skips holes in the array. + element is found.

        Returns an array consisting of every argument with all arrays + expanded in-place recursively.

        Parameters
        var_args: ...*
        The values to flatten.
        Returns
        An array containing the flattened values.
        code »<T, S> goog.array.forEach ( arr, f, opt_obj )

        Calls a function for each element in an array. Skips holes in the array. See http://tinyurl.com/developer-mozilla-org-array-foreach

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): ?
        The function to call for every element. This function takes 3 arguments (the element, the index and the - array). The return value is ignored.
        opt_obj: S=
        The object to be used as the value of 'this' within f.
        code »<T, S> goog.array.forEachRight ( arr, f, opt_obj )

        Calls a function for each element in an array, starting from the last + array). The return value is ignored.

        opt_obj: S=
        The object to be used as the value of 'this' within f.
        code »<T, S> goog.array.forEachRight ( arr, f, opt_obj )

        Calls a function for each element in an array, starting from the last element rather than the first.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): ?
        The function to call for every element. This function takes 3 arguments (the element, the index and the array). The return value is ignored.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.
        code »goog.array.indexOf ( arr, obj, opt_fromIndex )number

        Returns the index of the first element of an array with a specified - value, or -1 if the element is not present in the array. + within f.

        code »<T> goog.array.indexOf ( arr, obj, opt_fromIndex )number

        Returns the index of the first element of an array with a specified value, or + -1 if the element is not present in the array. - See http://tinyurl.com/developer-mozilla-org-array-indexof

        Parameters
        arr: goog.array.ArrayLike
        The array to be searched.
        obj: *
        The object for which we are searching.
        opt_fromIndex: number=
        The index at which to start the search. If - omitted the search starts at index 0.
        Returns
        The index of the first matching array element.
        code »<T> goog.array.insert ( arr, obj )

        Pushes an item into an array, if it's not already in the array.

        Parameters
        arr: Array.<T>
        Array into which to insert the item.
        obj: T
        Value to add.
        code »goog.array.insertArrayAt ( arr, elementsToAdd, opt_i )

        Inserts at the given index of the array, all elements of another array.

        Parameters
        arr: goog.array.ArrayLike
        The array to modify.
        elementsToAdd: goog.array.ArrayLike
        The array of elements to add.
        opt_i: number=
        The index at which to insert the object. If omitted, - treated as 0. A negative index is counted from the end of the array.
        code »goog.array.insertAt ( arr, obj, opt_i )

        Inserts an object at the given index of the array.

        Parameters
        arr: goog.array.ArrayLike
        The array to modify.
        obj: *
        The object to insert.
        opt_i: number=
        The index at which to insert the object. If omitted, - treated as 0. A negative index is counted from the end of the array.
        code »<T> goog.array.insertBefore ( arr, obj, opt_obj2 )

        Inserts an object into an array before a specified object.

        Parameters
        arr: Array.<T>
        The array to modify.
        obj: T
        The object to insert.
        opt_obj2: T=
        The object before which obj should be inserted. If obj2 - is omitted or not found, obj is inserted at the end of the array.

        Whether the array is empty.

        Parameters
        arr: goog.array.ArrayLike
        The array to test.
        Returns
        true if empty.
        code »<T> goog.array.isSorted ( arr, opt_compareFn, opt_strict )boolean

        Tells if the array is sorted.

        Parameters
        arr: !Array.<T>
        The array.
        opt_compareFn: ?function(T, T): number=
        Function to compare the + See http://tinyurl.com/developer-mozilla-org-array-indexof
        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        The array to be searched.
        obj: T
        The object for which we are searching.
        opt_fromIndex: number=
        The index at which to start the search. If + omitted the search starts at index 0.
        Returns
        The index of the first matching array element.
        code »<T> goog.array.insert ( arr, obj )

        Pushes an item into an array, if it's not already in the array.

        Parameters
        arr: Array.<T>
        Array into which to insert the item.
        obj: T
        Value to add.
        code »goog.array.insertArrayAt ( arr, elementsToAdd, opt_i )

        Inserts at the given index of the array, all elements of another array.

        Parameters
        arr: goog.array.ArrayLike
        The array to modify.
        elementsToAdd: goog.array.ArrayLike
        The array of elements to add.
        opt_i: number=
        The index at which to insert the object. If omitted, + treated as 0. A negative index is counted from the end of the array.
        code »goog.array.insertAt ( arr, obj, opt_i )

        Inserts an object at the given index of the array.

        Parameters
        arr: goog.array.ArrayLike
        The array to modify.
        obj: *
        The object to insert.
        opt_i: number=
        The index at which to insert the object. If omitted, + treated as 0. A negative index is counted from the end of the array.
        code »<T> goog.array.insertBefore ( arr, obj, opt_obj2 )

        Inserts an object into an array before a specified object.

        Parameters
        arr: Array.<T>
        The array to modify.
        obj: T
        The object to insert.
        opt_obj2: T=
        The object before which obj should be inserted. If obj2 + is omitted or not found, obj is inserted at the end of the array.

        Whether the array is empty.

        Parameters
        arr: goog.array.ArrayLike
        The array to test.
        Returns
        true if empty.
        code »<T> goog.array.isSorted ( arr, opt_compareFn, opt_strict )boolean

        Tells if the array is sorted.

        Parameters
        arr: !Array.<T>
        The array.
        opt_compareFn: ?function(T, T): number=
        Function to compare the array elements. Should take 2 arguments to compare, and return a negative number, zero, or a positive number depending on whether the first argument is less - than, equal to, or greater than the second.
        opt_strict: boolean=
        If true no equal elements are allowed.
        Returns
        Whether the array is sorted.
        code »goog.array.lastIndexOf ( arr, obj, opt_fromIndex )number

        Returns the index of the last element of an array with a specified value, or + than, equal to, or greater than the second.

        opt_strict: boolean=
        If true no equal elements are allowed.
        Returns
        Whether the array is sorted.
        code »<T> goog.array.join ( var_args )!Array.<T>

        Returns a new array that contains the contents of all the arrays passed.

        Parameters
        var_args
        code »<T> goog.array.last ( array )T

        Returns the last element in an array without removing it. + Same as goog.array.peek.

        Parameters
        array: (Array.<T>|goog.array.ArrayLike)
        The array.
        Returns
        Last item in array.
        code »<T> goog.array.lastIndexOf ( arr, obj, opt_fromIndex )number

        Returns the index of the last element of an array with a specified value, or -1 if the element is not present in the array. - See http://tinyurl.com/developer-mozilla-org-array-lastindexof

        Parameters
        arr: goog.array.ArrayLike
        The array to be searched.
        obj: *
        The object for which we are searching.
        opt_fromIndex: ?number=
        The index at which to start the search. If - omitted the search starts at the end of the array.
        Returns
        The index of the last matching array element.
        code »<T, S> goog.array.map ( arr, f, opt_obj )!Array

        Calls a function for each element in an array and inserts the result into a + See http://tinyurl.com/developer-mozilla-org-array-lastindexof

        Parameters
        arr: (!Array.<T>|!goog.array.ArrayLike)
        The array to be searched.
        obj: T
        The object for which we are searching.
        opt_fromIndex: ?number=
        The index at which to start the search. If + omitted the search starts at the end of the array.
        Returns
        The index of the last matching array element.
        code »<THIS, VALUE, RESULT> goog.array.map ( arr, f, opt_obj )!Array.<RESULT>

        Calls a function for each element in an array and inserts the result into a new array. - See http://tinyurl.com/developer-mozilla-org-array-map

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array - like object over which to iterate.
        f: ?function(this: S, T, number, ?): ?
        The function to call for every - element. This function - takes 3 arguments (the element, the index and the array) and should - return something. The result will be inserted into a new array.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.
        Returns
        a new array with the results from f.
        code »goog.array.peek ( array )*

        Returns the last element in an array without removing it.

        Parameters
        array: goog.array.ArrayLike
        The array.
        Returns
        Last item in array.
        code »goog.array.range ( startOrEnd, opt_end, opt_step )!Array.<number>

        Creates a range of numbers in an arithmetic progression. + See http://tinyurl.com/developer-mozilla-org-array-map

        Parameters
        arr: (Array.<VALUE>|goog.array.ArrayLike)
        Array or array like object + over which to iterate.
        f: function(this: THIS, VALUE, number, ?): RESULT
        The function to call + for every element. This function takes 3 arguments (the element, + the index and the array) and should return something. The result will be + inserted into a new array.
        opt_obj: THIS=
        The object to be used as the value of 'this' within f.
        Returns
        a new array with the results from f.
        code »goog.array.moveItem ( arr, fromIndex, toIndex )

        Moves one item of an array to a new position keeping the order of the rest + of the items. Example use case: keeping a list of JavaScript objects + synchronized with the corresponding list of DOM elements after one of the + elements has been dragged to a new position.

        Parameters
        arr: !(Array|Arguments|{length: number})
        The array to modify.
        fromIndex: number
        Index of the item to move between 0 and + arr.length - 1.
        toIndex: number
        Target index between 0 and arr.length - 1.
        code »<T> goog.array.peek ( array )T

        Returns the last element in an array without removing it. + Same as goog.array.last.

        Parameters
        array: (Array.<T>|goog.array.ArrayLike)
        The array.
        Returns
        Last item in array.
        code »goog.array.range ( startOrEnd, opt_end, opt_step )!Array.<number>

        Creates a range of numbers in an arithmetic progression. Range takes 1, 2, or 3 arguments:

        @@ -191,7 +203,7 @@
              is provided. Otherwise, the start value is 0, and this is the end value.
        opt_end: number=
        The optional end value of the range.
        opt_step: number=
        The step size between range values. Defaults to 1 if opt_step is undefined or 0.Returns
        An array of numbers for the requested range. May be an empty array if adding the step would not converge toward the end - value.
        code »<T, S, R> goog.array.reduce ( arr, f, val, opt_obj )R

        Passes every element of an array into a function and accumulates the result. + value.

        code »<T, S, R> goog.array.reduce ( arr, f, val, opt_obj )R

        Passes every element of an array into a function and accumulates the result. See http://tinyurl.com/developer-mozilla-org-array-reduce @@ -205,7 +217,7 @@ the value of the current array element, the current array index, and the array itself) function(previousValue, currentValue, index, array).

        val: ?
        The initial value to pass into the function on the first call.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.Returns
        Result of evaluating f repeatedly across the values of the array.
        code »<T, S, R> goog.array.reduceRight ( arr, f, val, opt_obj )R

        Passes every element of an array into a function and accumulates the result, + within f.Returns

        Result of evaluating f repeatedly across the values of the array.
        code »<T, S, R> goog.array.reduceRight ( arr, f, val, opt_obj )R

        Passes every element of an array into a function and accumulates the result, starting from the last element and working towards the first. See http://tinyurl.com/developer-mozilla-org-array-reduceright @@ -221,28 +233,39 @@ array itself) function(previousValue, currentValue, index, array).

        val: ?
        The initial value to pass into the function on the first call.
        opt_obj: S=
        The object to be used as the value of 'this' within f.Returns
        Object returned as a result of evaluating f repeatedly across the - values of the array.

        Removes the first occurrence of a particular value from an array.

        Parameters
        arr: goog.array.ArrayLike
        Array from which to remove value.
        obj: *
        Object to remove.
        Returns
        True if an element was removed.

        Removes from an array the element at index i

        Parameters
        arr: goog.array.ArrayLike
        Array or array like object from which to - remove value.
        i: number
        The index to remove.
        Returns
        True if an element was removed.

        Removes all duplicates from an array (retaining only the first + values of the array.

        code »<T> goog.array.remove ( arr, obj )boolean

        Removes the first occurrence of a particular value from an array.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array from which to remove + value.
        obj: T
        Object to remove.
        Returns
        True if an element was removed.
        code »<T, S> goog.array.removeAllIf ( arr, f, opt_obj )number

        Removes all values that satisfy the given condition.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array + like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call + for every element. This function + takes 3 arguments (the element, the index and the array) and should + return a boolean.
        opt_obj: S=
        An optional "this" context for the function.
        Returns
        The number of items removed

        Removes from an array the element at index i

        Parameters
        arr: goog.array.ArrayLike
        Array or array like object from which to + remove value.
        i: number
        The index to remove.
        Returns
        True if an element was removed.
        code »<T> goog.array.removeDuplicates ( arr, opt_rv, opt_hashFn )

        Removes all duplicates from an array (retaining only the first occurrence of each array element). This function modifies the array in place and doesn't change the order of the non-duplicate items. For objects, duplicates are identified as having the same unique ID as defined by goog.getUid. + Alternatively you can specify a custom hash function that returns a unique + value for each item in the array it should consider unique. + Runtime: N, - Worstcase space: 2N (no dupes)

        Parameters
        arr: goog.array.ArrayLike
        The array from which to remove duplicates.
        opt_rv: Array=
        An optional array in which to return the results, + Worstcase space: 2N (no dupes)
        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        The array from which to remove + duplicates.
        opt_rv: Array=
        An optional array in which to return the results, instead of performing the removal inplace. If specified, the original - array will remain unchanged.
        code »<T, S> goog.array.removeIf ( arr, f, opt_obj )boolean

        Removes the first value that satisfies the given condition.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array + array will remain unchanged.
        opt_hashFn: function(T): string=
        An optional function to use to + apply to every item in the array. This function should return a unique + value for each item in the array it should consider unique.
        code »<T, S> goog.array.removeIf ( arr, f, opt_obj )boolean

        Removes the first value that satisfies the given condition.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate.
        f: ?function(this: S, T, number, ?): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the array) and should - return a boolean.
        opt_obj: S=
        An optional "this" context for the function.
        Returns
        True if an element was removed.

        Returns an array consisting of the given value repeated N times.

        Parameters
        value: *
        The value to repeat.
        n: number
        The repeat count.
        Returns
        An array with the repeated value.
        code »<T> goog.array.rotate ( array, n )!Array.<T>

        Rotates an array in-place. After calling this method, the element at + return a boolean.

        opt_obj: S=
        An optional "this" context for the function.
        Returns
        True if an element was removed.
        code »<VALUE> goog.array.repeat ( value, n )!Array.<VALUE>

        Returns an array consisting of the given value repeated N times.

        Parameters
        value: VALUE
        The value to repeat.
        n: number
        The repeat count.
        Returns
        An array with the repeated value.
        code »<T> goog.array.rotate ( array, n )!Array.<T>

        Rotates an array in-place. After calling this method, the element at index i will be the element previously at index (i - n) % array.length, for all values of i between 0 and array.length - 1, inclusive. For example, suppose list comprises [t, a, n, k, s]. After invoking - rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].

        Parameters
        array: !Array.<T>
        The array to rotate.
        n: number
        The amount to rotate.
        Returns
        The array.
        code »goog.array.shuffle ( arr, opt_randFn )

        Shuffles the values in the specified array using the Fisher-Yates in-place + rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].

        Parameters
        array: !Array.<T>
        The array to rotate.
        n: number
        The amount to rotate.
        Returns
        The array.
        code »goog.array.shuffle ( arr, opt_randFn )

        Shuffles the values in the specified array using the Fisher-Yates in-place shuffle (also known as the Knuth Shuffle). By default, calls Math.random() and so resets the state of that random number generator. Similarly, may reset the state of the any other specified random number generator. @@ -250,11 +273,11 @@ Runtime: O(n)

        Parameters
        arr: !Array
        The array to be shuffled.
        opt_randFn: function(): number=
        Optional random function to use for shuffling. Takes no arguments, and returns a random number on the interval [0, 1). - Defaults to Math.random() using JavaScript's built-in Math library.
        code »<T> goog.array.slice ( arr, start, opt_end )!Array.<T>

        Returns a new array from a segment of an array. This is a generic version of + Defaults to Math.random() using JavaScript's built-in Math library.

        code »<T> goog.array.slice ( arr, start, opt_end )!Array.<T>

        Returns a new array from a segment of an array. This is a generic version of Array slice. This means that it might work on other objects similar to arrays, such as the arguments object.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        The array from which to copy a segment.
        start: number
        The index of the first element to copy.
        opt_end: number=
        The index after the last element to copy.
        Returns
        A new array containing the specified segment of the - original array.
        code »<T, S> goog.array.some ( arr, f, opt_obj )boolean

        Calls f for each element of an array. If any call returns true, some() + original array.

        code »<T, S> goog.array.some ( arr, f, opt_obj )boolean

        Calls f for each element of an array. If any call returns true, some() returns true (without checking the remaining elements). If all calls return false, some() returns false. @@ -262,7 +285,7 @@ like object over which to iterate.

        f: ?function(this: S, T, number, ?): boolean
        The function to call for for every element. This function takes 3 arguments (the element, the index and the array) and should return a boolean.
        opt_obj: S=
        The object to be used as the value of 'this' - within f.Returns
        true if any element passes the test.
        code »<T> goog.array.sort ( arr, opt_compareFn )

        Sorts the specified array into ascending order. If no opt_compareFn is + within f.Returns

        true if any element passes the test.
        code »<T> goog.array.sort ( arr, opt_compareFn )

        Sorts the specified array into ascending order. If no opt_compareFn is specified, elements are compared using goog.array.defaultCompare, which compares the elements using the built in < and > operators. This will produce the expected behavior @@ -276,18 +299,18 @@ function by which the array is to be ordered. Should take 2 arguments to compare, and return a negative number, zero, or a positive number depending on whether the - first argument is less than, equal to, or greater than the second.

        code »goog.array.sortObjectsByKey ( arr, key, opt_compareFn )

        Sorts an array of objects by the specified object key and compare + first argument is less than, equal to, or greater than the second.

        code »goog.array.sortObjectsByKey ( arr, key, opt_compareFn )

        Sorts an array of objects by the specified object key and compare function. If no compare function is provided, the key values are compared in ascending order using goog.array.defaultCompare. This won't work for keys that get renamed by the compiler. So use {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.

        Parameters
        arr: Array.<Object>
        An array of objects to sort.
        key: string
        The object key to sort by.
        opt_compareFn: Function=
        The function to use to compare key - values.
        code »goog.array.splice ( arr, index, howMany, var_args )!Array

        Adds or removes elements from an array. This is a generic version of Array + values.

        code »<T> goog.array.splice ( arr, index, howMany, var_args )!Array.<T>

        Adds or removes elements from an array. This is a generic version of Array splice. This means that it might work on other objects similar to arrays, - such as the arguments object.

        Parameters
        arr: goog.array.ArrayLike
        The array to modify.
        index: (number|undefined)
        The index at which to start changing the + such as the arguments object.
        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        The array to modify.
        index: (number|undefined)
        The index at which to start changing the array. If not defined, treated as 0.
        howMany: number
        How many elements to remove (0 means no removal. A value below 0 is treated as zero and so is any other non number. Numbers - are floored).
        var_args: ...*
        Optional, additional elements to insert into the - array.
        Returns
        the removed elements.
        code »<T> goog.array.stableSort ( arr, opt_compareFn )

        Sorts the specified array into ascending order in a stable way. If no + are floored).

        var_args: ...T
        Optional, additional elements to insert into the + array.
        Returns
        the removed elements.
        code »<T> goog.array.stableSort ( arr, opt_compareFn )

        Sorts the specified array into ascending order in a stable way. If no opt_compareFn is specified, elements are compared using goog.array.defaultCompare, which compares the elements using the built in < and > operators. This will produce the expected behavior @@ -298,10 +321,11 @@ by which the array is to be ordered. Should take 2 arguments to compare, and return a negative number, zero, or a positive number depending on whether the first argument is less than, equal to, or greater than the - second.

        Converts an object to an array.

        Parameters
        object: goog.array.ArrayLike
        The object to convert to an array.
        Returns
        The object converted into an array. If object has a + second.
        code »<T> goog.array.toArray ( object )!Array.<T>

        Converts an object to an array.

        Parameters
        object: (Array.<T>|goog.array.ArrayLike)
        The object to convert to an + array.
        Returns
        The object converted into an array. If object has a length property, every property indexed with a non-negative number less than length will be included in the result. If object does not - have a length property, an empty array will be returned.
        code »<T, S> goog.array.toObject ( arr, keyFunc, opt_obj )!Object.<T>

        Creates a new object built from the provided array and the key-generation + have a length property, an empty array will be returned.

        code »<T, S> goog.array.toObject ( arr, keyFunc, opt_obj )!Object.<T>

        Creates a new object built from the provided array and the key-generation function.

        Parameters
        arr: (Array.<T>|goog.array.ArrayLike)
        Array or array like object over which to iterate whose elements will be the values in the new object.
        keyFunc: ?function(this: S, T, number, ?): string
        The function to call for every element. This function takes 3 arguments (the element, the @@ -309,9 +333,9 @@ key for the element in the new object. If the function returns the same key for more than one element, the value for that key is implementation-defined.
        opt_obj: S=
        The object to be used as the value of 'this' - within keyFunc.
        Returns
        The new object.
        code »goog.array.zip ( var_args )!Array

        Creates a new array for which the element at position i is an array of the + within keyFunc.Returns

        The new object.
        code »goog.array.zip ( var_args )!Array

        Creates a new array for which the element at position i is an array of the ith element of the provided arrays. The returned array will only be as long as the shortest array provided; additional values are ignored. For example, the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]]. - This is similar to the zip() function in Python. See http://docs.python.org/library/functions.html#zip

        Parameters
        var_args: ...!goog.array.ArrayLike
        Arrays to be combined.
        Returns
        A new array of arrays created from provided arrays.

        Global Properties

        Reference to the original Array.prototype.

        \ No newline at end of file + This is similar to the zip() function in Python. See http://docs.python.org/library/functions.html#zip
        Parameters
        var_args: ...!goog.array.ArrayLike
        Arrays to be combined.
        Returns
        A new array of arrays created from provided arrays.

        Global Properties

        Reference to the original Array.prototype.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_asserts.html b/docs/namespace_goog_asserts.html index 5841b61..2c97fd2 100644 --- a/docs/namespace_goog_asserts.html +++ b/docs/namespace_goog_asserts.html @@ -1,12 +1,14 @@ -goog.asserts

        Namespace goog.asserts

        code »

        Classes

        goog.asserts.AssertionError
        Error object for failed assertions.
        Show:

        Global Functions

        code »goog.asserts.assert ( condition, opt_message, var_args )*

        Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is - true.

        Parameters
        condition: *
        The condition to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value of the condition.
        Throws
        goog.asserts.AssertionError
        When the condition evaluates to false.
        code »goog.asserts.assertArray ( value, opt_message, var_args )!Array

        Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a non-null array.
        Throws
        goog.asserts.AssertionError
        When the value is not an array.
        code »goog.asserts.assertBoolean ( value, opt_message, var_args )boolean

        Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a boolean when asserts are - enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a boolean.
        code »goog.asserts.assertFunction ( value, opt_message, var_args )!Function

        Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a function when asserts - enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a function.
        code »<T> goog.asserts.assertInstanceof ( value, type, opt_message, var_args )!T

        Checks if the value is an instance of the user-defined type if +goog.asserts

        Namespace goog.asserts

        code »

        Classes

        goog.asserts.AssertionError
        Error object for failed assertions.
        Show:

        Global Functions

        The default error handler.

        Parameters
        e: !goog.asserts.AssertionError
        The exception to be handled.
        code »<T> goog.asserts.assert ( condition, opt_message, var_args )T

        Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is + true.

        Parameters
        condition: T
        The condition to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value of the condition.
        Throws
        goog.asserts.AssertionError
        When the condition evaluates to false.
        code »goog.asserts.assertArray ( value, opt_message, var_args )!Array

        Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a non-null array.
        Throws
        goog.asserts.AssertionError
        When the value is not an array.
        code »goog.asserts.assertBoolean ( value, opt_message, var_args )boolean

        Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a boolean when asserts are + enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a boolean.
        code »goog.asserts.assertElement ( value, opt_message, var_args )!Element

        Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, likely to be a DOM Element when asserts are + enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a boolean.
        code »goog.asserts.assertFunction ( value, opt_message, var_args )!Function

        Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a function when asserts + enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a function.
        code »<T> goog.asserts.assertInstanceof ( value, type, opt_message, var_args )!T

        Checks if the value is an instance of the user-defined type if goog.asserts.ENABLE_ASSERTS is true. The compiler may tighten the type returned by this function.

        Parameters
        value: *
        The value to check.
        type: function(new: T, ...)
        A user-defined constructor.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Throws
        goog.asserts.AssertionError
        When the value is not an instance of - type.
        code »goog.asserts.assertNumber ( value, opt_message, var_args )number

        Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a number when asserts enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a number.
        code »goog.asserts.assertObject ( value, opt_message, var_args )!Object

        Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a non-null object.
        Throws
        goog.asserts.AssertionError
        When the value is not an object.
        code »goog.asserts.assertString ( value, opt_message, var_args )string

        Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a string when asserts enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a string.
        code »goog.asserts.doAssertFailure_ ( defaultMessage, defaultArgs, givenMessage, givenArgs )

        Throws an exception with the given message and "Assertion failed" prefixed - onto it.

        Parameters
        defaultMessage: string
        The message to use if givenMessage is empty.
        defaultArgs: Array
        The substitution arguments for defaultMessage.
        givenMessage: (string|undefined)
        Message supplied by the caller.
        givenArgs: Array
        The substitution arguments for givenMessage.
        Throws
        goog.asserts.AssertionError
        When the value is not a number.
        code »goog.asserts.fail ( opt_message, var_args )

        Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case + type.

        code »goog.asserts.assertNumber ( value, opt_message, var_args )number

        Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a number when asserts enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a number.
        code »goog.asserts.assertObject ( value, opt_message, var_args )!Object

        Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a non-null object.
        Throws
        goog.asserts.AssertionError
        When the value is not an object.

        Checks that no enumerable keys are present in Object.prototype. Such keys + would break most code that use for (var ... in ...) loops.

        code »goog.asserts.assertString ( value, opt_message, var_args )string

        Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.

        Parameters
        value: *
        The value to check.
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Returns
        The value, guaranteed to be a string when asserts enabled.
        Throws
        goog.asserts.AssertionError
        When the value is not a string.
        code »goog.asserts.doAssertFailure_ ( defaultMessage, defaultArgs, givenMessage, givenArgs )

        Throws an exception with the given message and "Assertion failed" prefixed + onto it.

        Parameters
        defaultMessage: string
        The message to use if givenMessage is empty.
        defaultArgs: Array
        The substitution arguments for defaultMessage.
        givenMessage: (string|undefined)
        Message supplied by the caller.
        givenArgs: Array
        The substitution arguments for givenMessage.
        Throws
        goog.asserts.AssertionError
        When the value is not a number.

        The handler responsible for throwing or logging assertion errors.

        code »goog.asserts.fail ( opt_message, var_args )

        Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case when we want to add a check in the unreachable area like switch-case statement: @@ -17,4 +19,6 @@ default: goog.assert.fail('Unrecognized type: ' + type); // We have only 2 types - "default:" section is unreachable code. } -

        Parameters
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Throws
        goog.asserts.AssertionError
        Failure.

        Compiler Constants

        \ No newline at end of file +
        Parameters
        opt_message: string=
        Error message in case of failure.
        var_args: ...*
        The items to substitute into the failure message.
        Throws
        goog.asserts.AssertionError
        Failure.

        Sets a custom error handler that can be used to customize the behavior of + assertion failures, for example by turning all assertion failures into log + messages.

        Parameters
        errorHandler

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_async.html b/docs/namespace_goog_async.html new file mode 100644 index 0000000..75fad2a --- /dev/null +++ b/docs/namespace_goog_async.html @@ -0,0 +1,14 @@ +goog.async

        Namespace goog.async

        code »
        Show:

        Global Functions

        code »<SCOPE> goog.async.nextTick ( callback, opt_context )

        Fires the provided callbacks as soon as possible after the current JS + execution context. setTimeout(…, 0) takes at least 4ms when called from + within another setTimeout(…, 0) for legacy reasons. + + This will not schedule the callback as a microtask (i.e. a task that can + preempt user input or networking callbacks). It is meant to emulate what + setTimeout(_, 0) would do if it were not throttled. If you desire microtask + behavior, use goog.Promise instead.

        Parameters
        callback: function(this: SCOPE)
        Callback function to fire as soon as + possible.
        opt_context: SCOPE=
        Object in whose scope to call the listener.
        code »<THIS> goog.async.run ( callback, opt_context )

        Fires the provided callback just before the current callstack unwinds, or as + soon as possible after the current JS execution context.

        Parameters
        callback
        opt_context: THIS=
        Object to use as the "this value" when calling + the provided function.

        Throw an item without interrupting the current execution context. For + example, if processing a group of items in a loop, sometimes it is useful + to report an error while still allowing the rest of the batch to be + processed.

        Parameters
        exception
        \ No newline at end of file diff --git a/docs/namespace_goog_async_nextTick.html b/docs/namespace_goog_async_nextTick.html new file mode 100644 index 0000000..26a5026 --- /dev/null +++ b/docs/namespace_goog_async_nextTick.html @@ -0,0 +1,11 @@ +goog.async.nextTick

        Namespace goog.async.nextTick.<SCOPE>

        code »

        Fires the provided callbacks as soon as possible after the current JS + execution context. setTimeout(…, 0) takes at least 4ms when called from + within another setTimeout(…, 0) for legacy reasons. + + This will not schedule the callback as a microtask (i.e. a task that can + preempt user input or networking callbacks). It is meant to emulate what + setTimeout(_, 0) would do if it were not throttled. If you desire microtask + behavior, use goog.Promise instead.

        Main

        <SCOPE> nextTick ( callback, opt_context )
        Parameters
        callback: function(this: SCOPE)
        Callback function to fire as soon as + possible.
        opt_context: SCOPE=
        Object in whose scope to call the listener.
        Show:

        Global Functions

        Determines the best possible implementation to run a function as soon as + the JS event loop is idle.

        Returns
        The "setImmediate" implementation.

        Cache for the setImmediate implementation.

        code »goog.async.nextTick.wrapCallback_ ( callback )function()

        Helper function that is overrided to protect callbacks with entry point + monitor if the application monitors entry points.

        Parameters
        callback: function()
        Callback function to fire as soon as possible.
        Returns
        The wrapped callback.
        \ No newline at end of file diff --git a/docs/namespace_goog_async_run.html b/docs/namespace_goog_async_run.html new file mode 100644 index 0000000..80a1ddc --- /dev/null +++ b/docs/namespace_goog_async_run.html @@ -0,0 +1,9 @@ +goog.async.run

        Namespace goog.async.run.<THIS>

        code »

        Fires the provided callback just before the current callstack unwinds, or as + soon as possible after the current JS execution context.

        Main

        <THIS> run ( callback, opt_context )
        Parameters
        callback
        opt_context: THIS=
        Object to use as the "this value" when calling + the provided function.

        Classes

        Show:

        Global Functions

        Forces goog.async.run to use nextTick instead of Promise. + + This should only be done in unit tests. It's useful because MockClock + replaces nextTick, but not the browser Promise implementation, so it allows + Promise-based code to be tested with MockClock.

        Initializes the function to use to process the work queue.

        Run any pending goog.async.run work items. This function is not intended + for general use, but for use by entry point handlers to run items ahead of + goog.async.nextTick.

        Reset the event queue.

        The function used to schedule work asynchronousely.

        Global Properties

        \ No newline at end of file diff --git a/docs/namespace_goog_debug.html b/docs/namespace_goog_debug.html index 351be28..45efc3d 100644 --- a/docs/namespace_goog_debug.html +++ b/docs/namespace_goog_debug.html @@ -1 +1,27 @@ -goog.debug

        Namespace goog.debug

        code »

        Classes

        goog.debug.Error
        Base class for custom error objects.
        Show:
        \ No newline at end of file +goog.debug

        Namespace goog.debug

        code »

        Interfaces

        Classes

        goog.debug.Error
        Base class for custom error objects.
        goog.debug.LogBuffer
        Creates the log buffer.
        goog.debug.LogRecord
        LogRecord objects are used to pass logging requests between + the logging framework and individual log Handlers.
        goog.debug.Logger
        The Logger is an object used for logging debug messages.
        Show:

        Type Definitions

        A message value that can be handled by a Logger. + + Functions are treated like callbacks, but are only called when the event's + log level is enabled. This is useful for logging messages that are expensive + to construct.

        Global Functions

        code »goog.debug.catchErrors ( logFunc, opt_cancel, opt_target )

        Catches onerror events fired by windows and similar objects.

        Parameters
        logFunc: function(Object)
        The function to call with the error + information.
        opt_cancel: boolean=
        Whether to stop the error from reaching the + browser.
        opt_target: Object=
        Object that fires onerror events.
        code »goog.debug.deepExpose ( obj, opt_showFn )string

        Creates a string representing a given primitive or object, and for an + object, all its properties and nested objects. WARNING: If an object is + given, it and all its nested objects will be modified. To detect reference + cycles, this method identifies objects using goog.getUid() which mutates the + object.

        Parameters
        obj: *
        Object to expose.
        opt_showFn: boolean=
        Also show properties that are functions (by + default, functions are omitted).
        Returns
        A string representation of obj.
        code »goog.debug.enhanceError ( err, opt_message )!Error

        Converts an object to an Error if it's a String, + adds a stacktrace if there isn't one, + and optionally adds an extra message.

        Parameters
        err: (Error|string)
        the original thrown object or string.
        opt_message: string=
        optional additional message to add to the + error.
        Returns
        If err is a string, it is used to create a new Error, + which is enhanced and returned. Otherwise err itself is enhanced + and returned.
        code »goog.debug.expose ( obj, opt_showFn )string

        Creates a string representing an object and all its properties.

        Parameters
        obj: (Object|null|undefined)
        Object to expose.
        opt_showFn: boolean=
        Show the functions as well as the properties, + default is false.
        Returns
        The string representation of obj.

        Recursively outputs a nested array as a string.

        Parameters
        arr: Array
        The array.
        Returns
        String representing nested array.

        Exposes an exception that has been caught by a try...catch and outputs the + error with a stack trace.

        Parameters
        err: Object
        Error object or string.
        opt_fn: Function=
        Optional function to start stack trace from.
        Returns
        Details of exception.

        Resolves functions to their names. Resolved function names will be cached.

        Gets a function name

        Parameters
        fn: Function
        Function to get name of.
        Returns
        Function's name.
        Parameters
        fn: Function
        The function to start getting the trace from.

        Gets the current stack trace, either starting from the caller or starting + from a specified function that's currently on the call stack.

        Parameters
        opt_fn: Function=
        Optional function to start getting the trace from. + If not provided, defaults to the function that called this.
        Returns
        Stack trace.

        Private helper for getStacktrace().

        Parameters
        fn: Function
        Function to start getting the trace from.
        visited: Array
        List of functions visited so far.
        Returns
        Stack trace starting from function fn.

        Gets the current stack trace. Simple and iterative - doesn't worry about + catching circular references or getting the args.

        Parameters
        opt_depth: number=
        Optional maximum depth to trace back to.
        Returns
        A string with the function names of all functions in the + stack, separated by \n.

        Makes whitespace visible by replacing it with printable characters. + This is useful in finding diffrences between the expected and the actual + output strings of a testcase.

        Parameters
        string: string
        whose whitespace needs to be made visible.
        Returns
        string whose whitespace is made visible.

        Normalizes the error/exception object between browsers.

        Parameters
        err: Object
        Raw error object.
        Returns
        Normalized error object.

        Set a custom function name resolver.

        Parameters
        resolver: function(Function): string
        Resolves functions to their + names.

        Global Properties

        Max length of stack to try and output

        Hash map for storing function names that have already been looked up.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_debug_LogManager.html b/docs/namespace_goog_debug_LogManager.html new file mode 100644 index 0000000..4d2a933 --- /dev/null +++ b/docs/namespace_goog_debug_LogManager.html @@ -0,0 +1,10 @@ +goog.debug.LogManager

        Namespace goog.debug.LogManager

        code »

        There is a single global LogManager object that is used to maintain a set of + shared state about Loggers and log services. This is loosely based on the + java class java.util.logging.LogManager.

        Show:

        Global Functions

        Creates a function that can be passed to goog.debug.catchErrors. The function + will log all reported errors using the given logger.

        Parameters
        opt_logger: goog.debug.Logger=
        The logger to log the errors to. + Defaults to the root logger.
        Returns
        The created function.

        Creates the named logger. Will also create the parents of the named logger + if they don't yet exist.

        Parameters
        name: string
        The name of the logger.
        Returns
        The named logger.

        Finds a named logger.

        Parameters
        name: string
        A name for the logger. This should be a dot-separated + name and should normally be based on the package name or class name of the + subsystem, such as goog.net.BrowserChannel.
        Returns
        The named logger.

        Returns all the loggers.

        Returns
        Map of logger names to logger + objects.

        Returns the root of the logger tree namespace, the logger with the empty + string as its name.

        Returns
        The root logger.

        Initializes the LogManager if not already initialized.

        Global Properties

        Map of logger names to logger objects.

        The root logger which is the root of the logger tree.

        \ No newline at end of file diff --git a/docs/namespace_goog_debug_entryPointRegistry.html b/docs/namespace_goog_debug_entryPointRegistry.html new file mode 100644 index 0000000..c76becc --- /dev/null +++ b/docs/namespace_goog_debug_entryPointRegistry.html @@ -0,0 +1,17 @@ +goog.debug.entryPointRegistry

        Namespace goog.debug.entryPointRegistry

        code »
        Show:

        Global Functions

        Configures a monitor to wrap all entry points. + + Entry points that have already been registered are immediately wrapped by + the monitor. When an entry point is registered in the future, it will also + be wrapped by the monitor when it is registered.

        Parameters
        monitor: !goog.debug.EntryPointMonitor
        An entry point monitor.

        Register an entry point with this module. + + The entry point will be instrumented when a monitor is passed to + goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the + entry point is instrumented immediately.

        Parameters
        callback: function(!Function)
        A callback function which is called + with a transforming function to instrument the entry point. The callback + is responsible for wrapping the relevant entry point with the + transforming function.

        Try to unmonitor all the entry points that have already been registered. If + an entry point is registered in the future, it will not be wrapped by the + monitor when it is registered. Note that this may fail if the entry points + have additional wrapping.

        Parameters
        monitor: !goog.debug.EntryPointMonitor
        The last monitor to wrap + the entry points.
        Throws
        Error
        If the monitor is not the most recently configured monitor.

        Global Properties

        Whether goog.debug.entryPointRegistry.monitorAll has ever been called. + Checking this allows the compiler to optimize out the registrations.

        Monitors that should wrap all the entry points.

        An array of entry point callbacks.

        \ No newline at end of file diff --git a/docs/namespace_goog_defineClass.html b/docs/namespace_goog_defineClass.html new file mode 100644 index 0000000..e0d1d13 --- /dev/null +++ b/docs/namespace_goog_defineClass.html @@ -0,0 +1,16 @@ +goog.defineClass

        Namespace goog.defineClass

        code »

        Creates a restricted form of a Closure "class": + - from the compiler's perspective, the instance returned from the + constructor is sealed (no new properties may be added). This enables + better checks. + - the compiler will rewrite this definition to a form that is optimal + for type checking and optimization (initially this will be a more + traditional form).

        Main

        defineClass ( superClass, def )!Function
        Parameters
        superClass: Function
        The superclass, Object or null.
        def: goog.defineClass.ClassDescriptor
        An object literal describing the + the class. It may have the following properties: + "constructor": the constructor function + "statics": an object literal containing methods to add to the constructor + as "static" methods or a function that will receive the constructor + function as its only parameter to which static properties can + be added. + all other properties are added to the prototype.
        Returns
        The class constructor.
        Show:

        Type Definitions

        code »goog.defineClass.ClassDescriptor : (!Object|{constructor: !Function}|{constructor: !Function, statics: (Object|function(Function): void)})
        No description.

        Global Functions

        Parameters
        target: !Object
        The object to add properties to.
        source: !Object
        The object to copy properites from.

        If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is + defined, this function will wrap the constructor in a function that seals the + results of the provided constructor function.

        Parameters
        ctr: !Function
        The constructor whose results maybe be sealed.
        superClass: Function
        The superclass constructor.
        Returns
        The replacement constructor.

        Global Properties

        The names of the fields that are defined on Object.prototype.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_disposable.html b/docs/namespace_goog_disposable.html new file mode 100644 index 0000000..bedb313 --- /dev/null +++ b/docs/namespace_goog_disposable.html @@ -0,0 +1 @@ +goog.disposable

        Namespace goog.disposable

        code »

        Interfaces

        goog.disposable.IDisposable
        Interface for a disposable object.
        Show:
        \ No newline at end of file diff --git a/docs/namespace_goog_dom.html b/docs/namespace_goog_dom.html new file mode 100644 index 0000000..8a76354 --- /dev/null +++ b/docs/namespace_goog_dom.html @@ -0,0 +1,232 @@ +goog.dom

        Namespace goog.dom

        code »

        Classes

        goog.dom.DomHelper
        Create an instance of a DOM helper with a new document object.

        Enumerations

        goog.dom.BrowserFeature
        Enum of browser capabilities.
        goog.dom.NodeType
        Constants for the nodeType attribute in the Node interface.
        goog.dom.TagName
        Enum of all html tag names specified by the W3C HTML4.01 and HTML5 + specifications.
        Show:

        Type Definitions

        Typedef for use with goog.dom.createDom and goog.dom.append.

        Global Functions

        code »goog.dom.$ ( element )Element
        Deprecated: Use goog.dom.getElement instead.

        Alias for getElement.

        Parameters
        element: (string|Element)
        Element ID or a DOM node.
        Returns
        The element with the given ID, or the node passed in.
        code »goog.dom.$$ ( opt_tag, opt_class, opt_el ){length: number}
        Deprecated: Use goog.dom.getElementsByTagNameAndClass instead.

        Alias for getElementsByTagNameAndClass.

        Parameters
        opt_tag: ?string=
        Element tag name.
        opt_class: ?string=
        Optional class name.
        opt_el: Element=
        Optional element to look in.
        Returns
        Array-like list of elements (only a length + property and numerical indices are guaranteed to exist).
        code »goog.dom.$dom ( tagName, opt_attributes, var_args )!Element
        Deprecated: Use goog.dom.createDom instead.

        Alias for createDom.

        Parameters
        tagName: string
        Tag to create.
        opt_attributes: (string|Object)=
        If object, then a map of name-value + pairs for attributes. If a string, then this is the className of the new + element.
        var_args: ...(Object|string|Array|NodeList)
        Further DOM nodes or + strings for text nodes. If one of the var_args is an array, its + children will be added as childNodes instead.
        Returns
        Reference to a DOM node.
        code »goog.dom.append ( parent, var_args )

        Appends a node with text or other nodes.

        Parameters
        parent: !Node
        The node to append nodes to.
        var_args: ...goog.dom.Appendable
        The things to append to the node. + If this is a Node it is appended as is. + If this is a string then a text node is appended. + If this is an array like object then fields 0 to length - 1 are appended.
        code »goog.dom.appendChild ( parent, child )

        Appends a child to a node.

        Parameters
        parent: Node
        Parent.
        child: Node
        Child.
        code »goog.dom.append_ ( doc, parent, args, startIndex )

        Appends a node with text or other nodes.

        Parameters
        doc: !Document
        The document to create new nodes in.
        parent: !Node
        The node to append nodes to.
        args: !Arguments
        The values to add. See goog.dom.append.
        startIndex: number
        The index of the array to start from.

        Determines if the given node can contain children, intended to be used for + HTML generation. + + IE natively supports node.canHaveChildren but has inconsistent behavior. + Prior to IE8 the base tag allows children and in IE9 all nodes return true + for canHaveChildren. + + In practice all non-IE browsers allow you to add children to any node, but + the behavior is inconsistent: + +

        +   var a = document.createElement('br');
        +   a.appendChild(document.createTextNode('foo'));
        +   a.appendChild(document.createTextNode('bar'));
        +   console.log(a.childNodes.length);  // 2
        +   console.log(a.innerHTML);  // Chrome: "", IE9: "foobar", FF3.5: "foobar"
        + 
        + + For more information, see: + http://dev.w3.org/html5/markup/syntax.html#syntax-elements + + TODO(user): Rename shouldAllowChildren() ?
        Parameters
        node: Node
        The node to check.
        Returns
        Whether the node can contain children.

        Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and + fast W3C Selectors API.

        Parameters
        parent: !(Element|Document)
        The parent document object.
        Returns
        whether or not we can use parent.querySelector* APIs.

        Compares the document order of two nodes, returning 0 if they are the same + node, a negative number if node1 is before node2, and a positive number if + node2 is before node1. Note that we compare the order the tags appear in the + document so in the tree text the B node is considered to be + before the I node.

        Parameters
        node1: Node
        The first node to compare.
        node2: Node
        The second node to compare.
        Returns
        0 if the nodes are the same node, a negative number if node1 + is before node2, and a positive number if node2 is before node1.

        Utility function to compare the position of two nodes, when + textNode's parent is an ancestor of node. If this entry + condition is not met, this function will attempt to reference a null object.

        Parameters
        textNode: Node
        The textNode to compare.
        node: Node
        The node to compare.
        Returns
        -1 if node is before textNode, +1 otherwise.

        Utility function to compare the position of two nodes known to be non-equal + siblings.

        Parameters
        node1: Node
        The first node to compare.
        node2: Node
        The second node to compare.
        Returns
        -1 if node1 is before node2, +1 otherwise.
        code »goog.dom.contains ( parent, descendant )boolean

        Whether a node contains another node.

        Parameters
        parent: Node
        The node that should contain the other node.
        descendant: Node
        The node to test presence of.
        Returns
        Whether the parent node contains the descendent node.
        code »goog.dom.createDom ( tagName, opt_attributes, var_args )!Element

        Returns a dom node with a set of attributes. This function accepts varargs + for subsequent nodes to be added. Subsequent nodes will be added to the + first node as childNodes. + + So: + createDom('div', null, createDom('p'), createDom('p')); + would return a div with two child paragraphs

        Parameters
        tagName: string
        Tag to create.
        opt_attributes: (Object|Array.<string>|string)=
        If object, then a map + of name-value pairs for attributes. If a string, then this is the + className of the new element. If an array, the elements will be joined + together as the className of the new element.
        var_args: ...(Object|string|Array|NodeList)
        Further DOM nodes or + strings for text nodes. If one of the var_args is an array or NodeList,i + its elements will be added as childNodes instead.
        Returns
        Reference to a DOM node.

        Helper for createDom.

        Parameters
        doc: !Document
        The document to create the DOM in.
        args: !Arguments
        Argument object passed from the callers. See + goog.dom.createDom for details.
        Returns
        Reference to a DOM node.

        Creates a new element.

        Parameters
        name: string
        Tag name.
        Returns
        The new element.
        code »goog.dom.createTable ( rows, columns, opt_fillWithNbsp )!Element

        Create a table.

        Parameters
        rows: number
        The number of rows in the table. Must be >= 1.
        columns: number
        The number of columns in the table. Must be >= 1.
        opt_fillWithNbsp: boolean=
        If true, fills table entries with nsbps.
        Returns
        The created table.
        code »goog.dom.createTable_ ( doc, rows, columns, fillWithNbsp )!Element

        Create a table.

        Parameters
        doc: !Document
        Document object to use to create the table.
        rows: number
        The number of rows in the table. Must be >= 1.
        columns: number
        The number of columns in the table. Must be >= 1.
        fillWithNbsp: boolean
        If true, fills table entries with nsbps.
        Returns
        The created table.

        Creates a new text node.

        Parameters
        content: (number|string)
        Content.
        Returns
        The new text node.

        Find the deepest common ancestor of the given nodes.

        Parameters
        var_args: ...Node
        The nodes to find a common ancestor of.
        Returns
        The common ancestor of the nodes, or null if there is none. + null will only be returned if two or more of the nodes are from different + documents.

        Finds the first descendant node that matches the filter function, using + a depth first search. This function offers the most general purpose way + of finding a matching element. You may also wish to consider + goog.dom.query which can express many matching criteria using + CSS selector expressions. These expressions often result in a more + compact representation of the desired result.

        Parameters
        root: Node
        The root of the tree to search.
        p: function(Node): boolean
        The filter function.
        Returns
        The found node or undefined if none is found.

        Finds all the descendant nodes that match the filter function, using a + a depth first search. This function offers the most general-purpose way + of finding a set of matching elements. You may also wish to consider + goog.dom.query which can express many matching criteria using + CSS selector expressions. These expressions often result in a more + compact representation of the desired result.

        Parameters
        root: Node
        The root of the tree to search.
        p: function(Node): boolean
        The filter function.
        Returns
        The found nodes or an empty array if none are found.
        code »goog.dom.findNodes_ ( root, p, rv, findOne )boolean

        Finds the first or all the descendant nodes that match the filter function, + using a depth first search.

        Parameters
        root: Node
        The root of the tree to search.
        p: function(Node): boolean
        The filter function.
        rv: !Array
        The found nodes are added to this array.
        findOne: boolean
        If true we exit after the first found node.
        Returns
        Whether the search is complete or not. True in case findOne + is true and the node is found. False otherwise.

        Flattens an element. That is, removes it and replace it with its children. + Does nothing if the element is not in the document.

        Parameters
        element: Element
        The element to flatten.
        Returns
        The original element, detached from the document + tree, sans children; or undefined, if the element was not in the document + to begin with.

        Determines the active element in the given document.

        Parameters
        doc: Document
        The document to look in.
        Returns
        The active element.
        code »goog.dom.getAncestor ( element, matcher, opt_includeNode, opt_maxSearchSteps )Node

        Walks up the DOM hierarchy returning the first ancestor that passes the + matcher function.

        Parameters
        element: Node
        The DOM node to start with.
        matcher: function(Node): boolean
        A function that returns true if the + passed node matches the desired criteria.
        opt_includeNode: boolean=
        If true, the node itself is included in + the search (the first call to the matcher will pass startElement as + the node to test).
        opt_maxSearchSteps: number=
        Maximum number of levels to search up the + dom.
        Returns
        DOM node that matched the matcher, or null if there was + no match.
        code »goog.dom.getAncestorByClass ( element, className )Element

        Walks up the DOM hierarchy returning the first ancestor that has the passed + class name. If the passed element matches the specified criteria, the + element itself is returned.

        Parameters
        element: Node
        The DOM node to start with.
        className: string
        The class name to match.
        Returns
        The first ancestor that matches the passed criteria, or + null if none match.
        code »goog.dom.getAncestorByTagNameAndClass ( element, opt_tag, opt_class )Element

        Walks up the DOM hierarchy returning the first ancestor that has the passed + tag name and/or class name. If the passed element matches the specified + criteria, the element itself is returned.

        Parameters
        element: Node
        The DOM node to start with.
        opt_tag: ?(goog.dom.TagName|string)=
        The tag name to match (or + null/undefined to match only based on class name).
        opt_class: ?string=
        The class name to match (or null/undefined to + match only based on tag name).
        Returns
        The first ancestor that matches the passed criteria, or + null if no match is found.

        Returns an array containing just the element children of the given element.

        Parameters
        element: Element
        The element whose element children we want.
        Returns
        An array or array-like list of just the element + children of the given element.

        Gets the document object being used by the dom library.

        Returns
        Document object.

        Calculates the height of the document.

        Returns
        The height of the current document.

        Calculates the height of the document of the given window. + + Function code copied from the opensocial gadget api: + gadgets.window.adjustHeight(opt_height)

        Parameters
        win: Window
        The window whose document height to retrieve.
        Returns
        The height of the document of the given window.

        Gets the document scroll distance as a coordinate object.

        Returns
        Object with values 'x' and 'y'.

        Gets the document scroll element.

        Returns
        Scrolling element.

        Helper for getDocumentScrollElement.

        Parameters
        doc: !Document
        The document to get the scroll element for.
        Returns
        Scrolling element.

        Helper for getDocumentScroll.

        Parameters
        doc: !Document
        The document to get the scroll for.
        Returns
        Object with values 'x' and 'y'.

        Gets the DomHelper object for the document where the element resides.

        Parameters
        opt_element: (Node|Window)=
        If present, gets the DomHelper for this + element.
        Returns
        The DomHelper.

        Gets an element from the current document by element id. + + If an Element is passed in, it is returned.

        Parameters
        element: (string|Element)
        Element ID or a DOM node.
        Returns
        The element with the given ID, or the node passed in.
        code »goog.dom.getElementByClass ( className, opt_el )Element

        Returns the first element with the provided className.

        Parameters
        className: string
        the name of the class to look for.
        opt_el: (Element|Document)=
        Optional element to look in.
        Returns
        The first item with the class name provided.

        Gets an element by id from the given document (if present). + If an element is given, it is returned.

        Parameters
        doc
        element: (string|Element)
        Element ID or a DOM node.
        Returns
        The resulting element.
        code »goog.dom.getElementsByClass ( className, opt_el ){length: number}

        Returns a static, array-like list of the elements with the provided + className.

        Parameters
        className: string
        the name of the class to look for.
        opt_el: (Document|Element)=
        Optional element to look in.
        Returns
        The items found with the class name provided.
        code »goog.dom.getElementsByTagNameAndClass ( opt_tag, opt_class, opt_el ){length: number}

        Looks up elements by both tag and class name, using browser native functions + (querySelectorAll, getElementsByTagName or + getElementsByClassName) where possible. This function + is a useful, if limited, way of collecting a list of DOM elements + with certain characteristics. goog.dom.query offers a + more powerful and general solution which allows matching on CSS3 + selector expressions, but at increased cost in code size. If all you + need is particular tags belonging to a single class, this function + is fast and sleek. + + Note that tag names are case sensitive in the SVG namespace, and this + function converts opt_tag to uppercase for comparisons. For queries in the + SVG namespace you should use querySelector or querySelectorAll instead. + https://bugzilla.mozilla.org/show_bug.cgi?id=963870 + https://bugs.webkit.org/show_bug.cgi?id=83438

        Parameters
        opt_tag: ?string=
        Element tag name.
        opt_class: ?string=
        Optional class name.
        opt_el: (Document|Element)=
        Optional element to look in.
        Returns
        Array-like list of elements (only a length + property and numerical indices are guaranteed to exist).
        code »goog.dom.getElementsByTagNameAndClass_ ( doc, opt_tag, opt_class, opt_el ){length: number}

        Helper for getElementsByTagNameAndClass.

        Parameters
        doc: !Document
        The document to get the elements in.
        opt_tag: ?string=
        Element tag name.
        opt_class: ?string=
        Optional class name.
        opt_el: (Document|Element)=
        Optional element to look in.
        Returns
        Array-like list of elements (only a length + property and numerical indices are guaranteed to exist).

        Returns the first child node that is an element.

        Parameters
        node: Node
        The node to get the first child element of.
        Returns
        The first child node of node that is an element.

        Cross-browser function for getting the document element of a frame or iframe.

        Parameters
        frame: Element
        Frame element.
        Returns
        The frame content document.

        Cross-browser function for getting the window of a frame or iframe.

        Parameters
        frame: Element
        Frame element.
        Returns
        The window associated with the given frame.

        Returns the last child node that is an element.

        Parameters
        node: Node
        The node to get the last child element of.
        Returns
        The last child node of node that is an element.

        Returns the first node that is an element in the specified direction, + starting with node.

        Parameters
        node: Node
        The node to get the next element from.
        forward: boolean
        Whether to look forwards or backwards.
        Returns
        The first element.

        Returns the first next sibling that is an element.

        Parameters
        node: Node
        The node to get the next sibling element of.
        Returns
        The next sibling of node that is an element.

        Returns the next node in source order from the given node.

        Parameters
        node: Node
        The node.
        Returns
        The next node in the DOM tree, or null if this was the last + node.
        code »goog.dom.getNodeAtOffset ( parent, offset, opt_result )Node

        Returns the node at a given offset in a parent node. If an object is + provided for the optional third parameter, the node and the remainder of the + offset will stored as properties of this object.

        Parameters
        parent: Node
        The parent node.
        offset: number
        The offset into the parent node.
        opt_result: Object=
        Object to be used to store the return value. The + return value will be stored in the form {node: Node, remainder: number} + if this object is provided.
        Returns
        The node at the given offset.

        Returns the text length of the text contained in a node, without markup. This + is equivalent to the selection length if the node was selected, or the number + of cursor movements to traverse the node. Images & BRs take one space. New + lines are ignored.

        Parameters
        node: Node
        The node whose text content length is being calculated.
        Returns
        The length of node's text content.
        code »goog.dom.getNodeTextOffset ( node, opt_offsetParent )number

        Returns the text offset of a node relative to one of its ancestors. The text + length is the same as the length calculated by goog.dom.getNodeTextLength.

        Parameters
        node: Node
        The node whose offset is being calculated.
        opt_offsetParent: Node=
        The node relative to which the offset will + be calculated. Defaults to the node's owner document's body.
        Returns
        The text offset.

        Gets the outerHTML of a node, which islike innerHTML, except that it + actually contains the HTML of the node itself.

        Parameters
        element: Element
        The element to get the HTML of.
        Returns
        The outerHTML of the given element.

        Returns the owner document for a node.

        Parameters
        node: (Node|Window)
        The node to get the document for.
        Returns
        The document owning the node.
        Deprecated: Use goog.dom.getDocumentScroll instead.

        Gets the page scroll distance as a coordinate object.

        Parameters
        opt_window: Window=
        Optional window element to test.
        Returns
        Object with values 'x' and 'y'.

        Returns an element's parent, if it's an Element.

        Parameters
        element: Element
        The DOM element.
        Returns
        The parent, or null if not an Element.

        Gives the current devicePixelRatio. + + By default, this is the value of window.devicePixelRatio (which should be + preferred if present). + + If window.devicePixelRatio is not present, the ratio is calculated with + window.matchMedia, if present. Otherwise, gives 1.0. + + Some browsers (including Chrome) consider the browser zoom level in the pixel + ratio, so the value may change across multiple calls.

        Returns
        The number of actual pixels per virtual pixel.

        Returns the first previous sibling that is an element.

        Parameters
        node: Node
        The node to get the previous sibling element of.
        Returns
        The first previous sibling of node that is + an element.

        Returns the previous node in source order from the given node.

        Parameters
        node: Node
        The node.
        Returns
        The previous node in the DOM tree, or null if this was the + first node.

        Returns the text content of the current node, without markup. + + Unlike getTextContent this method does not collapse whitespaces + or normalize lines breaks.

        Parameters
        node: Node
        The node from which we are getting content.
        Returns
        The raw text content.

        Gets an element by id, asserting that the element is found. + + This is used when an element is expected to exist, and should fail with + an assertion error if it does not (if assertions are enabled).

        Parameters
        id: string
        Element ID.
        Returns
        The element with the given ID, if it exists.

        Ensures an element with the given className exists, and then returns the + first element with the provided className.

        Parameters
        className: string
        the name of the class to look for.
        opt_root: (!Element|!Document)=
        Optional element or document to look + in.
        Returns
        The first item with the class name provided.
        Throws
        goog.asserts.AssertionError
        Thrown if no element is found.

        Helper function for getRequiredElementHelper functions, both static and + on DomHelper. Asserts the element with the given id exists.

        Parameters
        doc
        id
        Returns
        The element with the given ID, if it exists.

        Returns the text content of the current node, without markup and invisible + symbols. New lines are stripped and whitespace is collapsed, + such that each character would be visible. + + In browsers that support it, innerText is used. Other browsers attempt to + simulate it via node traversal. Line breaks are canonicalized in IE.

        Parameters
        node: Node
        The node from which we are getting content.
        Returns
        The text content.
        code »goog.dom.getTextContent_ ( node, buf, normalizeWhitespace )

        Recursive support function for text content retrieval.

        Parameters
        node: Node
        The node from which we are getting content.
        buf: Array
        string buffer.
        normalizeWhitespace: boolean
        Whether to normalize whitespace.

        Gets the dimensions of the viewport. + + Gecko Standards mode: + docEl.clientWidth Width of viewport excluding scrollbar. + win.innerWidth Width of viewport including scrollbar. + body.clientWidth Width of body element. + + docEl.clientHeight Height of viewport excluding scrollbar. + win.innerHeight Height of viewport including scrollbar. + body.clientHeight Height of document. + + Gecko Backwards compatible mode: + docEl.clientWidth Width of viewport excluding scrollbar. + win.innerWidth Width of viewport including scrollbar. + body.clientWidth Width of viewport excluding scrollbar. + + docEl.clientHeight Height of document. + win.innerHeight Height of viewport including scrollbar. + body.clientHeight Height of viewport excluding scrollbar. + + IE6/7 Standards mode: + docEl.clientWidth Width of viewport excluding scrollbar. + win.innerWidth Undefined. + body.clientWidth Width of body element. + + docEl.clientHeight Height of viewport excluding scrollbar. + win.innerHeight Undefined. + body.clientHeight Height of document element. + + IE5 + IE6/7 Backwards compatible mode: + docEl.clientWidth 0. + win.innerWidth Undefined. + body.clientWidth Width of viewport excluding scrollbar. + + docEl.clientHeight 0. + win.innerHeight Undefined. + body.clientHeight Height of viewport excluding scrollbar. + + Opera 9 Standards and backwards compatible mode: + docEl.clientWidth Width of viewport excluding scrollbar. + win.innerWidth Width of viewport including scrollbar. + body.clientWidth Width of viewport excluding scrollbar. + + docEl.clientHeight Height of document. + win.innerHeight Height of viewport including scrollbar. + body.clientHeight Height of viewport excluding scrollbar. + + WebKit: + Safari 2 + docEl.clientHeight Same as scrollHeight. + docEl.clientWidth Same as innerWidth. + win.innerWidth Width of viewport excluding scrollbar. + win.innerHeight Height of the viewport including scrollbar. + frame.innerHeight Height of the viewport exluding scrollbar. + + Safari 3 (tested in 522) + + docEl.clientWidth Width of viewport excluding scrollbar. + docEl.clientHeight Height of viewport excluding scrollbar in strict mode. + body.clientHeight Height of viewport excluding scrollbar in quirks mode.

        Parameters
        opt_window: Window=
        Optional window element to test.
        Returns
        Object with values 'width' and 'height'.

        Helper for getViewportSize.

        Parameters
        win: Window
        The window to get the view port size for.
        Returns
        Object with values 'width' and 'height'.
        code »goog.dom.getWindow ( opt_doc )!Window

        Gets the window object associated with the given document.

        Parameters
        opt_doc: Document=
        Document object to get window for.
        Returns
        The window associated with the given document.
        code »goog.dom.getWindow_ ( doc )!Window

        Helper for getWindow.

        Parameters
        doc: !Document
        Document object to get window for.
        Returns
        The window associated with the given document.

        Returns true if the element has a bounding rectangle that would be visible + (i.e. its width and height are greater than zero).

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element has a non-zero bounding rectangle.

        Returns true if the element has a specified tab index.

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element has a specified tab index.

        Converts an HTML string into a document fragment. The string must be + sanitized in order to avoid cross-site scripting. For example + goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;') + triggers an alert in all browsers, even if the returned document fragment + is thrown away immediately.

        Parameters
        htmlString: string
        The HTML string to convert.
        Returns
        The resulting document fragment.

        Helper for htmlToDocumentFragment.

        Parameters
        doc: !Document
        The document.
        htmlString: string
        The HTML string to convert.
        Returns
        The resulting document fragment.
        code »goog.dom.insertChildAt ( parent, child, index )

        Insert a child at a given index. If index is larger than the number of child + nodes that the parent currently has, the node is inserted as the last child + node.

        Parameters
        parent: Element
        The element into which to insert the child.
        child: Node
        The element to insert.
        index: number
        The index at which to insert the new child node. Must + not be negative.

        Inserts a new node after an existing reference node (i.e. as the next + sibling). If the reference node has no parent, then does nothing.

        Parameters
        newNode: Node
        Node to insert.
        refNode: Node
        Reference node to insert after.

        Inserts a new node before an existing reference node (i.e. as the previous + sibling). If the reference node has no parent, then does nothing.

        Parameters
        newNode: Node
        Node to insert.
        refNode: Node
        Reference node to insert before.

        Returns true if the browser is in "CSS1-compatible" (standards-compliant) + mode, false otherwise.

        Returns
        True if in CSS1-compatible mode.

        Returns true if the browser is in "CSS1-compatible" (standards-compliant) + mode, false otherwise.

        Parameters
        doc: Document
        The document to check.
        Returns
        True if in CSS1-compatible mode.

        Whether the object looks like an Element.

        Parameters
        obj: ?
        The object being tested for Element likeness.
        Returns
        Whether the object looks like an Element.

        Returns true if the element can be focused, i.e. it has a tab index that + allows it to receive keyboard focus (tabIndex >= 0), or it is an element + that natively supports keyboard focus.

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element allows keyboard focus.

        Returns true if the element has a tab index that allows it to receive + keyboard focus (tabIndex >= 0), false otherwise. Note that some elements + natively support keyboard focus, even if they have no tab index.

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element has a tab index that allows keyboard + focus.

        Whether the object looks like a DOM node.

        Parameters
        obj: ?
        The object being tested for node likeness.
        Returns
        Whether the object looks like a DOM node.

        Returns true if the object is a NodeList. To qualify as a NodeList, + the object must have a numeric length property and an item function (which + has type 'string' on IE for some reason).

        Parameters
        val: Object
        Object to test.
        Returns
        Whether the object is a NodeList.

        Returns true if the element's tab index allows the element to be focused.

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element's tab index allows focus.

        Returns true if the specified value is a Window object. This includes the + global window for HTML pages, and iframe windows.

        Parameters
        obj: ?
        Variable to test.
        Returns
        Whether the variable is a window.

        Calculates a mediaQuery to check if the current device supports the + given actual to virtual pixel ratio.

        Parameters
        pixelRatio: number
        The ratio of actual pixels to virtual pixels.
        Returns
        pixelRatio if applicable, otherwise 0.

        Returns true if the element is focusable even when tabIndex is not set.

        Parameters
        element: Element
        Element to check.
        Returns
        Whether the element natively supports focus.

        Removes all the child nodes on a DOM node.

        Parameters
        node: Node
        Node to remove children from.

        Removes a node from its parent.

        Parameters
        node: Node
        The node to remove.
        Returns
        The node removed if removed; else, null.
        code »goog.dom.replaceNode ( newNode, oldNode )

        Replaces a node in the DOM tree. Will do nothing if oldNode has no + parent.

        Parameters
        newNode: Node
        Node to insert.
        oldNode: Node
        Node to replace.

        Enables or disables keyboard focus support on the element via its tab index. + Only elements for which goog.dom.isFocusableTabIndex returns true + (or elements that natively support keyboard focus, like form elements) can + receive keyboard focus. See http://go/tabindex for more info.

        Parameters
        element: Element
        Element whose tab index is to be changed.
        enable: boolean
        Whether to set or remove a tab index on the element + that supports keyboard focus.
        code »goog.dom.setProperties ( element, properties )

        Sets multiple properties on a node.

        Parameters
        element: Element
        DOM node to set properties on.
        properties: Object
        Hash of property:value pairs.

        Sets the text content of a node, with cross-browser support.

        Parameters
        node: Node
        The node to change the text content of.
        text: (string|number)
        The value that should replace the node's content.

        Global Properties

        Whether we know the compatibility mode at compile time.

        Map of attributes that should be set using + element.setAttribute(key, val) instead of element[key] = val. Used + by goog.dom.setProperties.

        Map of tags which have predefined values with regard to whitespace.

        Map of tags whose content to ignore when calculating text length.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_dom_vendor.html b/docs/namespace_goog_dom_vendor.html new file mode 100644 index 0000000..b8e21e7 --- /dev/null +++ b/docs/namespace_goog_dom_vendor.html @@ -0,0 +1,4 @@ +goog.dom.vendor

        Namespace goog.dom.vendor

        code »
        Show:

        Global Functions

        Parameters
        eventType: string
        An event type.
        Returns
        A lower-cased vendor prefixed event type.
        code »goog.dom.vendor.getPrefixedPropertyName ( propertyName, opt_object )?string
        Parameters
        propertyName: string
        A property name.
        opt_object: !Object=
        If provided, we verify if the property exists in + the object.
        Returns
        A vendor prefixed property name, or null if it does not + exist.

        Returns the JS vendor prefix used in CSS properties. Different vendors + use different methods of changing the case of the property names.

        Returns
        The JS vendor prefix or null if there is none.

        Returns the vendor prefix used in CSS properties.

        Returns
        The vendor prefix or null if there is none.
        \ No newline at end of file diff --git a/docs/namespace_goog_events.html b/docs/namespace_goog_events.html new file mode 100644 index 0000000..a072eb5 --- /dev/null +++ b/docs/namespace_goog_events.html @@ -0,0 +1,97 @@ +goog.events

        Namespace goog.events

        code »

        Interfaces

        goog.events.Listenable
        A listenable interface.
        goog.events.ListenableKey
        An interface that describes a single registered listener.

        Classes

        goog.events.BrowserEvent
        Accepts a browser event object and creates a patched, cross browser event + object.
        goog.events.Event
        A base class for event objects, so that they can support preventDefault and + stopPropagation.
        goog.events.EventId
        A templated class that is used when registering for events.
        goog.events.EventTarget
        An implementation of goog.events.Listenable with full W3C + EventTarget-like support (capture/bubble mechanism, stopping event + propagation, preventing default actions).
        goog.events.Listener
        Simple class that stores information about a listener
        goog.events.ListenerMap
        Creates a new listener map.

        Enumerations

        goog.events.BrowserFeature
        Enum of browser capabilities.
        goog.events.CaptureSimulationMode
        No Description.
        goog.events.EventType
        Constants for event names.
        goog.events.KeyCodes
        Key codes for common characters.
        Show:

        Type Definitions

        A typedef for event like objects that are dispatchable via the + goog.events.dispatchEvent function. strings are treated as the type for a + goog.events.Event. Objects are treated as an extension of a new + goog.events.Event with the type property of the object being used as the type + of the Event.

        Global Functions

        Dispatches an event (or event like object) and calls all listeners + listening for events of this type. The type of the event is decided by the + type property on the event object. + + If any of the listeners returns false OR calls preventDefault then this + function will return false. If one of the capture listeners calls + stopPropagation, then the bubble listeners won't fire.

        Parameters
        src: goog.events.Listenable
        The event target.
        e: goog.events.EventLike
        Event object.
        Returns
        If anyone called preventDefault on the event object (or + if any of the handlers returns false) this will also return false. + If there are no handlers, or if all handlers return true, this returns + true.

        Provides a nice string showing the normalized event objects public members

        Parameters
        e: Object
        Event Object.
        Returns
        String of the public members of the normalized event object.
        code »goog.events.fireListener ( listener, eventObject )boolean

        Fires a listener with a set of arguments

        Parameters
        listener: goog.events.Listener
        The listener object to call.
        eventObject: Object
        The event object to pass to the listener.
        Returns
        Result of listener.
        code »goog.events.fireListeners ( obj, type, capture, eventObject )boolean

        Fires an object's listeners of a particular type and phase

        Parameters
        obj: Object
        Object whose listeners to call.
        type: (string|!goog.events.EventId)
        Event type.
        capture: boolean
        Which event phase.
        eventObject: Object
        Event object to be passed to listener.
        Returns
        True if all listeners returned true else false.
        code »goog.events.fireListeners_ ( obj, type, capture, eventObject )boolean

        Fires an object's listeners of a particular type and phase.

        Parameters
        obj: Object
        Object whose listeners to call.
        type: (string|!goog.events.EventId)
        Event type.
        capture: boolean
        Which event phase.
        eventObject: Object
        Event object to be passed to listener.
        Returns
        True if all listeners returned true else false.
        code »<EVENTOBJ> goog.events.getListener ( src, type, listener, opt_capt, opt_handler )goog.events.ListenableKey

        Gets the goog.events.Listener for the event or null if no such listener is + in use.

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The target from + which to get listeners.
        type: (?string|!goog.events.EventId.<EVENTOBJ>)
        The type of the event.
        listener: (function(EVENTOBJ): ?|{handleEvent: function(?): ?}|null)
        The + listener function to get.
        opt_capt: boolean=
        In DOM-compliant browsers, this determines + whether the listener is fired during the + capture or bubble phase of the event.
        opt_handler: Object=
        Element in whose scope to call the listener.
        Returns
        the found listener or null if not found.
        Parameters
        src: EventTarget
        The source object.
        Returns
        A listener map for the given + source object, or null if none exists.

        Gets the listeners for a given object, type and capture phase.

        Parameters
        obj: Object
        Object to get listeners for.
        type: (string|!goog.events.EventId)
        Event type.
        capture: boolean
        Capture phase?.
        Returns
        Array of listener objects.

        Returns a string with on prepended to the specified type. This is used for IE + which expects "on" to be prepended. This function caches the string in order + to avoid extra allocations in steady state.

        Parameters
        type: string
        Event type.
        Returns
        The type string with 'on' prepended.

        Helper function for returning a proxy function.

        Returns
        A new or reused function object.
        Deprecated: This returns estimated count, now that Closure no longer + stores a central listener registry. We still return an estimation + to keep existing listener-related tests passing. In the near future, + this function will be removed.

        Gets the total number of listeners currently in the system.

        Returns
        Number of listeners.

        Creates a unique event id.

        Parameters
        identifier: string
        The identifier.
        Returns
        A unique identifier.

        Returns a prefixed event name for the current browser.

        Parameters
        eventName: string
        The name of the event.
        Returns
        The prefixed event name.

        Handles an event and dispatches it to the correct listeners. This + function is a proxy for the real listener the user specified.

        Parameters
        listener: goog.events.Listener
        The listener object.
        opt_evt: Event=
        Optional event object that gets passed in via the + native event handlers.
        Returns
        Result of the event handler.
        code »goog.events.hasListener ( obj, opt_type, opt_capture )boolean

        Returns whether an event target has any active listeners matching the + specified signature. If either the type or capture parameters are + unspecified, the function will match on the remaining criteria.

        Parameters
        obj: (EventTarget|goog.events.Listenable)
        Target to get + listeners for.
        opt_type: (string|!goog.events.EventId)=
        Event type.
        opt_capture: boolean=
        Whether to check for capture or bubble-phase + listeners.
        Returns
        Whether an event target has one or more listeners matching + the requested type and/or capture phase.

        This is used to check if an IE event has already been handled by the Closure + system so we do not do the Closure pass twice for a bubbling event.

        Parameters
        e: Event
        The IE browser event.
        Returns
        True if the event object has been marked.
        code »<T, EVENTOBJ> goog.events.listen ( src, type, listener, opt_capt, opt_handler )goog.events.Key

        Adds an event listener for a specific event on a native event + target (such as a DOM element) or an object that has implemented + goog.events.Listenable. A listener can only be added once + to an object and if it is added again the key for the listener is + returned. Note that if the existing listener is a one-off listener + (registered via listenOnce), it will no longer be a one-off + listener after a call to listen().

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The node to listen + to events on.
        type: (string|Array.<string>|!goog.events.EventId.<EVENTOBJ>|!Array)
        Event type or array of event types.
        listener: (function(this: T, EVENTOBJ): ?|{handleEvent: function(?): ?}|null)
        Callback method, or an object with a handleEvent function. + WARNING: passing an Object is now softly deprecated.
        opt_capt: boolean=
        Whether to fire in capture phase (defaults to + false).
        opt_handler: T=
        Element in whose scope to call the listener.
        Returns
        Unique key for the listener.
        code »<T, EVENTOBJ> goog.events.listenOnce ( src, type, listener, opt_capt, opt_handler )goog.events.Key

        Adds an event listener for a specific event on a native event + target (such as a DOM element) or an object that has implemented + goog.events.Listenable. After the event has fired the event + listener is removed from the target. + + If an existing listener already exists, listenOnce will do + nothing. In particular, if the listener was previously registered + via listen(), listenOnce() will not turn the listener into a + one-off listener. Similarly, if there is already an existing + one-off listener, listenOnce does not modify the listeners (it is + still a once listener).

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The node to listen + to events on.
        type: (string|Array.<string>|!goog.events.EventId.<EVENTOBJ>|!Array)
        Event type or array of event types.
        listener: (function(this: T, EVENTOBJ): ?|{handleEvent: function(?): ?}|null)
        Callback method.
        opt_capt: boolean=
        Fire in capture phase?.
        opt_handler: T=
        Element in whose scope to call the listener.
        Returns
        Unique key for the listener.
        code »<T> goog.events.listenWithWrapper ( src, wrapper, listener, opt_capt, opt_handler )

        Adds an event listener with a specific event wrapper on a DOM Node or an + object that has implemented goog.events.Listenable. A listener can + only be added once to an object.

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The target to + listen to events on.
        wrapper: goog.events.EventWrapper
        Event wrapper to use.
        listener: (function(this: T, ?): ?|{handleEvent: function(?): ?}|null)
        Callback method, or an object with a handleEvent function.
        opt_capt: boolean=
        Whether to fire in capture phase (defaults to + false).
        opt_handler: T=
        Element in whose scope to call the listener.
        code »goog.events.listen_ ( src, type, listener, callOnce, opt_capt, opt_handler )goog.events.ListenableKey

        Adds an event listener for a specific event on a native event + target. A listener can only be added once to an object and if it + is added again the key for the listener is returned. + + Note that a one-off listener will not change an existing listener, + if any. On the other hand a normal listener will change existing + one-off listener to become a normal listener.

        Parameters
        src: EventTarget
        The node to listen to events on.
        type: (string|!goog.events.EventId)
        Event type.
        listener: !Function
        Callback function.
        callOnce: boolean
        Whether the listener is a one-off + listener or otherwise.
        opt_capt: boolean=
        Whether to fire in capture phase (defaults to + false).
        opt_handler: Object=
        Element in whose scope to call the listener.
        Returns
        Unique key for the listener.

        This is used to mark the IE event object so we do not do the Closure pass + twice for a bubbling event.

        Parameters
        e: Event
        The IE browser event.

        Installs exception protection for the browser event entry point using the + given error handler.

        Parameters
        errorHandler: goog.debug.ErrorHandler
        Error handler with which to + protect the entry point.
        code »goog.events.removeAll ( obj, opt_type )number

        Removes all listeners from an object. You can also optionally + remove listeners of a particular type.

        Parameters
        obj: (Object|undefined)
        Object to remove listeners from. Must be an + EventTarget or a goog.events.Listenable.
        opt_type: (string|!goog.events.EventId)=
        Type of event to remove. + Default is all types.
        Returns
        Number of listeners removed.
        Deprecated: This doesn't do anything, now that Closure no longer + stores a central listener registry.

        Removes all native listeners registered via goog.events. Native + listeners are listeners on native browser objects (such as DOM + elements). In particular, goog.events.Listenable and + goog.events.EventTarget listeners will NOT be removed.

        Returns
        Number of listeners removed.
        code »<EVENTOBJ> goog.events.unlisten ( src, type, listener, opt_capt, opt_handler )?boolean

        Removes an event listener which was added with listen().

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The target to stop + listening to events on.
        type: (string|Array.<string>|!goog.events.EventId.<EVENTOBJ>|!Array)
        Event type or array of event types to unlisten to.
        listener: (function(?): ?|{handleEvent: function(?): ?}|null)
        The + listener function to remove.
        opt_capt: boolean=
        In DOM-compliant browsers, this determines + whether the listener is fired during the capture or bubble phase of the + event.
        opt_handler: Object=
        Element in whose scope to call the listener.
        Returns
        indicating whether the listener was there to remove.

        Removes an event listener which was added with listen() by the key + returned by listen().

        Parameters
        key: goog.events.Key
        The key returned by listen() for this + event listener.
        Returns
        indicating whether the listener was there to remove.
        code »goog.events.unlistenWithWrapper ( src, wrapper, listener, opt_capt, opt_handler )

        Removes an event listener which was added with listenWithWrapper().

        Parameters
        src: (EventTarget|goog.events.Listenable)
        The target to stop + listening to events on.
        wrapper: goog.events.EventWrapper
        Event wrapper to use.
        listener: (function(?): ?|{handleEvent: function(?): ?}|null)
        The + listener function to remove.
        opt_capt: boolean=
        In DOM-compliant browsers, this determines + whether the listener is fired during the capture or bubble phase of the + event.
        opt_handler: Object=
        Element in whose scope to call the listener.
        Parameters
        listener: (Object|Function)
        The listener function or an + object that contains handleEvent method.
        Returns
        Either the original function or a function that + calls obj.handleEvent. If the same listener is passed to this + function more than once, the same function is guaranteed to be + returned.

        Global Properties

        Property name on a native event target for the listener map + associated with the event target.

        Expando property for listener function wrapper for Object with + handleEvent.

        Estimated count of total native listeners.

        Map of computed "on" strings for IE event types. Caching + this removes an extra object allocation in goog.events.listen which + improves IE6 performance.

        String used to prepend to IE event types.

        Counter to create unique event ids.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_functions.html b/docs/namespace_goog_functions.html new file mode 100644 index 0000000..a40a710 --- /dev/null +++ b/docs/namespace_goog_functions.html @@ -0,0 +1,30 @@ +goog.functions

        Namespace goog.functions

        code »
        Show:

        Global Functions

        Always returns false.

        Always returns NULL.

        Always returns true.

        code »goog.functions.and ( var_args )function(?): boolean

        Creates a function that returns true if each of its components evaluates + to true. The components are evaluated in order, and the evaluation will be + short-circuited as soon as a function returns false. + For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).

        Parameters
        var_args: ...Function
        A list of functions.
        Returns
        A function that ANDs its component + functions.
        code »<T> goog.functions.cacheReturnValue ( fn )!function(): T

        Gives a wrapper function that caches the return value of a parameterless + function when first called. + + When called for the first time, the given function is called and its + return value is cached (thus this is only appropriate for idempotent + functions). Subsequent calls will return the cached return value. This + allows the evaluation of expensive functions to be delayed until first used. + + To cache the return values of functions with parameters, see goog.memoize.

        Parameters
        fn: !function(): T
        A function to lazily evaluate.
        Returns
        A wrapped version the function.
        code »<T> goog.functions.compose ( fn, var_args )function(?): T

        Creates the composition of the functions passed in. + For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).

        Parameters
        fn: function(?): T
        The final function.
        var_args: ...Function
        A list of functions.
        Returns
        The composition of all inputs.
        code »<T> goog.functions.constant ( retValue )function(): T

        Creates a function that always returns the same value.

        Parameters
        retValue: T
        The value to return.
        Returns
        The new function.
        code »goog.functions.create ( constructor, var_args )!Object

        Generic factory function to construct an object given the constructor + and the arguments. Intended to be bound to create object factories. + + Callers should cast the result to the appropriate type for proper type + checking by the compiler.

        Parameters
        constructor: !Function
        The constructor for the Object.
        var_args: ...*
        The arguments to be passed to the constructor.
        Returns
        A new instance of the class given in constructor.

        Creates a function that always throws an error with the given message.

        Parameters
        message: string
        The error message.
        Returns
        The error-throwing function.

        Creates a function that throws the given object.

        Parameters
        err: *
        An object to be thrown.
        Returns
        The error-throwing function.
        code »<T> goog.functions.identity ( opt_returnValue, var_args )T

        A simple function that returns the first argument of whatever is passed + into it.

        Parameters
        opt_returnValue: T=
        The single value that will be returned.
        var_args: ...*
        Optional trailing arguments. These are ignored.
        Returns
        The first argument passed in, or undefined if nothing was passed.
        code »goog.functions.lock ( f, opt_numArgs )!Function

        Given a function, create a function that keeps opt_numArgs arguments and + silently discards all additional arguments.

        Parameters
        f: Function
        The original function.
        opt_numArgs: number=
        The number of arguments to keep. Defaults to 0.
        Returns
        A version of f that only keeps the first opt_numArgs + arguments.
        code »goog.functions.not ( f )function(?): boolean

        Creates a function that returns the Boolean opposite of a provided function. + For example, (goog.functions.not(f))(x) is equivalent to !f(x).

        Parameters
        f: !Function
        The original function.
        Returns
        A function that delegates to f and returns + opposite.

        Creates a function that returns its nth argument.

        Parameters
        n: number
        The position of the return argument.
        Returns
        A new function.
        code »goog.functions.or ( var_args )function(?): boolean

        Creates a function that returns true if any of its components evaluates + to true. The components are evaluated in order, and the evaluation will be + short-circuited as soon as a function returns true. + For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).

        Parameters
        var_args: ...Function
        A list of functions.
        Returns
        A function that ORs its component + functions.

        Creates a function that calls the functions passed in in sequence, and + returns the value of the last function. For example, + (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).

        Parameters
        var_args: ...Function
        A list of functions.
        Returns
        A function that calls all inputs in sequence.
        code »<T> goog.functions.withReturnValue ( f, retValue )function(?): T

        Given a function, create a new function that swallows its return value + and replaces it with a new one.

        Parameters
        f: Function
        A function.
        retValue: T
        A new return value.
        Returns
        A new function.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_iter.html b/docs/namespace_goog_iter.html index 22ae8f4..eacac6f 100644 --- a/docs/namespace_goog_iter.html +++ b/docs/namespace_goog_iter.html @@ -1,50 +1,127 @@ -goog.iter

        Namespace goog.iter

        code »

        Classes

        goog.iter.Iterator
        Class/interface for iterators.
        Show:

        Type Definitions

        code »goog.iter.Iterable : (goog.iter.Iterator|{length: number}|{__iterator__: ?})
        No description.

        Global Functions

        Takes zero or more iterators and returns one iterator that will iterate over - them in the order chained.

        Parameters
        var_args: ...goog.iter.Iterator
        Any number of iterator objects.
        Returns
        Returns a new iterator that will iterate over - all the given iterators' contents.

        Create an iterator to cycle over the iterable's elements indefinitely. - For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...

        Parameters
        iterable: !goog.iter.Iterable
        The iterable object.
        Returns
        An iterator that iterates indefinitely over - the values in iterable.
        code »<T> goog.iter.dropWhile ( iterable, f, opt_obj )!goog.iter.Iterator

        Builds a new iterator that iterates over the original, but skips elements as - long as a supplied function returns true.

        Parameters
        iterable: goog.iter.Iterable
        The iterator object.
        f: function(this: T, ?, undefined, ?): boolean
        The function to call for - every value. This function - takes 3 arguments (the value, undefined, and the iterator) and should - return a boolean.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        Returns
        A new iterator that drops elements from the - original iterator as long as f is true.
        code »goog.iter.equals ( iterable1, iterable2 )boolean

        Iterates over 2 iterators and returns true if they contain the same sequence - of elements and have the same length.

        Parameters
        iterable1: goog.iter.Iterable
        The first iterable object.
        iterable2: goog.iter.Iterable
        The second iterable object.
        Returns
        true if the iterators contain the same sequence of - elements and have the same length.
        code »<T> goog.iter.every ( iterable, f, opt_obj )boolean

        Goes through the values in the iterator. Calls f for each these and if any of - them returns false this returns false (without checking the rest). If all - return true this will return true.

        Parameters
        iterable: goog.iter.Iterable
        The iterator object.
        f: function(this: T, ?, undefined, ?): boolean
        The function to call for - every value. This function - takes 3 arguments (the value, undefined, and the iterator) and should - return a boolean.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        Returns
        true if every value passes the test.
        code »<T> goog.iter.filter ( iterable, f, opt_obj )!goog.iter.Iterator

        Calls a function for every element in the iterator, and if the function - returns true adds the element to a new iterator.

        Parameters
        iterable: goog.iter.Iterable
        The iterator to iterate over.
        f: function(this: T, ?, undefined, ?): boolean
        The function to call for - every element. This function - takes 3 arguments (the element, undefined, and the iterator) and should - return a boolean. If the return value is true the element will be - included in the returned iteror. If it is false the element is not - included.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        Returns
        A new iterator in which only elements that - passed the test are present.
        code »<T> goog.iter.forEach ( iterable, f, opt_obj )

        Calls a function for each element in the iterator with the element of the - iterator passed as argument.

        Parameters
        iterable: goog.iter.Iterable
        The iterator to iterate - over. If the iterable is an object toIterator will be called on - it.
        f: function(this: T, ?, ?, ?): ?
        The function to call for every - element. This function - takes 3 arguments (the element, undefined, and the iterator) and the - return value is irrelevant. The reason for passing undefined as the - second argument is so that the same function can be used in - goog.array#forEach as well as others.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        code »goog.iter.join ( iterable, deliminator )string

        Joins the values in a iterator with a delimiter.

        Parameters
        iterable: goog.iter.Iterable
        The iterator to get the values from.
        deliminator: string
        The text to put between the values.
        Returns
        The joined value string.
        code »<T> goog.iter.map ( iterable, f, opt_obj )!goog.iter.Iterator

        For every element in the iterator call a function and return a new iterator - with that value.

        Parameters
        iterable: goog.iter.Iterable
        The iterator to iterate over.
        f: function(this: T, ?, undefined, ?): ?
        The function to call for every - element. This function - takes 3 arguments (the element, undefined, and the iterator) and should - return a new value.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        Returns
        A new iterator that returns the results of - applying the function to each element in the original iterator.
        code »goog.iter.nextOrValue ( iterable, defaultValue )*

        Advances the iterator to the next position, returning the given default value - instead of throwing an exception if the iterator has no more entries.

        Parameters
        iterable: goog.iter.Iterable
        The iterable object.
        defaultValue: *
        The value to return if the iterator is empty.
        Returns
        The next item in the iteration, or defaultValue if the iterator - was empty.

        Cartesian product of zero or more sets. Gives an iterator that gives every +goog.iter

        Namespace goog.iter

        code »

        Classes

        goog.iter.GroupByIterator_
        Implements the goog.iter.groupBy iterator.
        goog.iter.Iterator
        Class/interface for iterators.
        Show:

        Type Definitions

        code »goog.iter.Iterable : (goog.iter.Iterator|{length: number}|{__iterator__: ?})
        No description.

        Global Functions

        Creates an iterator that returns running totals from the numbers in + iterable. For example, the array [1, 2, 3, 4, 5] yields + 1 -> 3 -> 6 -> 10 -> 15.

        Parameters
        iterable: !goog.iter.Iterable.<number>
        The iterable of numbers to + accumulate.
        Returns
        A new iterator that returns the + numbers in the series.
        code »<VALUE> goog.iter.chain ( var_args )!goog.iter.Iterator.<VALUE>

        Takes zero or more iterables and returns one iterator that will iterate over + them in the order chained.

        Parameters
        var_args: ...(!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        Any + number of iterable objects.
        Returns
        Returns a new iterator that will + iterate over all the given iterables' contents.
        code »<VALUE> goog.iter.chainFromIterable ( iterable )!goog.iter.Iterator.<VALUE>

        Takes a single iterable containing zero or more iterables and returns one + iterator that will iterate over each one in the order given.

        Parameters
        iterable: goog.iter.Iterable
        The iterable of iterables to chain.
        Returns
        Returns a new iterator that will + iterate over all the contents of the iterables contained within + iterable.
        code »<VALUE> goog.iter.combinations ( iterable, length )!goog.iter.Iterator

        Creates an iterator that returns combinations of elements from + iterable. + + Combinations are obtained by taking the goog.iter#permutations of + iterable and filtering those whose elements appear in the order they + are encountered in iterable. For example, the 3-length combinations + of [0,1,2,3] are [[0,1,2], [0,1,3], [0,2,3], [1,2,3]].

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable from which to generate combinations.
        length: number
        The length of each combination.
        Returns
        A new iterator containing + combinations from the iterable.

        Creates an iterator that returns combinations of elements from + iterable, with repeated elements possible. + + Combinations are obtained by taking the Cartesian product of length + iterables and filtering those whose elements appear in the order they are + encountered in iterable. For example, the 2-length combinations of + [1,2,3] are [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]].

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to combine.
        length: number
        The length of each combination.
        Returns
        A new iterator containing + combinations from the iterable.
        code »<VALUE> goog.iter.compress ( iterable, selectors )!goog.iter.Iterator.<VALUE>

        Creates an iterator that filters iterable based on a series of + selectors. On each call to next(), one item is taken from + both the iterable and selectors iterators. If the item from + selectors evaluates to true, the item from iterable is given. + Otherwise, it is skipped. Once either iterable or selectors + is exhausted, subsequent calls to next() will throw + goog.iter.StopIteration.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to filter.
        selectors: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        An + iterable of items to be evaluated in a boolean context to determine if + the corresponding element in iterable should be included in the + result.
        Returns
        A new iterator that returns the + filtered values.
        code »<VALUE> goog.iter.consume ( iterable, count )!goog.iter.Iterator.<VALUE>

        Creates an iterator that is advanced count steps ahead. Consumed + values are silently discarded. If count is greater than the number + of elements in iterable, an empty iterator is returned. Subsequent + calls to next() will throw goog.iter.StopIteration.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to consume.
        count: number
        The number of elements to consume from the iterator.
        Returns
        An iterator advanced zero or more steps + ahead.
        code »goog.iter.count ( opt_start, opt_step )!goog.iter.Iterator.<number>

        Creates an iterator that counts indefinitely from a starting value.

        Parameters
        opt_start: number=
        The starting value. Default is 0.
        opt_step: number=
        The number to increment with between each call to + next. Negative and floating point numbers are allowed. Default is 1.
        Returns
        A new iterator that returns the values + in the series.
        code »<VALUE> goog.iter.cycle ( iterable )!goog.iter.Iterator.<VALUE>

        Create an iterator to cycle over the iterable's elements indefinitely. + For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable object.
        Returns
        An iterator that iterates indefinitely + over the values in iterable.
        code »<THIS, VALUE> goog.iter.dropWhile ( iterable, f, opt_obj )!goog.iter.Iterator.<VALUE>

        Builds a new iterator that iterates over the original, but skips elements as + long as a supplied function returns true.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + object.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every value. This function takes 3 arguments + (the value, undefined, and the iterator) and should return a boolean.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        A new iterator that drops elements from + the original iterator as long as f is true.
        code »<VALUE> goog.iter.enumerate ( iterable, opt_start )!goog.iter.Iterator

        Creates an iterator that returns arrays containing a count and an element + obtained from the given iterable.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to enumerate.
        opt_start: number=
        Optional starting value. Default is 0.
        Returns
        A new iterator containing count/item + pairs.
        code »<VALUE> goog.iter.equals ( iterable1, iterable2 )boolean

        Iterates over two iterables and returns true if they contain the same + sequence of elements and have the same length.

        Parameters
        iterable1: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The first + iterable object.
        iterable2: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The second + iterable object.
        Returns
        true if the iterables contain the same sequence of elements + and have the same length.
        code »<THIS, VALUE> goog.iter.every ( iterable, f, opt_obj )boolean

        Goes through the values in the iterator. Calls f for each of these and if any + of them returns false this returns false (without checking the rest). If all + return true this will return true.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + object.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every value. This function takes 3 arguments + (the value, undefined, and the iterator) and should return a boolean.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        true if every value passes the test.
        code »<THIS, VALUE> goog.iter.filter ( iterable, f, opt_obj )!goog.iter.Iterator.<VALUE>

        Calls a function for every element in the iterator, and if the function + returns true adds the element to a new iterator.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to iterate over.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every element. This function takes 3 arguments + (the element, undefined, and the iterator) and should return a boolean. + If the return value is true the element will be included in the returned + iterator. If it is false the element is not included.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        A new iterator in which only elements + that passed the test are present.
        code »<THIS, VALUE> goog.iter.filterFalse ( iterable, f, opt_obj )!goog.iter.Iterator.<VALUE>

        Calls a function for every element in the iterator, and if the function + returns false adds the element to a new iterator.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to iterate over.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every element. This function takes 3 arguments + (the element, undefined, and the iterator) and should return a boolean. + If the return value is false the element will be included in the returned + iterator. If it is true the element is not included.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        A new iterator in which only elements + that did not pass the test are present.
        code »<THIS, VALUE> goog.iter.forEach ( iterable, f, opt_obj )

        Calls a function for each element in the iterator with the element of the + iterator passed as argument.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to iterate over. If the iterable is an object toIterator will be + called on it.
        f: (function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>)|function(this: THIS, number, undefined, goog.iter.Iterator.<VALUE>))
        The function to call for every element. This function takes 3 arguments + (the element, undefined, and the iterator) and the return value is + irrelevant. The reason for passing undefined as the second argument is + so that the same function can be used in goog.array#forEach as + well as others.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        code »<KEY, VALUE> goog.iter.groupBy ( iterable, opt_keyFunc )!goog.iter.Iterator

        Creates an iterator that returns arrays containing elements from the + iterable grouped by a key value. For iterables with repeated + elements (i.e. sorted according to a particular key function), this function + has a uniq-like effect. For example, grouping the array: + [A, B, B, C, C, A] produces + [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]].

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to group.
        opt_keyFunc: function(VALUE): KEY=
        Optional function for + determining the key value for each group in the iterable. Default + is the identity function.
        Returns
        A new iterator that returns arrays of + consecutive key and groups.

        Checks an array for duplicate elements.

        Parameters
        arr: (Array.<VALUE>|goog.array.ArrayLike)
        The array to check for + duplicates.
        Returns
        True, if the array contains duplicates, false otherwise.
        code »<VALUE> goog.iter.join ( iterable, deliminator )string

        Joins the values in a iterator with a delimiter.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to get the values from.
        deliminator: string
        The text to put between the values.
        Returns
        The joined value string.
        code »<VALUE> goog.iter.limit ( iterable, limitSize )!goog.iter.Iterator.<VALUE>

        Creates an iterator that returns the first limitSize elements from an + iterable. If this number is greater than the number of elements in the + iterable, all the elements are returned.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to limit.
        limitSize: number
        The maximum number of elements to return.
        Returns
        A new iterator containing + limitSize elements.
        code »<THIS, VALUE, RESULT> goog.iter.map ( iterable, f, opt_obj )!goog.iter.Iterator.<RESULT>

        For every element in the iterator call a function and return a new iterator + with that value.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterator to iterate over.
        f: function(this: THIS, VALUE, undefined, !goog.iter.Iterator.<VALUE>): RESULT
        The function to call for every element. This function takes 3 arguments + (the element, undefined, and the iterator) and should return a new value.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        A new iterator that returns the + results of applying the function to each element in the original + iterator.
        code »<VALUE> goog.iter.nextOrValue ( iterable, defaultValue )VALUE

        Advances the iterator to the next position, returning the given default value + instead of throwing an exception if the iterator has no more entries.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterable + object.
        defaultValue: VALUE
        The value to return if the iterator is empty.
        Returns
        The next item in the iteration, or defaultValue if the + iterator was empty.
        code »<VALUE> goog.iter.permutations ( iterable, opt_length )!goog.iter.Iterator

        Creates an iterator that returns permutations of elements in + iterable. + + Permutations are obtained by taking the Cartesian product of + opt_length iterables and filtering out those with repeated + elements. For example, the permutations of [1,2,3] are + [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]].

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable from which to generate permutations.
        opt_length: number=
        Length of each permutation. If omitted, defaults + to the length of iterable.
        Returns
        A new iterator containing the + permutations of iterable.

        Cartesian product of zero or more sets. Gives an iterator that gives every combination of one element chosen from each set. For example, - ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).

        Parameters
        var_args: ...!goog.array.ArrayLike
        Zero or more sets, as arrays.
        Returns
        An iterator that gives each n-tuple (as an - array).
        code »goog.iter.range ( startOrStop, opt_stop, opt_step )!goog.iter.Iterator

        Creates a new iterator that returns the values in a range. This function + ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).

        Parameters
        var_args: ...!goog.array.ArrayLike.<VALUE>
        Zero or more sets, as + arrays.
        Returns
        An iterator that gives each + n-tuple (as an array).
        code »goog.iter.range ( startOrStop, opt_stop, opt_step )!goog.iter.Iterator.<number>

        Creates a new iterator that returns the values in a range. This function can take 1, 2 or 3 arguments:

          range(5) same as range(0, 5, 1)
        @@ -53,26 +130,60 @@
              The start value if 2 or more arguments are provided.  If only one
              argument is used the start value is 0.
        opt_stop: number=
        The stop value. If left out then the first argument is used as the stop value.
        opt_step: number=
        The number to increment with between each call to - next. This can be negative.Returns
        A new iterator that returns the values in the - range.
        code »<T, V> goog.iter.reduce ( iterable, f, val, opt_obj )V

        Passes every element of an iterator into a function and accumulates the - result.

        Parameters
        iterable: goog.iter.Iterable
        The iterator to iterate over.
        f: function(this: T, V, ?): V
        The function to call for every - element. This function takes 2 arguments (the function's previous result - or the initial value, and the value of the current element). - function(previousValue, currentElement) : newValue.
        val: V
        The initial value to pass into the function on the first call.
        opt_obj: T=
        The object to be used as the value of 'this' - within f.
        Returns
        Result of evaluating f repeatedly across the values of - the iterator.
        code »<T> goog.iter.some ( iterable, f, opt_obj )boolean

        Goes through the values in the iterator. Calls f for each these and if any of - them returns true, this returns true (without checking the rest). If all - return false this will return false.

        Parameters
        iterable: goog.iter.Iterable
        The iterator object.
        f: function(this: T, ?, undefined, ?): boolean
        The function to call for - every value. This function - takes 3 arguments (the value, undefined, and the iterator) and should - return a boolean.
        opt_obj: T=
        The object to be used as the value of 'this' within - f.
        Returns
        true if any value passes the test.
        code »<T> goog.iter.takeWhile ( iterable, f, opt_obj )!goog.iter.Iterator

        Builds a new iterator that iterates over the original, but only as long as a - supplied function returns true.

        Parameters
        iterable: goog.iter.Iterable
        The iterator object.
        f: function(this: T, ?, undefined, ?): boolean
        The function to call for - every value. This function - takes 3 arguments (the value, undefined, and the iterator) and should - return a boolean.
        opt_obj: T=
        This is used as the 'this' object in f when called.
        Returns
        A new iterator that keeps elements in the - original iterator as long as the function is true.

        Converts the iterator to an array

        Parameters
        iterable: goog.iter.Iterable
        The iterator to convert to an array.
        Returns
        An array of the elements the iterator iterates over.

        Returns an iterator that knows how to iterate over the values in the object.

        Parameters
        iterable: goog.iter.Iterable
        If the object is an iterator it - will be returned as is. If the object has a __iterator__ method - that will be called to get the value iterator. If the object is an - array-like object we create an iterator for that.
        Returns
        An iterator that knows how to iterate over the - values in iterable.

        Global Properties

        Singleton Error object that is used to terminate iterations.

        \ No newline at end of file + next. This can be negative.Returns
        A new iterator that returns the values + in the range.
        code »<THIS, VALUE> goog.iter.reduce ( iterable, f, val, opt_obj )VALUE

        Passes every element of an iterator into a function and accumulates the + result.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to iterate over.
        f: function(this: THIS, VALUE, VALUE): VALUE
        The function to call for + every element. This function takes 2 arguments (the function's previous + result or the initial value, and the value of the current element). + function(previousValue, currentElement) : newValue.
        val: VALUE
        The initial value to pass into the function on the first + call.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        Result of evaluating f repeatedly across the values of + the iterator.
        code »<VALUE> goog.iter.repeat ( value )!goog.iter.Iterator.<VALUE>

        Creates an iterator that returns the same object or value repeatedly.

        Parameters
        value: VALUE
        Any object or value to repeat.
        Returns
        A new iterator that returns the + repeated value.
        code »<VALUE> goog.iter.slice ( iterable, start, opt_end )!goog.iter.Iterator.<VALUE>

        Creates an iterator that returns a range of elements from an iterable. + Similar to goog.array#slice but does not support negative indexes.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to slice.
        start: number
        The index of the first element to return.
        opt_end: number=
        The index after the last element to return. If + defined, must be greater than or equal to start.
        Returns
        A new iterator containing a slice of + the original.
        code »<THIS, VALUE> goog.iter.some ( iterable, f, opt_obj )boolean

        Goes through the values in the iterator. Calls f for each of these, and if + any of them returns true, this returns true (without checking the rest). If + all return false this will return false.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + object.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every value. This function takes 3 arguments + (the value, undefined, and the iterator) and should return a boolean.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        true if any value passes the test.
        code »<THIS, RESULT> goog.iter.starMap ( iterable, f, opt_obj )!goog.iter.Iterator.<RESULT>

        Gives an iterator that gives the result of calling the given function + f with the arguments taken from the next element from + iterable (the elements are expected to also be iterables). + + Similar to goog.iter#map but allows the function to accept multiple + arguments from the iterable.

        Parameters
        iterable: !goog.iter.Iterable
        The iterable of + iterables to iterate over.
        f: function(this: THIS, *): RESULT
        The function to call for every + element. This function takes N+2 arguments, where N represents the + number of items from the next element of the iterable. The two + additional arguments passed to the function are undefined and the + iterator itself. The function should return a new value.
        opt_obj: THIS=
        The object to be used as the value of 'this' within + f.
        Returns
        A new iterator that returns the + results of applying the function to each element in the original + iterator.
        code »<THIS, VALUE> goog.iter.takeWhile ( iterable, f, opt_obj )!goog.iter.Iterator.<VALUE>

        Builds a new iterator that iterates over the original, but only as long as a + supplied function returns true.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + object.
        f: function(this: THIS, VALUE, undefined, goog.iter.Iterator.<VALUE>): boolean
        The function to call for every value. This function takes 3 arguments + (the value, undefined, and the iterator) and should return a boolean.
        opt_obj: THIS=
        This is used as the 'this' object in f when called.
        Returns
        A new iterator that keeps elements in + the original iterator as long as the function is true.
        code »<VALUE> goog.iter.tee ( iterable, opt_num )!Array.<goog.iter.Iterator>

        Returns an array of iterators each of which can iterate over the values in + iterable without advancing the others.

        Parameters
        iterable: (!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        The + iterable to tee.
        opt_num: number=
        The number of iterators to create. Default is 2.
        Returns
        An array of iterators.
        code »<VALUE> goog.iter.toArray ( iterable )!Array.<VALUE>

        Converts the iterator to an array

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        The iterator + to convert to an array.
        Returns
        An array of the elements the iterator iterates over.
        code »<VALUE> goog.iter.toIterator ( iterable )!goog.iter.Iterator.<VALUE>

        Returns an iterator that knows how to iterate over the values in the object.

        Parameters
        iterable: (goog.iter.Iterator.<VALUE>|goog.iter.Iterable)
        If the + object is an iterator it will be returned as is. If the object has an + __iterator__ method that will be called to get the value + iterator. If the object is an array-like object we create an iterator + for that.
        Returns
        An iterator that knows how to iterate + over the values in iterable.
        code »<VALUE> goog.iter.zip ( var_args )!goog.iter.Iterator

        Creates an iterator that returns arrays containing the ith elements from the + provided iterables. The returned arrays will be the same size as the number + of iterables given in var_args. Once the shortest iterable is + exhausted, subsequent calls to next() will throw + goog.iter.StopIteration.

        Parameters
        var_args: ...(!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        Any + number of iterable objects.
        Returns
        A new iterator that returns + arrays of elements from the provided iterables.
        code »<VALUE> goog.iter.zipLongest ( fillValue, var_args )!goog.iter.Iterator

        Creates an iterator that returns arrays containing the ith elements from the + provided iterables. The returned arrays will be the same size as the number + of iterables given in var_args. Shorter iterables will be extended + with fillValue. Once the longest iterable is exhausted, subsequent + calls to next() will throw goog.iter.StopIteration.

        Parameters
        fillValue: VALUE
        The object or value used to fill shorter iterables.
        var_args: ...(!goog.iter.Iterator.<VALUE>|!goog.iter.Iterable)
        Any + number of iterable objects.
        Returns
        A new iterator that returns + arrays of elements from the provided iterables.

        Global Properties

        Singleton Error object that is used to terminate iterations.

        \ No newline at end of file diff --git a/docs/namespace_goog_json.html b/docs/namespace_goog_json.html index 7ce0e5b..a8d4125 100644 --- a/docs/namespace_goog_json.html +++ b/docs/namespace_goog_json.html @@ -1,12 +1,10 @@ -goog.json

        Namespace goog.json

        code »

        Classes

        goog.json.Serializer
        Class that is used to serialize JSON objects to a string.
        Show:

        Type Definitions

        code »goog.json.Replacer : function(this: Object, string, *): *
        JSON replacer, as defined in Section 15.12.3 of the ES5 spec. - - TODO(nicksantos): Array should also be a valid replacer.
        code »goog.json.Reviver : function(this: Object, string, *): *
        JSON reviver, as defined in Section 15.12.2 of the ES5 spec.

        Global Functions

        Tests if a string is an invalid JSON string. This only ensures that we are - not using any invalid characters

        Parameters
        s: string
        The string to test.
        Returns
        True if the input is a valid JSON string.

        Parses a JSON string and returns the result. This throws an exception if +goog.json

        Namespace goog.json

        code »

        Classes

        goog.json.Serializer
        Class that is used to serialize JSON objects to a string.
        Show:

        Type Definitions

        code »goog.json.Replacer : function(this: Object, string, *): *
        JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
        code »goog.json.Reviver : function(this: Object, string, *): *
        JSON reviver, as defined in Section 15.12.2 of the ES5 spec.

        Global Functions

        Tests if a string is an invalid JSON string. This only ensures that we are + not using any invalid characters

        Parameters
        s: string
        The string to test.
        Returns
        True if the input is a valid JSON string.

        Parses a JSON string and returns the result. This throws an exception if the string is an invalid JSON string. Note that this is very slow on large strings. If you trust the source of - the string then you should use unsafeParse instead.

        Parameters
        s: *
        The JSON string to parse.
        Returns
        The object generated from the JSON string.
        code »goog.json.serialize ( object, opt_replacer )string

        Serializes an object or a value to a JSON string.

        Parameters
        object: *
        The object to serialize.
        opt_replacer: ?goog.json.Replacer=
        A replacer function + the string then you should use unsafeParse instead.
        Parameters
        s: *
        The JSON string to parse.
        Returns
        The object generated from the JSON string, or null.
        Throws
        if s is invalid JSON.
        code »goog.json.serialize ( object, opt_replacer )string

        Serializes an object or a value to a JSON string.

        Parameters
        object: *
        The object to serialize.
        opt_replacer: ?goog.json.Replacer=
        A replacer function called for each (key, value) pair that determines how the value should be serialized. By defult, this just returns the value - and allows default serialization to kick in.
        Returns
        A JSON string representation of the input.
        Throws
        if there are loops in the object graph.

        Parses a JSON string and returns the result. This uses eval so it is open - to security issues and it should only be used if you trust the source.

        Parameters
        s: string
        The JSON string to parse.
        Returns
        The object generated from the JSON string.
        \ No newline at end of file + and allows default serialization to kick in.
        Returns
        A JSON string representation of the input.
        Throws
        if there are loops in the object graph.

        Parses a JSON string and returns the result. This uses eval so it is open + to security issues and it should only be used if you trust the source.

        Parameters
        s: string
        The JSON string to parse.
        Returns
        The object generated from the JSON string.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_labs.html b/docs/namespace_goog_labs.html index 873f175..ec6f76c 100644 --- a/docs/namespace_goog_labs.html +++ b/docs/namespace_goog_labs.html @@ -1 +1 @@ -goog.labs

        Namespace goog.labs

        code »
        Show:
        \ No newline at end of file +goog.labs

        Namespace goog.labs

        code »
        Show:
        \ No newline at end of file diff --git a/docs/namespace_goog_labs_testing.html b/docs/namespace_goog_labs_testing.html index 2d4df99..e97e643 100644 --- a/docs/namespace_goog_labs_testing.html +++ b/docs/namespace_goog_labs_testing.html @@ -1 +1 @@ -goog.labs.testing

        Namespace goog.labs.testing

        code »

        Interfaces

        goog.labs.testing.Matcher
        A matcher object to be used in assertThat statements.

        Classes

        goog.labs.testing.AllOfMatcher
        The AllOf matcher.
        goog.labs.testing.AnyOfMatcher
        The AnyOf matcher.
        goog.labs.testing.CloseToMatcher
        The CloseTo matcher.
        goog.labs.testing.ContainsStringMatcher
        The ContainsString matcher.
        goog.labs.testing.EndsWithMatcher
        The EndsWith matcher.
        goog.labs.testing.EqualToIgnoringWhitespaceMatcher
        The EqualToIgnoringWhitespace matcher.
        goog.labs.testing.EqualToMatcher
        The EqualTo matcher.
        goog.labs.testing.EqualsMatcher
        The Equals matcher.
        goog.labs.testing.GreaterThanEqualToMatcher
        The GreaterThanEqualTo matcher.
        goog.labs.testing.GreaterThanMatcher
        The GreaterThan matcher.
        goog.labs.testing.HasPropertyMatcher
        The HasProperty matcher.
        goog.labs.testing.InstanceOfMatcher
        The InstanceOf matcher.
        goog.labs.testing.IsNotMatcher
        The IsNot matcher.
        goog.labs.testing.IsNullMatcher
        The IsNull matcher.
        goog.labs.testing.IsNullOrUndefinedMatcher
        The IsNullOrUndefined matcher.
        goog.labs.testing.IsUndefinedMatcher
        The IsUndefined matcher.
        goog.labs.testing.LessThanEqualToMatcher
        The LessThanEqualTo matcher.
        goog.labs.testing.LessThanMatcher
        The lessThan matcher.
        goog.labs.testing.MatcherError
        Error thrown when a Matcher fails to match the input value.
        goog.labs.testing.ObjectEqualsMatcher
        The Equals matcher.
        goog.labs.testing.RegexMatcher
        The MatchesRegex matcher.
        goog.labs.testing.StartsWithMatcher
        The StartsWith matcher.
        goog.labs.testing.StringContainsInOrderMatcher
        The StringContainsInOrdermatcher.
        Show:

        Global Functions

        code »goog.labs.testing.assertThat ( actual, matcher, opt_reason )

        Asserts that the actual value evaluated by the matcher is true.

        Parameters
        actual: *
        The object to assert by the matcher.
        matcher: !goog.labs.testing.Matcher
        A matcher to verify values.
        opt_reason: string=
        Description of what is asserted.
        \ No newline at end of file +goog.labs.testing

        Namespace goog.labs.testing

        code »

        Interfaces

        goog.labs.testing.Matcher
        A matcher object to be used in assertThat statements.

        Classes

        goog.labs.testing.AllOfMatcher
        The AllOf matcher.
        goog.labs.testing.AnyOfMatcher
        The AnyOf matcher.
        goog.labs.testing.CloseToMatcher
        The CloseTo matcher.
        goog.labs.testing.ContainsStringMatcher
        The ContainsString matcher.
        goog.labs.testing.EndsWithMatcher
        The EndsWith matcher.
        goog.labs.testing.EqualToIgnoringWhitespaceMatcher
        The EqualToIgnoringWhitespace matcher.
        goog.labs.testing.EqualToMatcher
        The EqualTo matcher.
        goog.labs.testing.EqualsMatcher
        The Equals matcher.
        goog.labs.testing.GreaterThanEqualToMatcher
        The GreaterThanEqualTo matcher.
        goog.labs.testing.GreaterThanMatcher
        The GreaterThan matcher.
        goog.labs.testing.HasPropertyMatcher
        The HasProperty matcher.
        goog.labs.testing.InstanceOfMatcher
        The InstanceOf matcher.
        goog.labs.testing.IsNotMatcher
        The IsNot matcher.
        goog.labs.testing.IsNullMatcher
        The IsNull matcher.
        goog.labs.testing.IsNullOrUndefinedMatcher
        The IsNullOrUndefined matcher.
        goog.labs.testing.IsUndefinedMatcher
        The IsUndefined matcher.
        goog.labs.testing.LessThanEqualToMatcher
        The LessThanEqualTo matcher.
        goog.labs.testing.LessThanMatcher
        The lessThan matcher.
        goog.labs.testing.MatcherError
        Error thrown when a Matcher fails to match the input value.
        goog.labs.testing.ObjectEqualsMatcher
        The Equals matcher.
        goog.labs.testing.RegexMatcher
        The MatchesRegex matcher.
        goog.labs.testing.StartsWithMatcher
        The StartsWith matcher.
        goog.labs.testing.StringContainsInOrderMatcher
        The StringContainsInOrdermatcher.
        Show:

        Global Functions

        code »goog.labs.testing.assertThat ( actual, matcher, opt_reason )

        Asserts that the actual value evaluated by the matcher is true.

        Parameters
        actual: *
        The object to assert by the matcher.
        matcher: !goog.labs.testing.Matcher
        A matcher to verify values.
        opt_reason: string=
        Description of what is asserted.
        \ No newline at end of file diff --git a/docs/namespace_goog_labs_userAgent.html b/docs/namespace_goog_labs_userAgent.html new file mode 100644 index 0000000..732c4b7 --- /dev/null +++ b/docs/namespace_goog_labs_userAgent.html @@ -0,0 +1 @@ +goog.labs.userAgent

        Namespace goog.labs.userAgent

        code »
        Show:
        \ No newline at end of file diff --git a/docs/namespace_goog_labs_userAgent_browser.html b/docs/namespace_goog_labs_userAgent_browser.html new file mode 100644 index 0000000..acc4108 --- /dev/null +++ b/docs/namespace_goog_labs_userAgent_browser.html @@ -0,0 +1,13 @@ +goog.labs.userAgent.browser

        Namespace goog.labs.userAgent.browser

        code »
        Show:

        Global Functions

        Determines IE version. More information: + http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString + http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx + http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx + http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx

        Parameters
        userAgent: string
        the User-Agent.
        Returns
        The browser version or empty string if version cannot be + determined. Note that for Internet Explorer, this returns the version of + the browser, not the version of the rendering engine. (IE 8 in + compatibility mode will return 8.0 rather than 7.0. To determine the + rendering engine version, look at document.documentMode instead. See + http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more + details.)
        Returns
        Whether the user's browser is the Android browser.
        Returns
        Whether the user's browser is Chrome.
        Returns
        Whether the user's browser is Firefox.
        Returns
        Whether the user's browser is IE.
        Returns
        Whether the user's browser is Opera.
        Returns
        Whether the user's browser is Safari.

        For more information, see: + http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html

        Returns
        Whether the user's browser is Silk.
        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the browser version is higher or the same as the + given version.
        Returns
        Whether the user's browser is the Android browser.
        Returns
        Whether the user's browser is Chrome.
        Returns
        Whether the user's browser is Firefox.
        Returns
        Whether the user's browser is IE.
        Returns
        Whether the user's browser is Opera.
        Returns
        Whether the user's browser is Safari.
        \ No newline at end of file diff --git a/docs/namespace_goog_labs_userAgent_engine.html b/docs/namespace_goog_labs_userAgent_engine.html new file mode 100644 index 0000000..1f9b838 --- /dev/null +++ b/docs/namespace_goog_labs_userAgent_engine.html @@ -0,0 +1,4 @@ +goog.labs.userAgent.engine

        Namespace goog.labs.userAgent.engine

        code »
        Show:

        Global Functions

        Returns
        The rendering engine's version or empty string if version + can't be determined.
        Parameters
        tuples: !Array
        Version tuples.
        key: string
        The key to look for.
        Returns
        The version string of the given key, if present. + Otherwise, the empty string.
        Returns
        Whether the rendering engine is Gecko.
        Returns
        Whether the rendering engine is Presto.
        Returns
        Whether the rendering engine is Trident.
        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the rendering engine version is higher or the same + as the given version.
        Returns
        Whether the rendering engine is WebKit.
        \ No newline at end of file diff --git a/docs/namespace_goog_labs_userAgent_util.html b/docs/namespace_goog_labs_userAgent_util.html new file mode 100644 index 0000000..e56994c --- /dev/null +++ b/docs/namespace_goog_labs_userAgent_util.html @@ -0,0 +1,9 @@ +goog.labs.userAgent.util

        Namespace goog.labs.userAgent.util

        code »
        Show:

        Global Functions

        Parses the user agent into tuples for each section.

        Parameters
        userAgent
        Returns
        Tuples of key, version, and the contents + of the parenthetical.

        Gets the native userAgent string from navigator if it exists. + If navigator or navigator.userAgent string is missing, returns an empty + string.

        Getter for the native navigator. + This is a separate function so it can be stubbed out in testing.

        Returns
        The user agent string.
        Parameters
        str
        Returns
        Whether the user agent contains the given string, ignoring + case.
        Parameters
        str
        Returns
        Whether the user agent contains the given string.

        Applications may override browser detection on the built in + navigator.userAgent object by setting this string. Set to null to use the + browser object instead.

        Parameters
        opt_userAgent: ?string=
        The User-Agent override.

        Global Properties

        A possible override for applications which wish to not check + navigator.userAgent but use a specified value for detection instead.

        \ No newline at end of file diff --git a/docs/namespace_goog_math.html b/docs/namespace_goog_math.html new file mode 100644 index 0000000..224a4e6 --- /dev/null +++ b/docs/namespace_goog_math.html @@ -0,0 +1,61 @@ +goog.math

        Namespace goog.math

        code »

        Classes

        goog.math.Box
        Class for representing a box.
        goog.math.Coordinate
        Class for representing coordinates and positions.
        goog.math.Rect
        Class for representing rectangular regions.
        goog.math.Size
        Class for representing sizes consisting of a width and height.
        Show:

        Global Functions

        code »goog.math.angle ( x1, y1, x2, y2 )number

        Computes the angle between two points (x1,y1) and (x2,y2). + Angle zero points in the +X direction, 90 degrees points in the +Y + direction (down) and from there we grow clockwise towards 360 degrees.

        Parameters
        x1: number
        x of first point.
        y1: number
        y of first point.
        x2: number
        x of second point.
        y2: number
        y of second point.
        Returns
        Standardized angle in degrees of the vector from + x1,y1 to x2,y2.
        code »goog.math.angleDifference ( startAngle, endAngle )number

        Computes the difference between startAngle and endAngle (angles in degrees).

        Parameters
        startAngle: number
        Start angle in degrees.
        endAngle: number
        End angle in degrees.
        Returns
        The number of degrees that when added to + startAngle will result in endAngle. Positive numbers mean that the + direction is clockwise. Negative numbers indicate a counter-clockwise + direction. + The shortest route (clockwise vs counter-clockwise) between the angles + is used. + When the difference is 180 degrees, the function returns 180 (not -180) + angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10. + angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
        code »goog.math.angleDx ( degrees, radius )number

        For a given angle and radius, finds the X portion of the offset.

        Parameters
        degrees: number
        Angle in degrees (zero points in +X direction).
        radius: number
        Radius.
        Returns
        The x-distance for the angle and radius.
        code »goog.math.angleDy ( degrees, radius )number

        For a given angle and radius, finds the Y portion of the offset.

        Parameters
        degrees: number
        Angle in degrees (zero points in +X direction).
        radius: number
        Radius.
        Returns
        The y-distance for the angle and radius.

        Returns the arithmetic mean of the arguments.

        Parameters
        var_args: ...number
        Numbers to average.
        Returns
        The average of the arguments (NaN if no arguments + were provided or any of the arguments is not a valid number).
        code »goog.math.clamp ( value, min, max )number

        Takes a number and clamps it to within the provided bounds.

        Parameters
        value: number
        The input number.
        min: number
        The minimum value to return.
        max: number
        The maximum value to return.
        Returns
        The input number if it is within bounds, or the nearest + number within the bounds.

        Returns whether the supplied number is finite and not NaN.

        Parameters
        num: number
        The number to test.
        Returns
        Whether num is a finite number.

        Returns whether the supplied number represents an integer, i.e. that is has + no fractional component. No range-checking is performed on the number.

        Parameters
        num: number
        The number to test.
        Returns
        Whether num is an integer.

        Performs linear interpolation between values a and b. Returns the value + between a and b proportional to x (when x is between 0 and 1. When x is + outside this range, the return value is a linear extrapolation).

        Parameters
        a: number
        A number.
        b: number
        A number.
        x: number
        The proportion between a and b.
        Returns
        The interpolated value between a and b.

        Returns the precise value of floor(log10(num)). + Simpler implementations didn't work because of floating point rounding + errors. For example +

          +
        • Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3. +
        • Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15. +
        • Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1. +
        Parameters
        num: number
        A floating point number.
        Returns
        Its logarithm to base 10 rounded down to the nearest + integer if num > 0. -Infinity if num == 0. NaN if num < 0.
        code »goog.math.longestCommonSubsequence ( array1, array2, opt_compareFn, opt_collectorFn )!Array.<Object>

        JavaScript implementation of Longest Common Subsequence problem. + http://en.wikipedia.org/wiki/Longest_common_subsequence + + Returns the longest possible array that is subarray of both of given arrays.

        Parameters
        array1: Array.<Object>
        First array of objects.
        array2: Array.<Object>
        Second array of objects.
        opt_compareFn: Function=
        Function that acts as a custom comparator + for the array ojects. Function should return true if objects are equal, + otherwise false.
        opt_collectorFn: Function=
        Function used to decide what to return + as a result subsequence. It accepts 2 arguments: index of common element + in the first array and index in the second. The default function returns + element from the first array.
        Returns
        A list of objects that are common to both arrays + such that there is no common subsequence with size greater than the + length of the list.

        The % operator in JavaScript returns the remainder of a / b, but differs from + some other languages in that the result will have the same sign as the + dividend. For example, -1 % 8 == -1, whereas in some other languages + (such as Python) the result would be 7. This function emulates the more + correct modulo behavior, which is useful for certain applications such as + calculating an offset index in a circular list.

        Parameters
        a: number
        The dividend.
        b: number
        The divisor.
        Returns
        a % b where the result is between 0 and b (either 0 <= x < b + or b < x <= 0, depending on the sign of b).
        code »goog.math.nearlyEquals ( a, b, opt_tolerance )boolean

        Tests whether the two values are equal to each other, within a certain + tolerance to adjust for floating point errors.

        Parameters
        a: number
        A number.
        b: number
        A number.
        opt_tolerance: number=
        Optional tolerance range. Defaults + to 0.000001. If specified, should be greater than 0.
        Returns
        Whether a and b are nearly equal.

        Returns a random integer greater than or equal to 0 and less than a.

        Parameters
        a: number
        The upper bound for the random integer (exclusive).
        Returns
        A random integer N such that 0 <= N < a.
        code »goog.math.safeCeil ( num, opt_epsilon )number

        A tweaked variant of Math.ceil. See goog.math.safeFloor for + details.

        Parameters
        num: number
        A number.
        opt_epsilon: number=
        An infinitesimally small positive number, the + rounding error to tolerate.
        Returns
        The smallest integer greater than or equal to num.
        code »goog.math.safeFloor ( num, opt_epsilon )number

        A tweaked variant of Math.floor which tolerates if the passed number + is infinitesimally smaller than the closest integer. It often happens with + the results of floating point calculations because of the finite precision + of the intermediate results. For example Math.floor(Math.log(1000) / + Math.LN10) == 2, not 3 as one would expect.

        Parameters
        num: number
        A number.
        opt_epsilon: number=
        An infinitesimally small positive number, the + rounding error to tolerate.
        Returns
        The largest integer less than or equal to num.

        Returns the unbiased sample variance of the arguments. For a definition, + see e.g. http://en.wikipedia.org/wiki/Variance

        Parameters
        var_args: ...number
        Number samples to analyze.
        Returns
        The unbiased sample variance of the arguments (0 if fewer + than two samples were provided, or NaN if any of the samples is + not a valid number).

        Returns the sign of a number as per the "sign" or "signum" function.

        Parameters
        x: number
        The number to take the sign of.
        Returns
        -1 when negative, 1 when positive, 0 when 0.

        Normalizes an angle to be in range [0-360). Angles outside this range will + be normalized to be the equivalent angle with that range.

        Parameters
        angle: number
        Angle in degrees.
        Returns
        Standardized angle.

        Normalizes an angle to be in range [0-2*PI). Angles outside this range will + be normalized to be the equivalent angle with that range.

        Parameters
        angle: number
        Angle in radians.
        Returns
        Standardized angle.

        Returns the sample standard deviation of the arguments. For a definition of + sample standard deviation, see e.g. + http://en.wikipedia.org/wiki/Standard_deviation

        Parameters
        var_args: ...number
        Number samples to analyze.
        Returns
        The sample standard deviation of the arguments (0 if fewer + than two samples were provided, or NaN if any of the samples is + not a valid number).
        code »goog.math.sum ( var_args )number

        Returns the sum of the arguments.

        Parameters
        var_args: ...number
        Numbers to add.
        Returns
        The sum of the arguments (0 if no arguments were provided, + NaN if any of the arguments is not a valid number).
        code »goog.math.toDegrees ( angleRadians )number

        Converts radians to degrees.

        Parameters
        angleRadians: number
        Angle in radians.
        Returns
        Angle in degrees.
        code »goog.math.toRadians ( angleDegrees )number

        Converts degrees to radians.

        Parameters
        angleDegrees: number
        Angle in degrees.
        Returns
        Angle in radians.

        Returns a random number greater than or equal to a and less than + b.

        Parameters
        a: number
        The lower bound for the random number (inclusive).
        b: number
        The upper bound for the random number (exclusive).
        Returns
        A random number N such that a <= N < b.
        \ No newline at end of file diff --git a/docs/namespace_goog_net.html b/docs/namespace_goog_net.html index 0cc40e8..8079e41 100644 --- a/docs/namespace_goog_net.html +++ b/docs/namespace_goog_net.html @@ -1 +1 @@ -goog.net

        Namespace goog.net

        code »

        Classes

        goog.net.DefaultXmlHttpFactory
        Default factory to use when creating xhr objects.
        goog.net.WrapperXmlHttpFactory
        An xhr factory subclass which can be constructed using two factory methods.
        goog.net.XmlHttpFactory
        Abstract base class for an XmlHttpRequest factory.
        Show:

        Global Functions

        code »goog.net.XmlHttp ( )!(XMLHttpRequest|GearsHttpRequest)

        Static class for creating XMLHttpRequest objects.

        Returns
        A new XMLHttpRequest object.
        \ No newline at end of file +goog.net

        Namespace goog.net

        code »

        Interfaces

        goog.net.XhrLike
        Interface for the common parts of XMLHttpRequest.

        Classes

        goog.net.DefaultXmlHttpFactory
        Default factory to use when creating xhr objects.
        goog.net.WrapperXmlHttpFactory
        An xhr factory subclass which can be constructed using two factory methods.
        goog.net.XmlHttpFactory
        Abstract base class for an XmlHttpRequest factory.
        Show:

        Global Functions

        Static class for creating XMLHttpRequest objects.

        Returns
        A new XMLHttpRequest object.
        \ No newline at end of file diff --git a/docs/namespace_goog_net_XmlHttp.html b/docs/namespace_goog_net_XmlHttp.html index d9cce79..6d28fc5 100644 --- a/docs/namespace_goog_net_XmlHttp.html +++ b/docs/namespace_goog_net_XmlHttp.html @@ -1,4 +1,4 @@ -goog.net.XmlHttp

        Namespace goog.net.XmlHttp

        code »

        Static class for creating XMLHttpRequest objects.

        Main

        XmlHttp ( )!(XMLHttpRequest|GearsHttpRequest)
        Returns
        A new XMLHttpRequest object.

        Enumerations

        goog.net.XmlHttp.OptionType
        Type of options that an XmlHttp object can have.
        goog.net.XmlHttp.ReadyState
        Status constants for XMLHTTP, matches: +goog.net.XmlHttp

        Namespace goog.net.XmlHttp

        code »

        Static class for creating XMLHttpRequest objects.

        Main

        XmlHttp ( )!goog.net.XhrLike.OrNative
        Returns
        A new XMLHttpRequest object.

        Enumerations

        goog.net.XmlHttp.OptionType
        Type of options that an XmlHttp object can have.
        goog.net.XmlHttp.ReadyState
        Status constants for XMLHTTP, matches: http://msdn.microsoft.com/library/default.asp?url=/library/ - en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
        Show:

        Global Functions

        Gets the options to use with the XMLHttpRequest objects obtained using - the static methods.

        Returns
        The options.
        code »goog.net.XmlHttp.setFactory ( factory, optionsFactory )
        Deprecated: Use setGlobalFactory instead.

        Sets the factories for creating XMLHttpRequest objects and their options.

        Parameters
        factory: Function
        The factory for XMLHttpRequest objects.
        optionsFactory: Function
        The factory for options.

        Sets the global factory object.

        Parameters
        factory: !goog.net.XmlHttpFactory
        New global factory object.

        Global Properties

        The global factory instance for creating XMLHttpRequest objects.

        Compiler Constants

        \ No newline at end of file + en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
        Show:

        Global Functions

        Gets the options to use with the XMLHttpRequest objects obtained using + the static methods.

        Returns
        The options.
        code »goog.net.XmlHttp.setFactory ( factory, optionsFactory )
        Deprecated: Use setGlobalFactory instead.

        Sets the factories for creating XMLHttpRequest objects and their options.

        Parameters
        factory: Function
        The factory for XMLHttpRequest objects.
        optionsFactory: Function
        The factory for options.

        Sets the global factory object.

        Parameters
        factory: !goog.net.XmlHttpFactory
        New global factory object.

        Global Properties

        The global factory instance for creating XMLHttpRequest objects.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_net_XmlHttpDefines.html b/docs/namespace_goog_net_XmlHttpDefines.html new file mode 100644 index 0000000..18c999c --- /dev/null +++ b/docs/namespace_goog_net_XmlHttpDefines.html @@ -0,0 +1 @@ +goog.net.XmlHttpDefines

        Namespace goog.net.XmlHttpDefines

        code »
        Show:

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_object.html b/docs/namespace_goog_object.html index 8bf0a43..c9af760 100644 --- a/docs/namespace_goog_object.html +++ b/docs/namespace_goog_object.html @@ -1,70 +1,72 @@ -goog.object

        Namespace goog.object

        code »
        Show:

        Global Functions

        code »<K, V> goog.object.add ( obj, key, val )

        Adds a key-value pair to the object. Throws an exception if the key is - already in use. Use set if you want to change an existing pair.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        val: V
        The value to add.

        Removes all key value pairs from the object/map/hash.

        Parameters
        obj: Object
        The object to clear.
        code »<K, V> goog.object.clone ( obj )!Object.<K, V>

        Does a flat clone of the object.

        Parameters
        obj: Object.<K, V>
        Object to clone.
        Returns
        Clone of the input object.
        code »<K, V> goog.object.contains ( obj, val )boolean

        Whether the object/hash/map contains the given object as a value. - An alias for goog.object.containsValue(obj, val).

        Parameters
        obj: Object.<K, V>
        The object in which to look for val.
        val: V
        The object for which to check.
        Returns
        true if val is present.

        Whether the object/map/hash contains the given key.

        Parameters
        obj: Object
        The object in which to look for key.
        key: *
        The key for which to check.
        Returns
        true If the map contains the key.

        Whether the object/map/hash contains the given value. This is O(n).

        Parameters
        obj: Object.<K, V>
        The object in which to look for val.
        val: V
        The value for which to check.
        Returns
        true If the map contains the value.

        Creates a new object built from the key-value pairs provided as arguments.

        Parameters
        var_args: ...*
        If only one argument is provided and it is an array +goog.object

        Namespace goog.object

        code »
        Show:

        Global Functions

        code »<K, V> goog.object.add ( obj, key, val )

        Adds a key-value pair to the object. Throws an exception if the key is + already in use. Use set if you want to change an existing pair.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        val: V
        The value to add.

        Removes all key value pairs from the object/map/hash.

        Parameters
        obj: Object
        The object to clear.
        code »<K, V> goog.object.clone ( obj )!Object.<K, V>

        Does a flat clone of the object.

        Parameters
        obj: Object.<K, V>
        Object to clone.
        Returns
        Clone of the input object.
        code »<K, V> goog.object.contains ( obj, val )boolean

        Whether the object/hash/map contains the given object as a value. + An alias for goog.object.containsValue(obj, val).

        Parameters
        obj: Object.<K, V>
        The object in which to look for val.
        val: V
        The object for which to check.
        Returns
        true if val is present.

        Whether the object/map/hash contains the given key.

        Parameters
        obj: Object
        The object in which to look for key.
        key: *
        The key for which to check.
        Returns
        true If the map contains the key.

        Whether the object/map/hash contains the given value. This is O(n).

        Parameters
        obj: Object.<K, V>
        The object in which to look for val.
        val: V
        The value for which to check.
        Returns
        true If the map contains the value.

        Creates a new object built from the key-value pairs provided as arguments.

        Parameters
        var_args: ...*
        If only one argument is provided and it is an array then this is used as the arguments, otherwise even arguments are used as the property names and odd arguments are used as the property values.
        Returns
        The new object.
        Throws
        Error
        If there are uneven number of arguments or there is only one - non array argument.

        Creates an immutable view of the underlying object, if the browser + non array argument.

        Creates an immutable view of the underlying object, if the browser supports immutable objects. In default mode, writes to this view will fail silently. In strict mode, they will throw an error.

        Parameters
        obj: !Object.<K, V>
        An object.
        Returns
        An immutable view of that object, or the - original object if this browser does not support immutables.

        Creates a new object where the property names come from the arguments but + original object if this browser does not support immutables.

        Creates a new object where the property names come from the arguments but the value is always set to true

        Parameters
        var_args: ...*
        If only one argument is provided and it is an array then this is used as the arguments, otherwise the arguments are used - as the property names.
        Returns
        The new object.
        code »<T, K, V> goog.object.every ( obj, f, opt_obj )boolean

        Calls a function for each element in an object/map/hash. If + as the property names.Returns

        The new object.

        Compares two objects for equality using === on the values.

        Parameters
        a
        b
        code »<T, K, V> goog.object.every ( obj, f, opt_obj )boolean

        Calls a function for each element in an object/map/hash. If all calls return true, returns true. If any call returns false, returns false at this point and does not continue to check the remaining elements.

        Parameters
        obj: Object.<K, V>
        The object to check.
        f: ?function(this: T, V, ?, Object.<K, V>): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the object) and should - return a boolean.
        opt_obj: T=
        This is used as the 'this' object within f.
        Returns
        false if any element fails the test.
        code »goog.object.extend ( target, var_args )

        Extends an object with another object. + return a boolean.

        opt_obj: T=
        This is used as the 'this' object within f.Returns
        false if any element fails the test.
        code »goog.object.extend ( target, var_args )

        Extends an object with another object. This operates 'in-place'; it does not create a new Object. Example: var o = {}; goog.object.extend(o, {a: 0, b: 1}); o; // {a: 0, b: 1} - goog.object.extend(o, {c: 2}); - o; // {a: 0, b: 1, c: 2}

        Parameters
        target: Object
        The object to modify.
        var_args: ...Object
        The objects from which values will be copied.
        code »<T, K, V> goog.object.filter ( obj, f, opt_obj )!Object.<K, V>

        Calls a function for each element in an object/map/hash. If that call returns + goog.object.extend(o, {b: 2, c: 3}); + o; // {a: 0, b: 2, c: 3}

        Parameters
        target: Object
        The object to modify. Existing properties will be + overwritten if they are also present in one of the objects in + var_args.
        var_args: ...Object
        The objects from which values will be copied.
        code »<T, K, V> goog.object.filter ( obj, f, opt_obj )!Object.<K, V>

        Calls a function for each element in an object/map/hash. If that call returns true, adds the element to a new object.

        Parameters
        obj: Object.<K, V>
        The object over which to iterate.
        f: function(this: T, V, ?, Object.<K, V>): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the object) and should return a boolean. If the return value is true the element is added to the result object. If it is false the element is not included.
        opt_obj: T=
        This is used as the 'this' object within f.
        Returns
        a new object in which only elements that passed the - test are present.
        code »<T, K, V> goog.object.findKey ( obj, f, opt_this )(string|undefined)

        Searches an object for an element that satisfies the given condition and + test are present.

        code »<T, K, V> goog.object.findKey ( obj, f, opt_this )(string|undefined)

        Searches an object for an element that satisfies the given condition and returns its key.

        Parameters
        obj: Object.<K, V>
        The object to search in.
        f: function(this: T, V, string, Object.<K, V>): boolean
        The function to call for every element. Takes 3 arguments (the value, the key and the object) and should return a boolean.
        opt_this: T=
        An optional "this" context for the function.
        Returns
        The key of an element for which the function - returns true or undefined if no such element is found.
        code »<T, K, V> goog.object.findValue ( obj, f, opt_this )V

        Searches an object for an element that satisfies the given condition and + returns true or undefined if no such element is found.

        code »<T, K, V> goog.object.findValue ( obj, f, opt_this )V

        Searches an object for an element that satisfies the given condition and returns its value.

        Parameters
        obj: Object.<K, V>
        The object to search in.
        f: function(this: T, V, string, Object.<K, V>): boolean
        The function to call for every element. Takes 3 arguments (the value, the key and the object) and should return a boolean.
        opt_this: T=
        An optional "this" context for the function.
        Returns
        The value of an element for which the function returns true or - undefined if no such element is found.
        code »<T, K, V> goog.object.forEach ( obj, f, opt_obj )

        Calls a function for each element in an object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object over which to iterate.
        f: function(this: T, V, ?, Object.<K, V>): ?
        The function to call + undefined if no such element is found.
        code »<T, K, V> goog.object.forEach ( obj, f, opt_obj )

        Calls a function for each element in an object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object over which to iterate.
        f: function(this: T, V, ?, Object.<K, V>): ?
        The function to call for every element. This function takes 3 arguments (the element, the - index and the object) and the return value is ignored.
        opt_obj: T=
        This is used as the 'this' object within f.
        code »<K, V, R> goog.object.get ( obj, key, opt_val )(V|R|undefined)

        Returns the value for the given key.

        Parameters
        obj: Object.<K, V>
        The object from which to get the value.
        key: string
        The key for which to get the value.
        opt_val: R=
        The value to return if no item is found for the given - key (default is undefined).
        Returns
        The value for the given key.

        Returns one key from the object map, if any exists. + index and the object) and the return value is ignored.

        opt_obj: T=
        This is used as the 'this' object within f.
        code »<K, V, R> goog.object.get ( obj, key, opt_val )(V|R|undefined)

        Returns the value for the given key.

        Parameters
        obj: Object.<K, V>
        The object from which to get the value.
        key: string
        The key for which to get the value.
        opt_val: R=
        The value to return if no item is found for the given + key (default is undefined).
        Returns
        The value for the given key.

        Returns one key from the object map, if any exists. For map literals the returned key will be the first one in most of the - browsers (a know exception is Konqueror).

        Parameters
        obj: Object
        The object to pick a key from.
        Returns
        The key or undefined if the object is empty.

        Returns one value from the object map, if any exists. + browsers (a know exception is Konqueror).

        Parameters
        obj: Object
        The object to pick a key from.
        Returns
        The key or undefined if the object is empty.

        Returns one value from the object map, if any exists. For map literals the returned value will be the first one in most of the - browsers (a know exception is Konqueror).

        Parameters
        obj: Object.<K, V>
        The object to pick a value from.
        Returns
        The value or undefined if the object is empty.

        Returns the number of key-value pairs in the object map.

        Parameters
        obj: Object
        The object for which to get the number of key-value - pairs.
        Returns
        The number of key-value pairs in the object map.

        Returns the keys of the object/map/hash.

        Parameters
        obj: Object
        The object from which to get the keys.
        Returns
        Array of property keys.
        code »goog.object.getValueByKeys ( obj, var_args )*

        Get a value from an object multiple levels deep. This is useful for + browsers (a know exception is Konqueror).

        Parameters
        obj: Object.<K, V>
        The object to pick a value from.
        Returns
        The value or undefined if the object is empty.

        Returns the number of key-value pairs in the object map.

        Parameters
        obj: Object
        The object for which to get the number of key-value + pairs.
        Returns
        The number of key-value pairs in the object map.

        Returns the keys of the object/map/hash.

        Parameters
        obj: Object
        The object from which to get the keys.
        Returns
        Array of property keys.
        code »goog.object.getValueByKeys ( obj, var_args )*

        Get a value from an object multiple levels deep. This is useful for pulling values from deeply nested objects, such as JSON responses. Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)

        Parameters
        obj: !Object
        An object to get the value from. Can be array-like.
        var_args: ...(string|number|!Array)
        A number of keys (as strings, or numbers, for array-like objects). Can also be specified as a single array of keys.
        Returns
        The resulting value. If, at any point, the value for a key - is undefined, returns undefined.
        code »<K, V> goog.object.getValues ( obj )!Array.<V>

        Returns the values of the object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object from which to get the values.
        Returns
        The values in the object/map/hash.

        Whether the object/map/hash is empty.

        Parameters
        obj: Object
        The object to test.
        Returns
        true if obj is empty.
        Parameters
        obj: !Object
        An object.
        Returns
        Whether this is an immutable view of the object.
        code »<T, K, V, R> goog.object.map ( obj, f, opt_obj )!Object.<K, R>

        For every element in an object/map/hash calls a function and inserts the + is undefined, returns undefined.

        code »<K, V> goog.object.getValues ( obj )!Array.<V>

        Returns the values of the object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object from which to get the values.
        Returns
        The values in the object/map/hash.

        Whether the object/map/hash is empty.

        Parameters
        obj: Object
        The object to test.
        Returns
        true if obj is empty.
        Parameters
        obj: !Object
        An object.
        Returns
        Whether this is an immutable view of the object.
        code »<T, K, V, R> goog.object.map ( obj, f, opt_obj )!Object.<K, R>

        For every element in an object/map/hash calls a function and inserts the result into a new object.

        Parameters
        obj: Object.<K, V>
        The object over which to iterate.
        f: function(this: T, V, ?, Object.<K, V>): R
        The function to call for every element. This function takes 3 arguments (the element, the index and the object) and should return something. The result will be inserted - into a new object.
        opt_obj: T=
        This is used as the 'this' object within f.
        Returns
        a new object with the results from f.

        Removes a key-value pair based on the key.

        Parameters
        obj: Object
        The object from which to remove the key.
        key: *
        The key to remove.
        Returns
        Whether an element was removed.
        code »<K, V> goog.object.set ( obj, key, value )

        Adds a key-value pair to the object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        value: V
        The value to add.
        code »<K, V> goog.object.setIfUndefined ( obj, key, value )V

        Adds a key-value pair to the object/map/hash if it doesn't exist yet.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        value: V
        The value to add if the key wasn't present.
        Returns
        The value of the entry at the end of the function.
        code »<T, K, V> goog.object.some ( obj, f, opt_obj )boolean

        Calls a function for each element in an object/map/hash. If any + into a new object.

        opt_obj: T=
        This is used as the 'this' object within f.Returns
        a new object with the results from f.

        Removes a key-value pair based on the key.

        Parameters
        obj: Object
        The object from which to remove the key.
        key: *
        The key to remove.
        Returns
        Whether an element was removed.
        code »<K, V> goog.object.set ( obj, key, value )

        Adds a key-value pair to the object/map/hash.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        value: V
        The value to add.
        code »<K, V> goog.object.setIfUndefined ( obj, key, value )V

        Adds a key-value pair to the object/map/hash if it doesn't exist yet.

        Parameters
        obj: Object.<K, V>
        The object to which to add the key-value pair.
        key: string
        The key to add.
        value: V
        The value to add if the key wasn't present.
        Returns
        The value of the entry at the end of the function.
        code »<T, K, V> goog.object.some ( obj, f, opt_obj )boolean

        Calls a function for each element in an object/map/hash. If any call returns true, returns true (without checking the rest). If all calls return false, returns false.

        Parameters
        obj: Object.<K, V>
        The object to check.
        f: function(this: T, V, ?, Object.<K, V>): boolean
        The function to call for every element. This function takes 3 arguments (the element, the index and the object) and should - return a boolean.
        opt_obj: T=
        This is used as the 'this' object within f.
        Returns
        true if any element passes the test.

        Returns a new object in which all the keys and values are interchanged + return a boolean.

        opt_obj: T=
        This is used as the 'this' object within f.Returns
        true if any element passes the test.

        Returns a new object in which all the keys and values are interchanged (keys become values and values become keys). If multiple keys map to the - same value, the chosen transposed value is implementation-dependent.

        Parameters
        obj: Object
        The object to transpose.
        Returns
        The transposed object.

        Clones a value. The input may be an Object, Array, or basic type. Objects and + same value, the chosen transposed value is implementation-dependent.

        Parameters
        obj: Object
        The object to transpose.
        Returns
        The transposed object.

        Clones a value. The input may be an Object, Array, or basic type. Objects and arrays will be cloned recursively. WARNINGS: @@ -72,4 +74,4 @@ that refer to themselves will cause infinite recursion. goog.object.unsafeClone is unaware of unique identifiers, and - copies UIDs created by getUid into cloned results.

        Parameters
        obj: *
        The value to clone.
        Returns
        A clone of the input value.

        Global Properties

        The names of the fields that are defined on Object.prototype.

        \ No newline at end of file + copies UIDs created by getUid into cloned results.
        Parameters
        obj: *
        The value to clone.
        Returns
        A clone of the input value.

        Global Properties

        The names of the fields that are defined on Object.prototype.

        \ No newline at end of file diff --git a/docs/namespace_goog_reflect.html b/docs/namespace_goog_reflect.html new file mode 100644 index 0000000..68871f5 --- /dev/null +++ b/docs/namespace_goog_reflect.html @@ -0,0 +1,7 @@ +goog.reflect

        Namespace goog.reflect

        code »
        Show:

        Global Functions

        Check if a property can be accessed without throwing an exception.

        Parameters
        obj: Object
        The owner of the property.
        prop: string
        The property name.
        Returns
        Whether the property is accessible. Will also return true + if obj is null.
        code »goog.reflect.object ( type, object )Object

        Syntax for object literal casts.

        Parameters
        type: !Function
        Type to cast to.
        object: Object
        Object literal to cast.
        Returns
        The object literal.

        To assert to the compiler that an operation is needed when it would + otherwise be stripped. For example: + + // Force a layout + goog.reflect.sinkValue(dialog.offsetHeight); +

        \ No newline at end of file diff --git a/docs/namespace_goog_string.html b/docs/namespace_goog_string.html index e4d7474..e4ace34 100644 --- a/docs/namespace_goog_string.html +++ b/docs/namespace_goog_string.html @@ -1,4 +1,4 @@ -goog.string

        Namespace goog.string

        code »

        Enumerations

        goog.string.Unicode
        Common Unicode string characters.
        Show:

        Global Functions

        Concatenates string expressions. This is useful +goog.string

        Namespace goog.string

        code »

        Enumerations

        goog.string.Unicode
        Common Unicode string characters.
        Show:

        Global Functions

        Concatenates string expressions. This is useful since some browsers are very inefficient when it comes to using plus to concat strings. Be careful when using null and undefined here since these will not be included in the result. If you need to represent these @@ -7,39 +7,43 @@

        buildString('a', 'b', 'c', 'd') -> 'abcd'
          buildString(null, undefined) -> ''
          
        Parameters
        var_args: ...*
        A list of strings to concatenate. If not a string, - it will be casted to one.
        Returns
        The concatenation of var_args.

        Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.

        Parameters
        str: string
        The string to in which to canonicalize newlines.
        Returns
        str A copy of {@code} with canonicalized newlines.

        A string comparator that ignores case. + it will be casted to one.Returns

        The concatenation of var_args.

        Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.

        Parameters
        str: string
        The string to in which to canonicalize newlines.
        Returns
        str A copy of {@code} with canonicalized newlines.

        A string comparator that ignores case. -1 = str1 less than str2 0 = str1 equals str2 - 1 = str1 greater than str2

        Parameters
        str1: string
        The string to compare.
        str2: string
        The string to compare str1 to.
        Returns
        The comparator result, as described above.

        Case-insensitive suffix-checker.

        Parameters
        str: string
        The string to check.
        suffix: string
        A string to look for at the end of str.
        Returns
        True if str ends with suffix (ignoring - case).

        Case-insensitive equality checker.

        Parameters
        str1: string
        First string to check.
        str2: string
        Second string to check.
        Returns
        True if str1 and str2 are the same string, - ignoring case.

        Case-insensitive prefix-checker.

        Parameters
        str: string
        The string to check.
        prefix: string
        A string to look for at the end of str.
        Returns
        True if str begins with prefix (ignoring - case).

        Removes the breaking spaces from the left and right of the string and + 1 = str1 greater than str2

        Parameters
        str1: string
        The string to compare.
        str2: string
        The string to compare str1 to.
        Returns
        The comparator result, as described above.

        Determines whether a string contains a substring, ignoring case.

        Parameters
        str: string
        The string to search.
        subString: string
        The substring to search for.
        Returns
        Whether str contains subString.

        Case-insensitive suffix-checker.

        Parameters
        str: string
        The string to check.
        suffix: string
        A string to look for at the end of str.
        Returns
        True if str ends with suffix (ignoring + case).

        Case-insensitive equality checker.

        Parameters
        str1: string
        First string to check.
        str2: string
        Second string to check.
        Returns
        True if str1 and str2 are the same string, + ignoring case.

        Case-insensitive prefix-checker.

        Parameters
        str: string
        The string to check.
        prefix: string
        A string to look for at the end of str.
        Returns
        True if str begins with prefix (ignoring + case).

        Removes the breaking spaces from the left and right of the string and collapses the sequences of breaking spaces in the middle into single spaces. - The original and the result strings render the same way in HTML.

        Parameters
        str: string
        A string in which to collapse spaces.
        Returns
        Copy of the string with normalized breaking spaces.

        Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines - and tabs) to a single space, and strips leading and trailing whitespace.

        Parameters
        str: string
        Input string.
        Returns
        A copy of str with collapsed whitespace.

        Compares elements of a version number.

        Parameters
        left: (string|number|boolean)
        An element from a version number.
        right: (string|number|boolean)
        An element from a version number.
        Returns
        1 if left is higher. + The original and the result strings render the same way in HTML.
        Parameters
        str: string
        A string in which to collapse spaces.
        Returns
        Copy of the string with normalized breaking spaces.

        Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines + and tabs) to a single space, and strips leading and trailing whitespace.

        Parameters
        str: string
        Input string.
        Returns
        A copy of str with collapsed whitespace.

        Compares elements of a version number.

        Parameters
        left: (string|number|boolean)
        An element from a version number.
        right: (string|number|boolean)
        An element from a version number.
        Returns
        1 if left is higher. 0 if arguments are equal. - -1 if right is higher.
        code »goog.string.compareVersions ( version1, version2 )number

        Compares two version numbers.

        Parameters
        version1: (string|number)
        Version of first item.
        version2: (string|number)
        Version of second item.
        Returns
        1 if version1 is higher. + -1 if right is higher.
        code »goog.string.compareVersions ( version1, version2 )number

        Compares two version numbers.

        Parameters
        version1: (string|number)
        Version of first item.
        version2: (string|number)
        Version of second item.
        Returns
        1 if version1 is higher. 0 if arguments are equal. - -1 if version2 is higher.

        Checks whether a string contains a given substring.

        Parameters
        s: string
        The string to test.
        ss: string
        The substring to test for.
        Returns
        True if s contains ss.

        Returns the non-overlapping occurrences of ss in s. - If either s or ss evalutes to false, then returns zero.

        Parameters
        s: string
        The string to look in.
        ss: string
        The string to look for.
        Returns
        Number of occurrences of ss in s.

        Generates and returns a string which is unique in the current document. - This is useful, for example, to create unique IDs for DOM elements.

        Returns
        A unique id.

        Fast suffix-checker.

        Parameters
        str: string
        The string to check.
        suffix: string
        A string to look for at the end of str.
        Returns
        True if str ends with suffix.

        Takes a character and returns the escaped string for that character. For - example escapeChar(String.fromCharCode(15)) -> "\\x0E".

        Parameters
        c: string
        The character to escape.
        Returns
        An escaped string representing c.

        Takes a string and returns the escaped string for that character.

        Parameters
        str: string
        The string to escape.
        Returns
        An escaped string representing str.

        Returns a string with at least 64-bits of randomness. + -1 if version2 is higher.

        code »goog.string.contains ( str, subString )boolean

        Determines whether a string contains a substring.

        Parameters
        str: string
        The string to search.
        subString: string
        The substring to search for.
        Returns
        Whether str contains subString.

        Returns the non-overlapping occurrences of ss in s. + If either s or ss evalutes to false, then returns zero.

        Parameters
        s: string
        The string to look in.
        ss: string
        The string to look for.
        Returns
        Number of occurrences of ss in s.

        Generates and returns a string which is unique in the current document. + This is useful, for example, to create unique IDs for DOM elements.

        Returns
        A unique id.

        Fast suffix-checker.

        Parameters
        str: string
        The string to check.
        suffix: string
        A string to look for at the end of str.
        Returns
        True if str ends with suffix.

        Takes a character and returns the escaped string for that character. For + example escapeChar(String.fromCharCode(15)) -> "\\x0E".

        Parameters
        c: string
        The character to escape.
        Returns
        An escaped string representing c.

        Takes a string and returns the escaped string for that character.

        Parameters
        str: string
        The string to escape.
        Returns
        An escaped string representing str.

        Returns a string with at least 64-bits of randomness. Doesn't trust Javascript's random function entirely. Uses a combination of random and current timestamp, and then encodes the string in base-36 to - make it shorter.

        Returns
        A random string, e.g. sn1s7vb4gcic.

        String hash function similar to java.lang.String.hashCode(). + make it shorter.

        Returns
        A random string, e.g. sn1s7vb4gcic.

        String hash function similar to java.lang.String.hashCode(). The hash code for a string is computed as s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], where s[i] is the ith character of the string and n is the length of the string. We mod the result to make it between 0 (inclusive) and 2^32 (exclusive).

        Parameters
        str: string
        A string.
        Returns
        Hash value for str, between 0 (inclusive) and 2^32 - (exclusive). The empty string returns 0.
        code »goog.string.htmlEscape ( str, opt_isLikelyToContainHtmlChars )string

        Escape double quote '"' characters in addition to '&', '<', and '>' so that a - string can be included in an HTML tag attribute value within double quotes. + (exclusive). The empty string returns 0.

        code »goog.string.htmlEscape ( str, opt_isLikelyToContainHtmlChars )string

        Escapes double quote '"' and single quote '\'' characters in addition to + '&', '<', and '>' so that a string can be included in an HTML tag attribute + value within double or single quotes. It should be noted that > doesn't need to be escaped for the HTML or XML to be valid, but it has been decided to escape it for consistency with other implementations. + With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the + lowercase letter "e". + NOTE(user): HtmlEscape is often called during the generation of large blocks of HTML. Using statics for the regular expressions and strings is an optimization @@ -66,17 +70,17 @@ application grows the difference between the various methods would increase.

        Parameters
        str: string
        string to be escaped.
        opt_isLikelyToContainHtmlChars: boolean=
        Don't perform a check to see if the character needs replacing - use this option if you expect each of the characters to appear often. Leave false if you expect few html - characters to occur in your strings, such as if you are escaping HTML.
        Returns
        An escaped copy of str.

        Checks if a string contains all letters.

        Parameters
        str: string
        string to check.
        Returns
        True if str consists entirely of letters.

        Checks if a string contains only numbers or letters.

        Parameters
        str: string
        string to check.
        Returns
        True if str is alphanumeric.

        Checks if a string is all breaking whitespace.

        Parameters
        str: string
        The string to check.
        Returns
        Whether the string is all breaking whitespace.

        Checks if a string is empty or contains only whitespaces.

        Parameters
        str: string
        The string to check.
        Returns
        True if str is empty or whitespace only.

        Checks if a string is null, undefined, empty or contains only whitespaces.

        Parameters
        str: *
        The string to check.
        Returns
        True ifstr is null, undefined, empty, or - whitespace only.

        Returns whether the given string is lower camel case (e.g. "isFooBar"). + characters to occur in your strings, such as if you are escaping HTML.Returns

        An escaped copy of str.

        Checks if a string contains all letters.

        Parameters
        str: string
        string to check.
        Returns
        True if str consists entirely of letters.

        Checks if a string contains only numbers or letters.

        Parameters
        str: string
        string to check.
        Returns
        True if str is alphanumeric.

        Checks if a string is all breaking whitespace.

        Parameters
        str: string
        The string to check.
        Returns
        Whether the string is all breaking whitespace.

        Checks if a string is empty or contains only whitespaces.

        Parameters
        str: string
        The string to check.
        Returns
        True if str is empty or whitespace only.

        Checks if a string is null, undefined, empty or contains only whitespaces.

        Parameters
        str: *
        The string to check.
        Returns
        True ifstr is null, undefined, empty, or + whitespace only.

        Returns whether the given string is lower camel case (e.g. "isFooBar"). - Note that this assumes the string is entirely letters.

        Parameters
        str: string
        String to test.
        Returns
        Whether the string is lower camel case.

        Checks if a string contains only numbers.

        Parameters
        str: *
        string to check. If not a string, it will be - casted to one.
        Returns
        True if str is numeric.

        Checks if a character is a space character.

        Parameters
        ch: string
        Character to check.
        Returns
        True if {code ch} is a space.

        Checks if a character is a valid unicode character.

        Parameters
        ch: string
        Character to check.
        Returns
        True if {code ch} is a valid unicode character.

        Returns whether the given string is upper camel case (e.g. "FooBarBaz"). + Note that this assumes the string is entirely letters.

        Parameters
        str: string
        String to test.
        Returns
        Whether the string is lower camel case.

        Checks if a string contains only numbers.

        Parameters
        str: *
        string to check. If not a string, it will be + casted to one.
        Returns
        True if str is numeric.

        Checks if a character is a space character.

        Parameters
        ch: string
        Character to check.
        Returns
        True if {code ch} is a space.

        Checks if a character is a valid unicode character.

        Parameters
        ch: string
        Character to check.
        Returns
        True if {code ch} is a valid unicode character.

        Returns whether the given string is upper camel case (e.g. "FooBarBaz"). - Note that this assumes the string is entirely letters.

        Parameters
        str: string
        String to test.
        Returns
        Whether the string is upper camel case.

        Returns a string representation of the given object, with - null and undefined being returned as the empty string.

        Parameters
        obj: *
        The object to convert.
        Returns
        A string representation of the obj.

        Converts \n to
        s or
        s.

        Parameters
        str: string
        The string in which to convert newlines.
        opt_xml: boolean=
        Whether to use XML compatible tags.
        Returns
        A copy of str with converted newlines.

        Normalizes spaces in a string, replacing all consecutive spaces and tabs + Note that this assumes the string is entirely letters.

        Parameters
        str: string
        String to test.
        Returns
        Whether the string is upper camel case.

        Returns a string representation of the given object, with + null and undefined being returned as the empty string.

        Parameters
        obj: *
        The object to convert.
        Returns
        A string representation of the obj.

        Converts \n to
        s or
        s.

        Parameters
        str: string
        The string in which to convert newlines.
        opt_xml: boolean=
        Whether to use XML compatible tags.
        Returns
        A copy of str with converted newlines.

        Normalizes spaces in a string, replacing all consecutive spaces and tabs with a single space. Replaces non-breaking space with a space.

        Parameters
        str: string
        The string in which to normalize spaces.
        Returns
        A copy of str with all consecutive spaces and tabs - replaced with a single space.

        Normalizes whitespace in a string, replacing all whitespace chars with - a space.

        Parameters
        str: string
        The string in which to normalize whitespace.
        Returns
        A copy of str with all whitespace normalized.

        String comparison function that handles numbers in a way humans might expect. + replaced with a single space.

        Normalizes whitespace in a string, replacing all whitespace chars with + a space.

        Parameters
        str: string
        The string in which to normalize whitespace.
        Returns
        A copy of str with all whitespace normalized.

        String comparison function that handles numbers in a way humans might expect. Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The comparison is mostly case-insensitive, though strings that are identical except for case are sorted with the upper-case strings before lower-case. @@ -85,12 +89,12 @@ the default or the case-insensitive compare. It should not be used in time-critical code, but should be fast enough to sort several hundred short strings (like filenames) with a reasonable delay.

        Parameters
        str1: string
        The string to compare in a numerically sensitive way.
        str2: string
        The string to compare str1 to.
        Returns
        less than 0 if str1 < str2, 0 if str1 == str2, greater than - 0 if str1 > str2.
        code »goog.string.padNumber ( num, length, opt_precision )string

        Pads number to given length and optionally rounds it to a given precision. + 0 if str1 > str2.

        code »goog.string.padNumber ( num, length, opt_precision )string

        Pads number to given length and optionally rounds it to a given precision. For example:

        padNumber(1.25, 2, 3) -> '01.250'
          padNumber(1.25, 2) -> '01.25'
          padNumber(1.25, 2, 1) -> '01.3'
        - padNumber(1.25, 0) -> '1.25'
        Parameters
        num: number
        The number to pad.
        length: number
        The desired length.
        opt_precision: number=
        The desired precision.
        Returns
        num as a string with the given options.

        Parse a string in decimal or hexidecimal ('0xFFFF') form. + padNumber(1.25, 0) -> '1.25'

        Parameters
        num: number
        The number to pad.
        length: number
        The desired length.
        opt_precision: number=
        The desired precision.
        Returns
        num as a string with the given options.

        Parse a string in decimal or hexidecimal ('0xFFFF') form. To parse a particular radix, please use parseInt(string, radix) directly. See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt @@ -101,14 +105,15 @@ this behavior. ES5 forbids it. This function emulates the ES5 behavior. For more information, see Mozilla JS Reference: http://goo.gl/8RiFj

        Parameters
        value: (string|number|null|undefined)
        The value to be parsed.
        Returns
        The number, parsed. If the string failed to parse, this - will be NaN.

        Encloses a string in double quotes and escapes characters so that the - string is a valid JS string.

        Parameters
        s: string
        The string to quote.
        Returns
        A copy of s surrounded by double quotes.

        Escapes characters in the string that are not safe to use in a RegExp.

        Parameters
        s: *
        The string to escape. If not a string, it will be casted - to one.
        Returns
        A RegExp safe, escaped copy of s.

        Removes the first occurrence of a substring from a string.

        Parameters
        s: string
        The base string from which to remove.
        ss: string
        The string to remove.
        Returns
        A copy of s with ss removed or the full - string if nothing is removed.

        Removes all occurrences of a substring from a string.

        Parameters
        s: string
        The base string from which to remove.
        ss: string
        The string to remove.
        Returns
        A copy of s with ss removed or the full - string if nothing is removed.
        code »goog.string.removeAt ( s, index, stringLength )string

        Removes a substring of a specified length at a specific + will be NaN.

        Preserve spaces that would be otherwise collapsed in HTML by replacing them + with non-breaking space Unicode characters.

        Parameters
        str: string
        The string in which to preserve whitespace.
        Returns
        A copy of str with preserved whitespace.

        Encloses a string in double quotes and escapes characters so that the + string is a valid JS string.

        Parameters
        s: string
        The string to quote.
        Returns
        A copy of s surrounded by double quotes.

        Escapes characters in the string that are not safe to use in a RegExp.

        Parameters
        s: *
        The string to escape. If not a string, it will be casted + to one.
        Returns
        A RegExp safe, escaped copy of s.

        Removes the first occurrence of a substring from a string.

        Parameters
        s: string
        The base string from which to remove.
        ss: string
        The string to remove.
        Returns
        A copy of s with ss removed or the full + string if nothing is removed.

        Removes all occurrences of a substring from a string.

        Parameters
        s: string
        The base string from which to remove.
        ss: string
        The string to remove.
        Returns
        A copy of s with ss removed or the full + string if nothing is removed.
        code »goog.string.removeAt ( s, index, stringLength )string

        Removes a substring of a specified length at a specific index in a string.

        Parameters
        s: string
        The base string from which to remove.
        index: number
        The index at which to remove the substring.
        stringLength: number
        The length of the substring to remove.
        Returns
        A copy of s with the substring removed or the full - string if nothing is removed or the input is invalid.
        code »goog.string.repeat ( string, length )string

        Repeats a string n times.

        Parameters
        string: string
        The string to repeat.
        length: number
        The number of times to repeat.
        Returns
        A string containing length repetitions of - string.
        code »goog.string.splitLimit ( str, separator, limit )!Array.<string>

        Splits a string on a separator a limited number of times. + string if nothing is removed or the input is invalid.

        code »goog.string.repeat ( string, length )string

        Repeats a string n times.

        Parameters
        string: string
        The string to repeat.
        length: number
        The number of times to repeat.
        Returns
        A string containing length repetitions of + string.
        code »goog.string.splitLimit ( str, separator, limit )!Array.<string>

        Splits a string on a separator a limited number of times. This implementation is more similar to Python or Java, where the limit parameter specifies the maximum number of splits rather than truncating @@ -118,8 +123,8 @@ See JavaDoc: http://goo.gl/F2AsY See Mozilla reference: http://goo.gl/dZdZs

        Parameters
        str: string
        String to split.
        separator: string
        The separator.
        limit: number
        The limit to the number of splits. The resulting array will have a maximum length of limit+1. Negative numbers are the same - as zero.
        Returns
        The string, split.

        Fast prefix-checker.

        Parameters
        str: string
        The string to check.
        prefix: string
        A string to look for at the start of str.
        Returns
        True if str begins with prefix.

        Takes a string and replaces newlines with a space. Multiple lines are - replaced with a single space.

        Parameters
        str: string
        The string from which to strip newlines.
        Returns
        A copy of str stripped of newlines.
        code »goog.string.stripQuotes ( str, quoteChars )string

        Strip quote characters around a string. The second argument is a string of + as zero.Returns

        The string, split.

        Fast prefix-checker.

        Parameters
        str: string
        The string to check.
        prefix: string
        A string to look for at the start of str.
        Returns
        True if str begins with prefix.

        Takes a string and replaces newlines with a space. Multiple lines are + replaced with a single space.

        Parameters
        str: string
        The string from which to strip newlines.
        Returns
        A copy of str stripped of newlines.
        code »goog.string.stripQuotes ( str, quoteChars )string

        Strip quote characters around a string. The second argument is a string of characters to treat as quotes. This can be a single character or a string of multiple character and in that case each of those are treated as possible quote characters. For example: @@ -127,20 +132,18 @@

          goog.string.stripQuotes('"abc"', '"`') --> 'abc'
          goog.string.stripQuotes('`abc`', '"`') --> 'abc'
        - 
        Parameters
        str: string
        The string to strip.
        quoteChars: string
        The quote characters to strip.
        Returns
        A copy of str without the quotes.
        code »goog.string.subs ( str, var_args )string

        Does simple python-style string substitution. +

        Parameters
        str: string
        The string to strip.
        quoteChars: string
        The quote characters to strip.
        Returns
        A copy of str without the quotes.
        code »goog.string.subs ( str, var_args )string

        Does simple python-style string substitution. subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".

        Parameters
        str: string
        The string containing the pattern.
        var_args: ...*
        The items to substitute into the pattern.
        Returns
        A copy of str in which each occurrence of - %s has been replaced an argument from var_args.

        Converts a string from selector-case to camelCase (e.g. from + %s has been replaced an argument from var_args.

        Converts a string from selector-case to camelCase (e.g. from "multi-part-string" to "multiPartString"), useful for converting - CSS selectors and HTML dataset keys to their equivalent JS properties.

        Parameters
        str: string
        The string in selector-case form.
        Returns
        The string in camelCase form.

        Takes a string and creates a map (Object) in which the keys are the - characters in the string. The value for the key is set to true. You can - then use goog.object.map or goog.array.map to change the values.

        Parameters
        s: string
        The string to build the map from.
        Returns
        The map of characters used.

        Converts the supplied string to a number, which may be Ininity or NaN. + CSS selectors and HTML dataset keys to their equivalent JS properties.

        Parameters
        str: string
        The string in selector-case form.
        Returns
        The string in camelCase form.

        Converts the supplied string to a number, which may be Infinity or NaN. This function strips whitespace: (toNumber(' 123') === 123) This function accepts scientific notation: (toNumber('1e1') === 10) This is better than Javascript's built-in conversions because, sadly: - (Number(' ') === 0) and (parseFloat('123a') === 123)

        Parameters
        str: string
        The string to convert.
        Returns
        The number the supplied string represents, or NaN.

        Converts a string from camelCase to selector-case (e.g. from + (Number(' ') === 0) and (parseFloat('123a') === 123)

        Parameters
        str: string
        The string to convert.
        Returns
        The number the supplied string represents, or NaN.

        Converts a string from camelCase to selector-case (e.g. from "multiPartString" to "multi-part-string"), useful for converting JS - style and dataset properties to equivalent CSS selectors and HTML keys.

        Parameters
        str: string
        The string in camelCase form.
        Returns
        The string in selector-case form.
        code »goog.string.toTitleCase ( str, opt_delimiters )string

        Converts a string into TitleCase. First character of the string is always + style and dataset properties to equivalent CSS selectors and HTML keys.

        Parameters
        str: string
        The string in camelCase form.
        Returns
        The string in selector-case form.
        code »goog.string.toTitleCase ( str, opt_delimiters )string

        Converts a string into TitleCase. First character of the string is always capitalized in addition to the first letter of every subsequent word. Words are delimited by one or more whitespaces by default. Custom delimiters can optionally be specified to replace the default, which doesn't preserve @@ -164,18 +167,20 @@ goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'

        Parameters
        str: string
        String value in camelCase form.
        opt_delimiters: string=
        Custom delimiter character set used to distinguish words in the string value. Each character represents a single delimiter. When provided, default whitespace delimiter is - overridden and must be explicitly included if needed.
        Returns
        String value in TitleCase form.

        Trims white spaces to the left and right of a string.

        Parameters
        str: string
        The string to trim.
        Returns
        A trimmed copy of str.

        Trims whitespaces at the left end of a string.

        Parameters
        str: string
        The string to left trim.
        Returns
        A trimmed copy of str.

        Trims whitespaces at the right end of a string.

        Parameters
        str: string
        The string to right trim.
        Returns
        A trimmed copy of str.
        code »goog.string.truncate ( str, chars, opt_protectEscapedCharacters )string

        Truncates a string to a certain length and adds '...' if necessary. The + overridden and must be explicitly included if needed.Returns

        String value in TitleCase form.

        Trims white spaces to the left and right of a string.

        Parameters
        str: string
        The string to trim.
        Returns
        A trimmed copy of str.

        Trims whitespaces at the left end of a string.

        Parameters
        str: string
        The string to left trim.
        Returns
        A trimmed copy of str.

        Trims whitespaces at the right end of a string.

        Parameters
        str: string
        The string to right trim.
        Returns
        A trimmed copy of str.
        code »goog.string.truncate ( str, chars, opt_protectEscapedCharacters )string

        Truncates a string to a certain length and adds '...' if necessary. The length also accounts for the ellipsis, so a maximum length of 10 and a string 'Hello World!' produces 'Hello W...'.

        Parameters
        str: string
        The string to truncate.
        chars: number
        Max number of characters.
        opt_protectEscapedCharacters: boolean=
        Whether to protect escaped - characters from being cut off in the middle.
        Returns
        The truncated str string.
        code »goog.string.truncateMiddle ( str, chars, opt_protectEscapedCharacters, opt_trailingChars )string

        Truncate a string in the middle, adding "..." if necessary, + characters from being cut off in the middle.Returns

        The truncated str string.
        code »goog.string.truncateMiddle ( str, chars, opt_protectEscapedCharacters, opt_trailingChars )string

        Truncate a string in the middle, adding "..." if necessary, and favoring the beginning of the string.

        Parameters
        str: string
        The string to truncate the middle of.
        chars: number
        Max number of characters.
        opt_protectEscapedCharacters: boolean=
        Whether to protect escaped characters from being cutoff in the middle.
        opt_trailingChars: number=
        Optional number of trailing characters to leave at the end of the string, instead of truncating as close to the - middle as possible.
        Returns
        A truncated copy of str.

        Unescapes an HTML string.

        Parameters
        str: string
        The string to unescape.
        Returns
        An unescaped copy of str.

        Unescapes an HTML string using a DOM to resolve non-XML, non-numeric - entities. This function is XSS-safe and whitespace-preserving.

        Parameters
        str: string
        The string to unescape.
        Returns
        The unescaped str string.

        Unescapes XML entities.

        Parameters
        str: string
        The string to unescape.
        Returns
        An unescaped copy of str.

        URL-decodes the string. We need to specially handle '+'s because - the javascript library doesn't convert them to spaces.

        Parameters
        str: string
        The string to url decode.
        Returns
        The decoded str.

        URL-encodes a string

        Parameters
        str: *
        The string to url-encode.
        Returns
        An encoded copy of str that is safe for urls. + middle as possible.
        Returns
        A truncated copy of str.

        Unescapes an HTML string.

        Parameters
        str: string
        The string to unescape.
        Returns
        An unescaped copy of str.

        Unescapes an HTML string using a DOM to resolve non-XML, non-numeric + entities. This function is XSS-safe and whitespace-preserving.

        Parameters
        str: string
        The string to unescape.
        opt_document: Document=
        An optional document to use for creating + elements. If this is not specified then the default window.document + will be used.
        Returns
        The unescaped str string.

        Unescapes a HTML string using the provided document.

        Parameters
        str: string
        The string to unescape.
        document: !Document
        A document to use in escaping the string.
        Returns
        An unescaped copy of str.

        Unescapes XML entities.

        Parameters
        str: string
        The string to unescape.
        Returns
        An unescaped copy of str.

        URL-decodes the string. We need to specially handle '+'s because + the javascript library doesn't convert them to spaces.

        Parameters
        str: string
        The string to url decode.
        Returns
        The decoded str.

        URL-encodes a string

        Parameters
        str: *
        The string to url-encode.
        Returns
        An encoded copy of str that is safe for urls. Note that '#', ':', and other characters used to delimit portions - of URLs *will* be encoded.

        Do escaping of whitespace to preserve spatial formatting. We use character - entity #160 to make it safer for xml.

        Parameters
        str: string
        The string in which to escape whitespace.
        opt_xml: boolean=
        Whether to use XML compatible tags.
        Returns
        An escaped copy of str.

        Global Properties

        Maximum value of #goog.string.hashCode, exclusive. 2^32.

        Regular expression that matches an HTML entity. - See also HTML5: Tokenization / Tokenizing character references.

        Regular expression that matches any character that needs to be escaped.

        Regular expression that matches an ampersand, for use in escaping.

        Regular expression that matches a greater than sign, for use in escaping.

        Character mappings used internally for goog.string.escapeChar.

        Regular expression that matches a less than sign, for use in escaping.

        Regular expression used for splitting a string into substrings of fractional - numbers, integers, and non-numeric characters.

        Regular expression that matches a double quote, for use in escaping.

        Special chars that need to be escaped for goog.string.quote.

        The most recent unique ID. |0 is equivalent to Math.floor in this case.

        \ No newline at end of file + of URLs *will* be encoded.

        Do escaping of whitespace to preserve spatial formatting. We use character + entity #160 to make it safer for xml.

        Parameters
        str: string
        The string in which to escape whitespace.
        opt_xml: boolean=
        Whether to use XML compatible tags.
        Returns
        An escaped copy of str.

        Global Properties

        Regular expression that matches any character that needs to be escaped.

        Regular expression that matches an ampersand, for use in escaping.

        Regular expression that matches a lowercase letter "e", for use in escaping.

        Regular expression that matches a greater than sign, for use in escaping.

        Maximum value of #goog.string.hashCode, exclusive. 2^32.

        Regular expression that matches an HTML entity. + See also HTML5: Tokenization / Tokenizing character references.

        Regular expression that matches a less than sign, for use in escaping.

        Regular expression that matches null character, for use in escaping.

        Regular expression that matches a double quote, for use in escaping.

        Regular expression that matches a single quote, for use in escaping.

        Character mappings used internally for goog.string.escapeChar.

        Regular expression used for splitting a string into substrings of fractional + numbers, integers, and non-numeric characters.

        Special chars that need to be escaped for goog.string.quote.

        The most recent unique ID. |0 is equivalent to Math.floor in this case.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_structs.html b/docs/namespace_goog_structs.html index 95e655e..3b83e2b 100644 --- a/docs/namespace_goog_structs.html +++ b/docs/namespace_goog_structs.html @@ -1,11 +1,11 @@ -goog.structs

        Namespace goog.structs

        code »

        Classes

        goog.structs.Map
        Class for Hash Map datastructure.
        Show:

        Global Functions

        Removes all the elements from the collection.

        Parameters
        col: Object
        The collection-like object.

        Whether the collection contains the given value. This is O(n) and uses - equals (==) to test the existence.

        Parameters
        col: Object
        The collection-like object.
        val: *
        The value to check for.
        Returns
        True if the map contains the value.
        code »<T, S> goog.structs.every ( col, f, opt_obj )boolean

        Calls f for each value in a collection. If all calls return true this return +goog.structs

        Namespace goog.structs

        code »

        Interfaces

        goog.structs.Collection
        An interface for a collection of values.

        Classes

        goog.structs.Map
        Class for Hash Map datastructure.
        goog.structs.Set
        A set that can contain both primitives and objects.
        Show:

        Global Functions

        Removes all the elements from the collection.

        Parameters
        col: Object
        The collection-like object.

        Whether the collection contains the given value. This is O(n) and uses + equals (==) to test the existence.

        Parameters
        col: Object
        The collection-like object.
        val: *
        The value to check for.
        Returns
        True if the map contains the value.
        code »<T, S> goog.structs.every ( col, f, opt_obj )boolean

        Calls f for each value in a collection. If all calls return true this return true this returns true. If any returns false this returns false at this point and does not continue to check the remaining values.

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): boolean
        The function to call for every value. This function takes 3 arguments (the value, the key or undefined if the collection has no notion of keys, and the collection) and should return a boolean.
        opt_obj: T=
        The object to be used as the value of 'this' - within f.
        Returns
        True if all key-value pairs pass the test.
        code »<T, S> goog.structs.filter ( col, f, opt_obj )(!Object|!Array)

        Calls a function for every value in the collection. When a call returns true, + within f.Returns

        True if all key-value pairs pass the test.
        code »<T, S> goog.structs.filter ( col, f, opt_obj )(!Object|!Array)

        Calls a function for every value in the collection. When a call returns true, adds the value to a new collection (Array is returned by default).

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): boolean
        The function to call for every value. This function takes 3 arguments (the value, the key or undefined if the collection has no @@ -14,22 +14,25 @@ is false the value is not included.
        opt_obj: T=
        The object to be used as the value of 'this' within f.
        Returns
        A new collection where the passed values are present. If col is a key-less collection an array is returned. If col - has keys and values a plain old JS object is returned.
        code »<T, S> goog.structs.forEach ( col, f, opt_obj )

        Calls a function for each value in a collection. The function takes - three arguments; the value, the key and the collection.

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): ?
        The function to call for every value. + has keys and values a plain old JS object is returned.
        code »<T, S> goog.structs.forEach ( col, f, opt_obj )

        Calls a function for each value in a collection. The function takes + three arguments; the value, the key and the collection. + + NOTE: This will be deprecated soon! Please use a more specific method if + possible, e.g. goog.array.forEach, goog.object.forEach, etc.

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): ?
        The function to call for every value. This function takes 3 arguments (the value, the key or undefined if the collection has no notion of keys, and the collection) and the return value is irrelevant.
        opt_obj: T=
        The object to be used as the value of 'this' within f.

        Returns the number of values in the collection-like object.

        Parameters
        col: Object
        The collection-like object.
        Returns
        The number of values in the collection-like object.

        Returns the keys of the collection. Some collections have no notion of - keys/indexes and this function will return undefined in those cases.

        Parameters
        col: Object
        The collection-like object.
        Returns
        The keys in the collection.

        Returns the values of the collection-like object.

        Parameters
        col: Object
        The collection-like object.
        Returns
        The values in the collection-like object.

        Whether the collection is empty.

        Parameters
        col: Object
        The collection-like object.
        Returns
        True if empty.
        code »<T, S, V> goog.structs.map ( col, f, opt_obj )(!Object.<V>|!Array.<V>)

        Calls a function for every value in the collection and adds the result into a + keys/indexes and this function will return undefined in those cases.

        Parameters
        col: Object
        The collection-like object.
        Returns
        The keys in the collection.

        Returns the values of the collection-like object.

        Parameters
        col: Object
        The collection-like object.
        Returns
        The values in the collection-like object.

        Whether the collection is empty.

        Parameters
        col: Object
        The collection-like object.
        Returns
        True if empty.
        code »<T, S, V> goog.structs.map ( col, f, opt_obj )(!Object.<V>|!Array.<V>)

        Calls a function for every value in the collection and adds the result into a new collection (defaults to creating a new Array).

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): V
        The function to call for every value. This function takes 3 arguments (the value, the key or undefined if the collection has no notion of keys, and the collection) and should return something. The result will be used as the value in the new collection.
        opt_obj: T=
        The object to be used as the value of 'this' within f.
        Returns
        A new collection with the new values. If col is a key-less collection an array is returned. If col has keys and - values a plain old JS object is returned.
        code »<T, S> goog.structs.some ( col, f, opt_obj )boolean

        Calls f for each value in a collection. If any call returns true this returns + values a plain old JS object is returned.

        code »<T, S> goog.structs.some ( col, f, opt_obj )boolean

        Calls f for each value in a collection. If any call returns true this returns true (without checking the rest). If all returns false this returns false.

        Parameters
        col: S
        The collection-like object.
        f: function(this: T, ?, ?, S): boolean
        The function to call for every value. This function takes 3 arguments (the value, the key or undefined if the collection has no notion of keys, and the collection) and should return a boolean.
        opt_obj: T=
        The object to be used as the value of 'this' - within f.
        Returns
        True if any value passes the test.
        \ No newline at end of file + within f.Returns
        True if any value passes the test.
        \ No newline at end of file diff --git a/docs/namespace_goog_style.html b/docs/namespace_goog_style.html new file mode 100644 index 0000000..0c25e81 --- /dev/null +++ b/docs/namespace_goog_style.html @@ -0,0 +1,211 @@ +goog.style

        Namespace goog.style

        code »
        Show:

        Global Functions

        Clears the background image of an element in a browser independent manner.

        Parameters
        el: Element
        The element to clear background image for.

        Call fn on element such that element's dimensions are + accurate when it's passed to fn.

        Parameters
        fn: function(!Element): T
        Function to call with element as + an argument after temporarily changing element's display such + that its dimensions are accurate.
        element: !Element
        Element (which may have display none) to use as + argument to fn.
        Returns
        Value returned by calling fn with element.

        Retrieves the computed background color string for a given element. The + string returned is suitable for assigning to another element's + background-color, but is not guaranteed to be in any particular string + format. Accessing the color in a numeric form may not be possible in all + browsers or with all input. + + If the background color for the element is defined as a hexadecimal value, + the resulting string can be parsed by goog.color.parse in all supported + browsers. + + Whether named colors like "red" or "lightblue" get translated into a + format which can be parsed is browser dependent. Calling this function on + transparent elements will return "transparent" in most browsers or + "rgba(0, 0, 0, 0)" in WebKit.

        Parameters
        element: Element
        The element to get the background color of.
        Returns
        The computed string value of the background color.

        Gets the computed border widths (on all sides) in pixels

        Parameters
        element: Element
        The element to get the border widths for.
        Returns
        The computed border widths.

        Gets the border box size for an element.

        Parameters
        element: Element
        The element to get the size for.
        Returns
        The border box size.

        Gets the client rectangle of the DOM element. + + getBoundingClientRect is part of a new CSS object model draft (with a + long-time presence in IE), replacing the error-prone parent offset + computation and the now-deprecated Gecko getBoxObjectFor. + + This utility patches common browser bugs in getBoundingClientRect. It + will fail if getBoundingClientRect is unsupported. + + If the element is not in the DOM, the result is undefined, and an error may + be thrown depending on user agent.

        Parameters
        el: !Element
        The element whose bounding rectangle is being queried.
        Returns
        A native bounding rectangle with numerical left, top, + right, and bottom. Reported by Firefox to be of object type ClientRect.

        Returns a bounding rectangle for a given element in page space.

        Parameters
        element: Element
        Element to get bounds of. Must not be display none.
        Returns
        Bounding rectangle for the element.
        code »goog.style.getBox_ ( element, stylePrefix )!goog.math.Box

        Gets the computed paddings or margins (on all sides) in pixels.

        Parameters
        element: Element
        The element to get the padding for.
        stylePrefix: string
        Pass 'padding' to retrieve the padding box, + or 'margin' to retrieve the margin box.
        Returns
        The computed paddings or margins.

        Gets the cascaded style value of a node, or null if the value cannot be + computed (only Internet Explorer can do this).

        Parameters
        element: Element
        Element to get style of.
        style: string
        Property to get (camel-case).
        Returns
        Style value.

        Returns clientLeft (width of the left border and, if the directionality is + right to left, the vertical scrollbar) and clientTop as a coordinate object.

        Parameters
        el: Element
        Element to get clientLeft for.
        Returns
        Client left and top.

        Returns the position of the event or the element's border box relative to + the client viewport.

        Parameters
        el: (Element|Event|goog.events.Event)
        Element or a mouse / touch event.
        Returns
        The position.

        Returns the position of the event or the element's border box relative to + the client viewport.

        Parameters
        el: !Element
        Element whose position to get.
        Returns
        The position.

        Returns the viewport element for a particular document

        Parameters
        opt_node: Node=
        DOM node (Document is OK) to get the viewport element + of.
        Returns
        document.documentElement or document.body.

        Retrieves the computed value of the box-sizing CSS attribute. + Browser support: http://caniuse.com/css3-boxsizing.

        Parameters
        element: !Element
        The element whose box-sizing to get.
        Returns
        'content-box', 'border-box' or 'padding-box'. null if + box-sizing is not supported (IE7 and below).

        Retrieves the computed value of the cursor CSS attribute.

        Parameters
        element: Element
        The element to get the cursor of.
        Returns
        The computed string value of the cursor attribute.

        Retrieves the computed value of the overflow-x CSS attribute.

        Parameters
        element: Element
        The element to get the overflow-x of.
        Returns
        The computed string value of the overflow-x attribute.

        Retrieves the computed value of the overflow-y CSS attribute.

        Parameters
        element: Element
        The element to get the overflow-y of.
        Returns
        The computed string value of the overflow-y attribute.

        Retrieves the computed value of the position CSS attribute.

        Parameters
        element: Element
        The element to get the position of.
        Returns
        Position value.
        code »goog.style.getComputedStyle ( element, property )string

        Retrieves a computed style value of a node. It returns empty string if the + value cannot be computed (which will be the case in Internet Explorer) or + "none" if the property requested is an SVG one and it has not been + explicitly set (firefox and webkit).

        Parameters
        element: Element
        Element to get style of.
        property: string
        Property to get (camel-case).
        Returns
        Style value.

        Retrieves the computed value of the text-align CSS attribute.

        Parameters
        element: Element
        The element to get the text-align of.
        Returns
        The computed string value of the text-align attribute.

        Retrieves the computed value of the CSS transform attribute.

        Parameters
        element: Element
        The element to get the transform of.
        Returns
        The computed string representation of the transform matrix.

        Retrieves the computed value of the z-index CSS attribute.

        Parameters
        element: Element
        The element to get the z-index of.
        Returns
        The computed value of the z-index attribute.

        Calculate the scroll position of container with the minimum amount so + that the content and the borders of the given element become visible. + If the element is bigger than the container, its top left corner will be + aligned as close to the container's top left corner as possible.

        Parameters
        element: Element
        The element to make visible.
        container: Element
        The container to scroll.
        opt_center: boolean=
        Whether to center the element in the container. + Defaults to false.
        Returns
        The new scroll position of the container, + in form of goog.math.Coordinate(scrollLeft, scrollTop).

        Gets the content box size for an element. This is potentially expensive in + all browsers.

        Parameters
        element: Element
        The element to get the size for.
        Returns
        The content box size.

        Returns the x,y translation component of any CSS transforms applied to the + element, in pixels.

        Parameters
        element: !Element
        The element to get the translation of.
        Returns
        The CSS translation of the element in px.

        Gets value of explicitly-set float CSS property on an element.

        Parameters
        el: Element
        The element to get float property of.
        Returns
        The value of explicitly-set float CSS property on this + element.

        Returns the font face applied to a given node. Opera and IE should return + the font actually displayed. Firefox returns the author's most-preferred + font (whether the browser is capable of displaying it or not.)

        Parameters
        el: Element
        The element whose font family is returned.
        Returns
        The font family applied to el.

        Returns the font size, in pixels, of text in an element.

        Parameters
        el: Element
        The element whose font size is returned.
        Returns
        The font size (in pixels).

        Returns a Coordinate object relative to the top-left of an HTML document + in an ancestor frame of this element. Used for measuring the position of + an element inside a frame relative to a containing frame.

        Parameters
        el: Element
        Element to get the page offset for.
        relativeWin: Window
        The window to measure relative to. If relativeWin + is not in the ancestor frame chain of the element, we measure relative to + the top-most window.
        Returns
        The page offset.

        Helper function for IE to get the pixel border.

        Parameters
        element: Element
        The element to get the pixel border for.
        prop: string
        The part of the property name.
        Returns
        The value in pixels.

        Helper function for getting the pixel padding or margin for IE.

        Parameters
        element: Element
        The element to get the padding for.
        propName: string
        The property name.
        Returns
        The pixel padding.
        code »goog.style.getIePixelValue_ ( element, value, name, pixelName )number

        IE specific function that converts a non pixel unit to pixels.

        Parameters
        element: Element
        The element to convert the value for.
        value: string
        The current value as a string. The value must not be + ''.
        name: string
        The CSS property name to use for the converstion. This + should be 'left', 'top', 'width' or 'height'.
        pixelName: string
        The CSS pixel property name to use to get the + value in pixels.
        Returns
        The value in pixels.

        Returns the units used for a CSS length measurement.

        Parameters
        value: string
        A CSS length quantity.
        Returns
        The units of measurement.

        Gets the computed margins (on all sides) in pixels.

        Parameters
        element: Element
        The element to get the margins for.
        Returns
        The computed margins.

        Returns the first parent that could affect the position of a given element.

        Parameters
        element: Element
        The element to get the offset parent for.
        Returns
        The first offset parent or null if one cannot be found.

        Gets the opacity of a node (x-browser). This gets the inline style opacity + of the node, and does not take into account the cascaded or the computed + style for this node.

        Parameters
        el: Element
        Element whose opacity has to be found.
        Returns
        Opacity between 0 and 1 or an empty string '' + if the opacity is not set.

        Gets the computed paddings (on all sides) in pixels.

        Parameters
        element: Element
        The element to get the padding for.
        Returns
        The computed paddings.

        Returns a Coordinate object relative to the top-left of the HTML document. + Implemented as a single function to save having to do two recursive loops in + opera and safari just to get both coordinates. If you just want one value do + use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but + note if you call both those methods the tree will be analysed twice.

        Parameters
        el: Element
        Element to get the page offset for.
        Returns
        The page offset.

        Returns the left coordinate of an element relative to the HTML document

        Parameters
        el: Element
        Elements.
        Returns
        The left coordinate.

        Returns the top coordinate of an element relative to the HTML document

        Parameters
        el: Element
        Elements.
        Returns
        The top coordinate.

        Helper function to create a string to be set into a pixel-value style + property of an element. Can round to the nearest integer value.

        Parameters
        value: (string|number)
        The style value to be used. If a number, + 'px' will be appended, otherwise the value will be applied directly.
        round: boolean
        Whether to round the nearest integer (if property + is a number).
        Returns
        The string value for the property.

        Gets the offsetLeft and offsetTop properties of an element and returns them + in a Coordinate object

        Parameters
        element: Element
        Element.
        Returns
        The position.

        Returns the position of an element relative to another element in the + document. A relative to B

        Parameters
        a: (Element|Event|goog.events.Event)
        Element or mouse event whose + position we're calculating.
        b: (Element|Event|goog.events.Event)
        Element or mouse event position + is relative to.
        Returns
        The relative position.

        Returns the scroll bar width (represents the width of both horizontal + and vertical scroll).

        Parameters
        opt_className: string=
        An optional class name (or names) to apply + to the invisible div created to measure the scrollbar. This is necessary + if some scrollbars are styled differently than others.
        Returns
        The scroll bar width in px.

        Gets the height and width of an element, even if its display is none. + + Specifically, this returns the height and width of the border box, + irrespective of the box model in effect. + + Note that this function does not take CSS transforms into account. Please see + goog.style.getTransformedSize.

        Parameters
        element: Element
        Element to get size of.
        Returns
        Object with width/height properties.

        Gets the height and width of an element when the display is not none.

        Parameters
        element: Element
        Element to get size of.
        Returns
        Object with width/height properties.
        code »goog.style.getStyle ( element, property )string

        Retrieves an explicitly-set style value of a node. This returns '' if there + isn't a style attribute on the element or if this style property has not been + explicitly set in script.

        Parameters
        element: Element
        Element to get style of.
        property: string
        Property to get, css-style (if you have a camel-case + property, use element.style[style]).
        Returns
        Style value.
        code »goog.style.getStyle_ ( element, style )string

        Cross-browser pseudo get computed style. It returns the computed style where + available. If not available it tries the cascaded style value (IE + currentStyle) and in worst case the inline style value. It shouldn't be + called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for + discussion.

        Parameters
        element: Element
        Element to get style of.
        style: string
        Property to get (must be camelCase, not css-style.).
        Returns
        Style value.

        Gets the height and width of an element, post transform, even if its display + is none. + + This is like goog.style.getSize, except: +

          +
        1. Takes webkitTransforms such as rotate and scale into account. +
        2. Will return null if element doesn't respond to + getBoundingClientRect. +
        3. Currently doesn't make sense on non-WebKit browsers which don't support + webkitTransforms. +
        Parameters
        element: !Element
        Element to get size of.
        Returns
        Object with width/height properties.

        Returns the style property name in camel-case. If it does not exist and a + vendor-specific version of the property does exist, then return the vendor- + specific property name instead.

        Parameters
        element: Element
        The element to change.
        style: string
        Style name.
        Returns
        Vendor-specific style.

        Returns the style property name in CSS notation. If it does not exist and a + vendor-specific version of the property does exist, then return the vendor- + specific property name instead.

        Parameters
        element: Element
        The element to change.
        style: string
        Style name.
        Returns
        Vendor-specific style.

        Calculates the viewport coordinates relative to the page/document + containing the node. The viewport may be the browser viewport for + non-iframe document, or the iframe container for iframe'd document.

        Parameters
        doc: !Document
        The document to use as the reference point.
        Returns
        The page offset of the viewport.

        Calculates and returns the visible rectangle for a given element. Returns a + box describing the visible portion of the nearest scrollable offset ancestor. + Coordinates are given relative to the document.

        Parameters
        element: Element
        Element to get the visible rect for.
        Returns
        Bounding elementBox describing the visible rect or + null if scrollable ancestor isn't inside the visible viewport.
        code »goog.style.installStyles ( stylesString, opt_node )(Element|StyleSheet)

        Installs the styles string into the window that contains opt_element. If + opt_element is null, the main window is used.

        Parameters
        stylesString: string
        The style string to install.
        opt_node: Node=
        Node whose parent document should have the + styles installed.
        Returns
        The style element created.

        Test whether the given element has been shown or hidden via a call to + #setElementShown. + + Note this is strictly a companion method for a call + to #setElementShown and the same caveats apply; in particular, this + method does not guarantee that the return value will be consistent with + whether or not the element is actually visible.

        Parameters
        el: Element
        The element to test.
        Returns
        Whether the element has been shown.

        Returns true if the element is using right to left (rtl) direction.

        Parameters
        el: Element
        The element to test.
        Returns
        True for right to left, false for left to right.

        Returns true if the element is set to be unselectable, false otherwise. + Note that on some platforms (e.g. Mozilla), even if an element isn't set + to be unselectable, it will behave as such if any of its ancestors is + unselectable.

        Parameters
        el: Element
        Element to check.
        Returns
        Whether the element is set to be unselectable.

        Parses a style attribute value. Converts CSS property names to camel case.

        Parameters
        value: string
        The style attribute value.
        Returns
        Map of CSS properties to string values.
        code »goog.style.scrollIntoContainerView ( element, container, opt_center )

        Changes the scroll position of container with the minimum amount so + that the content and the borders of the given element become visible. + If the element is bigger than the container, its top left corner will be + aligned as close to the container's top left corner as possible.

        Parameters
        element: Element
        The element to make visible.
        container: Element
        The container to scroll.
        opt_center: boolean=
        Whether to center the element in the container. + Defaults to false.

        Sets the border box size of an element. This is potentially expensive in IE + if the document is CSS1Compat mode

        Parameters
        element: Element
        The element to set the size on.
        size: goog.math.Size
        The new size.
        code »goog.style.setBoxSizingSize_ ( element, size, boxSizing )

        Helper function that sets the box sizing as well as the width and height

        Parameters
        element: Element
        The element to set the size on.
        size: goog.math.Size
        The new size to set.
        boxSizing: string
        The box-sizing value.

        Sets the content box size of an element. This is potentially expensive in IE + if the document is BackCompat mode.

        Parameters
        element: Element
        The element to set the size on.
        size: goog.math.Size
        The new size.

        Shows or hides an element from the page. Hiding the element is done by + setting the display property to "none", removing the element from the + rendering hierarchy so it takes up no space. To show the element, the default + inherited display property is restored (defined either in stylesheets or by + the browser's default style rules). + + Caveat 1: if the inherited display property for the element is set to "none" + by the stylesheets, that is the property that will be restored by a call to + setElementShown(), effectively toggling the display between "none" and + "none". + + Caveat 2: if the element display style is set inline (by setting either + element.style.display or a style attribute in the HTML), a call to + setElementShown will clear that setting and defer to the inherited style in + the stylesheet.

        Parameters
        el: Element
        Element to show or hide.
        isShown: *
        True to render the element in its default style, + false to disable rendering the element.

        Sets CSS float property on an element.

        Parameters
        el: Element
        The element to set float property on.
        value: string
        The value of float CSS property to set on this element.
        code »goog.style.setHeight ( element, height )

        Set the height of an element. Sets the element's style property.

        Parameters
        element: Element
        Element to set the height of.
        height: (string|number)
        The height value to set. If a number, 'px' + will be appended, otherwise the value will be applied directly.

        Sets 'display: inline-block' for an element (cross-browser).

        Parameters
        el: Element
        Element to which the inline-block display style is to be + applied.

        Sets the opacity of a node (x-browser).

        Parameters
        el: Element
        Elements whose opacity has to be set.
        alpha: (number|string)
        Opacity between 0 and 1 or an empty string + '' to clear the opacity.

        Moves an element to the given coordinates relative to the client viewport.

        Parameters
        el: Element
        Absolutely positioned element to set page offset for. + It must be in the document.
        x: (number|goog.math.Coordinate)
        Left position of the element's margin + box or a coordinate object.
        opt_y: number=
        Top position of the element's margin box.
        code »goog.style.setPosition ( el, arg1, opt_arg2 )

        Sets the top/left values of an element. If no unit is specified in the + argument then it will add px. The second argument is required if the first + argument is a string or number and is ignored if the first argument + is a coordinate.

        Parameters
        el: Element
        Element to move.
        arg1: (string|number|goog.math.Coordinate)
        Left position or coordinate.
        opt_arg2: (string|number)=
        Top position.

        Sets 'white-space: pre-wrap' for a node (x-browser). + + There are as many ways of specifying pre-wrap as there are browsers. + + CSS3/IE8: white-space: pre-wrap; + Mozilla: white-space: -moz-pre-wrap; + Opera: white-space: -o-pre-wrap; + IE6/7: white-space: pre; word-wrap: break-word;

        Parameters
        el: Element
        Element to enable pre-wrap for.
        code »goog.style.setSize ( element, w, opt_h )

        Sets the width/height values of an element. If an argument is numeric, + or a goog.math.Size is passed, it is assumed to be pixels and will add + 'px' after converting it to an integer in string form. (This just sets the + CSS width and height properties so it might set content-box or border-box + size depending on the box model the browser is using.)

        Parameters
        element: Element
        Element to set the size of.
        w: (string|number|goog.math.Size)
        Width of the element, or a + size object.
        opt_h: (string|number)=
        Height of the element. Required if w is not a + size object.
        code »goog.style.setStyle ( element, style, opt_value )

        Sets a style value on an element. + + This function is not indended to patch issues in the browser's style + handling, but to allow easy programmatic access to setting dash-separated + style properties. An example is setting a batch of properties from a data + object without overwriting old styles. When possible, use native APIs: + elem.style.propertyKey = 'value' or (if obliterating old styles is fine) + elem.style.cssText = 'property1: value1; property2: value2'.

        Parameters
        element: Element
        The element to change.
        style: (string|Object)
        If a string, a style name. If an object, a hash + of style names to style values.
        opt_value: (string|number|boolean)=
        If style was a string, then this + should be the value.
        code »goog.style.setStyle_ ( element, value, style )

        Sets a style value on an element, with parameters swapped to work with + goog.object.forEach(). Prepends a vendor-specific prefix when + necessary.

        Parameters
        element: Element
        The element to change.
        value: (string|number|boolean|undefined)
        Style value.
        style: string
        Style name.
        code »goog.style.setStyles ( element, stylesString )

        Sets the content of a style element. The style element can be any valid + style element. This element will have its content completely replaced by + the new stylesString.

        Parameters
        element: (Element|StyleSheet)
        A stylesheet element as returned by + installStyles.
        stylesString: string
        The new content of the stylesheet.

        Sets the background of an element to a transparent image in a browser- + independent manner. + + This function does not support repeating backgrounds or alternate background + positions to match the behavior of Internet Explorer. It also does not + support sizingMethods other than crop since they cannot be replicated in + browsers other than Internet Explorer.

        Parameters
        el: Element
        The element to set background on.
        src: string
        The image source URL.
        code »goog.style.setUnselectable ( el, unselectable, opt_noRecurse )

        Makes the element and its descendants selectable or unselectable. Note + that on some platforms (e.g. Mozilla), even if an element isn't set to + be unselectable, it will behave as such if any of its ancestors is + unselectable.

        Parameters
        el: Element
        The element to alter.
        unselectable: boolean
        Whether the element and its descendants + should be made unselectable.
        opt_noRecurse: boolean=
        Whether to only alter the element's own + selectable state, and leave its descendants alone; defaults to false.
        code »goog.style.setWidth ( element, width )

        Set the width of an element. Sets the element's style property.

        Parameters
        element: Element
        Element to set the width of.
        width: (string|number)
        The width value to set. If a number, 'px' + will be appended, otherwise the value will be applied directly.
        Deprecated: Use goog.style.setElementShown instead.

        Shows or hides an element from the page. Hiding the element is done by + setting the display property to "none", removing the element from the + rendering hierarchy so it takes up no space. To show the element, the default + inherited display property is restored (defined either in stylesheets or by + the browser's default style rules.) + + Caveat 1: if the inherited display property for the element is set to "none" + by the stylesheets, that is the property that will be restored by a call to + showElement(), effectively toggling the display between "none" and "none". + + Caveat 2: if the element display style is set inline (by setting either + element.style.display or a style attribute in the HTML), a call to + showElement will clear that setting and defer to the inherited style in the + stylesheet.

        Parameters
        el: Element
        Element to show or hide.
        display: *
        True to render the element in its default style, + false to disable rendering the element.
        Deprecated: Use goog.string.toCamelCase instead.

        Converts a CSS selector in the form style-property to styleProperty.

        Parameters
        selector: *
        CSS Selector.
        Returns
        Camel case selector.
        Deprecated: Use goog.string.toSelectorCase instead.

        Converts a CSS selector in the form styleProperty to style-property.

        Parameters
        selector: string
        Camel case selector.
        Returns
        Selector cased.

        Reverse of parseStyleAttribute; that is, takes a style object and returns the + corresponding attribute value. Converts camel case property names to proper + CSS selector names.

        Parameters
        obj: Object
        Map of CSS properties to values.
        Returns
        The style attribute value.

        Translates the specified rect relative to origBase page, for newBase page. + If origBase and newBase are the same, this function does nothing.

        Parameters
        rect: goog.math.Rect
        The source rectangle relative to origBase page, + and it will have the translated result.
        origBase: goog.dom.DomHelper
        The DomHelper for the input rectangle.
        newBase: goog.dom.DomHelper
        The DomHelper for the resultant + coordinate. This must be a DOM for an ancestor frame of origBase + or the same as origBase.

        Removes the styles added by #installStyles.

        Parameters
        styleSheet: (Element|StyleSheet)
        The value returned by + #installStyles.

        Global Properties

        Map of absolute CSS length units

        Map of relative CSS length units that can be accurately converted to px + font-size values using getIePixelValue_. Only units that are defined in + relation to a font size are convertible (%, small, etc. are not).

        Regular expression to extract x and y translation components from a CSS + transform Matrix representation.

        A map used to map the border width keywords to a pixel width.

        Regular expression used for getLengthUnits.

        The CSS style property corresponding to an element being + unselectable on the current browser platform (null if none). + Opera and IE instead use a DOM attribute 'unselectable'.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_testing.html b/docs/namespace_goog_testing.html new file mode 100644 index 0000000..f20920e --- /dev/null +++ b/docs/namespace_goog_testing.html @@ -0,0 +1,28 @@ +goog.testing

        Namespace goog.testing

        code »

        Interfaces

        Classes

        goog.testing.AsyncTestCase
        A test case that is capable of running tests the contain asynchronous logic.
        goog.testing.FunctionCall
        Struct for a single function call.
        goog.testing.JsUnitException
        No Description.
        goog.testing.LooseExpectationCollection
        This class is an ordered collection of expectations for one method.
        goog.testing.LooseMock
        This is a mock that does not care about the order of method calls.
        goog.testing.Mock
        The base class for a mock object.
        goog.testing.MockClock
        Class for unit testing code that uses setTimeout and clearTimeout.
        goog.testing.MockControl
        Controls a set of mocks.
        goog.testing.MockExpectation
        This is a class that represents an expectation.
        goog.testing.ObjectPropertyString
        Object to pass a property name as a string literal and its containing object + when the JSCompiler is rewriting these names.
        goog.testing.PropertyReplacer
        Helper class for stubbing out variables and object properties for unit tests.
        goog.testing.StrictMock
        This is a mock that verifies that methods are called in the order that they + are specified during the recording phase.
        goog.testing.TestCase
        A class representing a JsUnit test case.
        goog.testing.TestRunner
        Construct a test runner.
        Show:

        Global Functions

        code »goog.testing.FunctionMock ( opt_functionName, opt_strictness )goog.testing.MockInterface

        Class used to mock a function. Useful for mocking closures and anonymous + callbacks etc. Creates a function object that extends goog.testing.Mock.

        Parameters
        opt_functionName: string=
        The optional name of the function to mock. + Set to '[anonymous mocked function]' if not passed in.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked function.

        Mocks a global / top-level function. Creates a goog.testing.MethodMock + in the global scope with the name specified by functionName.

        Parameters
        functionName: string
        The name of the function we're going to mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked global function.
        code »goog.testing.MethodMock ( scope, functionName, opt_strictness )!goog.testing.MockInterface

        Mocks an existing function. Creates a goog.testing.FunctionMock + and registers it in the given scope with the name specified by functionName.

        Parameters
        scope: Object
        The scope of the method to be mocked out.
        functionName: string
        The name of the function we're going to mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked method.
        code »goog.testing.createConstructorMock ( scope, constructorName, opt_strictness )!goog.testing.MockInterface

        Convenience method for creating a mock for a constructor. Copies class + members to the mock. + +

        When mocking a constructor to return a mocked instance, remember to create + the instance mock before mocking the constructor. If you mock the constructor + first, then the mock framework will be unable to examine the prototype chain + when creating the mock instance.

        Parameters
        scope: Object
        The scope of the constructor to be mocked out.
        constructorName: string
        The name of the constructor we're going to + mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked constructor.

        Convenience method for creating a mock for a function.

        Parameters
        opt_functionName: string=
        The optional name of the function to mock + set to '[anonymous mocked function]' if not passed in.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked function.

        Convenience method for creating a mocks for a global / top-level function.

        Parameters
        functionName: string
        The name of the function we're going to mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked global function.
        code »goog.testing.createMethodMock ( scope, functionName, opt_strictness )!goog.testing.MockInterface

        Convenience method for creating a mock for a method.

        Parameters
        scope: Object
        The scope of the method to be mocked out.
        functionName: string
        The name of the function we're going to mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked global function.

        Same as goog.testing.recordFunction but the recorded function will + have the same prototype and static fields as the original one. It can be + used with constructors.

        Parameters
        ctor: !Function
        The function to wrap and record.
        Returns
        The wrapped function.

        Wraps the function into another one which calls the inner function and + records its calls. The recorded function will have 3 static methods: + getCallCount, getCalls and getLastCall but won't + inherit the original function's prototype and static fields.

        Parameters
        opt_f: !Function=
        The function to wrap and record. Defaults to + goog.nullFunction.
        Returns
        The wrapped function.
        \ No newline at end of file diff --git a/docs/namespace_goog_testing_MethodMock.html b/docs/namespace_goog_testing_MethodMock.html new file mode 100644 index 0000000..6cc86c6 --- /dev/null +++ b/docs/namespace_goog_testing_MethodMock.html @@ -0,0 +1,3 @@ +goog.testing.MethodMock

        Namespace goog.testing.MethodMock

        code »

        Mocks an existing function. Creates a goog.testing.FunctionMock + and registers it in the given scope with the name specified by functionName.

        Main

        MethodMock ( scope, functionName, opt_strictness )!goog.testing.MockInterface
        Parameters
        scope: Object
        The scope of the method to be mocked out.
        functionName: string
        The name of the function we're going to mock.
        opt_strictness: number=
        One of goog.testing.Mock.LOOSE or + goog.testing.Mock.STRICT. The default is STRICT.
        Returns
        The mocked method.
        Show:

        Global Functions

        Resets the global function that we mocked back to its original state.

        \ No newline at end of file diff --git a/docs/namespace_goog_testing_asserts.html b/docs/namespace_goog_testing_asserts.html new file mode 100644 index 0000000..7eeb88e --- /dev/null +++ b/docs/namespace_goog_testing_asserts.html @@ -0,0 +1,15 @@ +goog.testing.asserts

        Namespace goog.testing.asserts

        code »
        Show:

        Type Definitions

        Global Functions

        Runs a function in an environment where test failures are not logged. This is + useful for testing test code, where failures can be a normal part of a test.

        Parameters
        fn: function(): void
        Function to run without logging failures.
        code »goog.testing.asserts.contains_ ( container, contained )boolean

        Tells whether the array contains the given element.

        Parameters
        container: goog.testing.asserts.ArrayLike
        The array to + find the element in.
        contained: *
        Element to find.
        Returns
        Whether the element is in the array.
        code »goog.testing.asserts.findDifferences ( expected, actual, opt_equalityPredicate )?string

        Determines if two items of any type match, and formulates an error message + if not.

        Parameters
        expected: *
        Expected argument to match.
        actual: *
        Argument as a result of performing the test.
        opt_equalityPredicate: (function(string, *, *): ?string)=
        An optional + function that can be used to check equality of variables. It accepts 3 + arguments: type-of-variables, var1, var2 (in that order) and returns an + error message if the variables are not equal, + goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL if the variables + are equal, or + goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS if the predicate + couldn't check the input variables. The function will be called only if + the types of var1 and var2 are identical.
        Returns
        Null on success, error message on failure.
        Parameters
        expected: *
        The expected value.
        actual: *
        The actual value.
        Returns
        A failure message of the values don't match.
        code »goog.testing.asserts.indexOf_ ( container, contained )number

        Finds the position of the first occurrence of an element in a container.

        Parameters
        container: goog.testing.asserts.ArrayLike
        The array to find the element in.
        contained: *
        Element to find.
        Returns
        Index of the first occurrence or -1 if not found.

        Helper function for assertObjectEquals.

        Parameters
        prop: string
        A property name.
        Returns
        If the property name is an array index.

        Compares equality of two numbers, allowing them to differ up to a given + tolerance.

        Parameters
        var1: number
        A number.
        var2: number
        A number.
        tolerance: number
        the maximum allowed difference.
        Returns
        Whether the two variables are sufficiently close.

        Raises a JsUnit exception with the given comment.

        Parameters
        comment: string
        A summary for the exception.
        opt_message: string=
        A description of the exception.

        Converts an array like object to array or clones it if it's already array.

        Parameters
        arrayLike: goog.testing.asserts.ArrayLike
        The collection.
        Returns
        Copy of the collection as array.

        Global Properties

        The return value of the equality predicate passed to findDifferences below, + in cases where the predicate can't test the input variables for equality.

        The return value of the equality predicate passed to findDifferences below, + in cases where the input vriables are equal.

        \ No newline at end of file diff --git a/docs/namespace_goog_testing_events.html b/docs/namespace_goog_testing_events.html new file mode 100644 index 0000000..930c01f --- /dev/null +++ b/docs/namespace_goog_testing_events.html @@ -0,0 +1,80 @@ +goog.testing.events

        Namespace goog.testing.events

        code »

        Classes

        goog.testing.events.Event
        goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.
        Show:

        Global Functions

        Asserts an event target exists. This will fail if target is not defined. + + TODO(nnaze): Gradually add this to the methods in this file, and eventually + update the method signatures to not take nullables. See http://b/8961907

        Parameters
        target: EventTarget
        A target to assert.
        Returns
        The target, guaranteed to exist.

        Simulate a blur event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        Returns
        The value returned by firing the blur browser event, + which returns false iff 'preventDefault' was invoked.

        Simulates an event's capturing and bubbling phases.

        Parameters
        event: Event
        A simulated native event. It will be wrapped in a + normalized BrowserEvent and dispatched to Closure listeners on all + ancestors of its target (inclusive).
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireClickEvent ( target, opt_button, opt_coords, opt_eventProperties )boolean

        Simulates a click event on the given target. IE only supports click with + the left mouse button.

        Parameters
        target: EventTarget
        The target for the event.
        opt_button: goog.events.BrowserEvent.MouseButton=
        Mouse button; + defaults to goog.events.BrowserEvent.MouseButton.LEFT.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireClickSequence ( target, opt_button, opt_coords, opt_eventProperties )boolean

        Simulates a mousedown, mouseup, and then click on the given event target, + with the left mouse button.

        Parameters
        target: EventTarget
        The target for the event.
        opt_button: goog.events.BrowserEvent.MouseButton=
        Mouse button; + defaults to goog.events.BrowserEvent.MouseButton.LEFT.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.

        Simulates a contextmenu event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.

        Simulates a mousedown, contextmenu, and the mouseup on the given event + target, with the right mouse button.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.
        code »goog.testing.events.fireDoubleClickEvent ( target, opt_coords, opt_eventProperties )boolean

        Simulates a double-click event on the given target. Always double-clicks + with the left mouse button since no browser supports double-clicking with + any other buttons.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireDoubleClickSequence ( target, opt_coords, opt_eventProperties )boolean

        Simulates the sequence of events fired by the browser when the user double- + clicks the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.

        Simulate a focus event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        Returns
        The value returned by firing the focus browser event, + which returns false iff 'preventDefault' was invoked.
        code »goog.testing.events.fireKeySequence ( target, keyCode, opt_eventProperties )boolean

        Simulates a complete keystroke (keydown, keypress, and keyup). Note that + if preventDefault is called on the keydown, the keypress will not fire.

        Parameters
        target: EventTarget
        The target for the event.
        keyCode: number
        The keycode of the key pressed.
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.
        code »goog.testing.events.fireMouseButtonEvent_ ( type, target, opt_button, opt_coords, opt_eventProperties )boolean

        Helper function to fire a mouse event. + with the left mouse button since no browser supports double-clicking with + any other buttons.

        Parameters
        type: string
        The event type.
        target: EventTarget
        The target for the event.
        opt_button: number=
        Mouse button; defaults to + goog.events.BrowserEvent.MouseButton.LEFT.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireMouseDownEvent ( target, opt_button, opt_coords, opt_eventProperties )boolean

        Simulates a mousedown event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_button: goog.events.BrowserEvent.MouseButton=
        Mouse button; + defaults to goog.events.BrowserEvent.MouseButton.LEFT.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.

        Simulates a mousemove event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireMouseOutEvent ( target, relatedTarget, opt_coords )boolean

        Simulates a mouseout event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        relatedTarget: EventTarget
        The related target for the event (e.g., + the node that the mouse is being moved into).
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireMouseOverEvent ( target, relatedTarget, opt_coords )boolean

        Simulates a mouseover event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        relatedTarget: EventTarget
        The related target for the event (e.g., + the node that the mouse is being moved out of).
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireMouseUpEvent ( target, opt_button, opt_coords, opt_eventProperties )boolean

        Simulates a mouseup event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_button: goog.events.BrowserEvent.MouseButton=
        Mouse button; + defaults to goog.events.BrowserEvent.MouseButton.LEFT.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireNonAsciiKeySequence ( target, keyCode, keyPressKeyCode, opt_eventProperties )boolean

        Simulates a complete keystroke (keydown, keypress, and keyup) when typing + a non-ASCII character. Same as fireKeySequence, the keypress will not fire + if preventDefault is called on the keydown.

        Parameters
        target: EventTarget
        The target for the event.
        keyCode: number
        The keycode of the keydown and keyup events.
        keyPressKeyCode: number
        The keycode of the keypress event.
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.

        Simulates a popstate event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        state: Object
        History state object.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireTouchEndEvent ( target, opt_coords, opt_eventProperties )boolean

        Simulates a touchend event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Touch position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireTouchMoveEvent ( target, opt_coords, opt_eventProperties )boolean

        Simulates a touchmove event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Touch position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        code »goog.testing.events.fireTouchSequence ( target, opt_coords, opt_eventProperties )boolean

        Simulates a simple touch sequence on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Touch position. Defaults to event + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the sequence: false if preventDefault() + was called on any of the events, true otherwise.
        code »goog.testing.events.fireTouchStartEvent ( target, opt_coords, opt_eventProperties )boolean

        Simulates a touchstart event on the given target.

        Parameters
        target: EventTarget
        The target for the event.
        opt_coords: goog.math.Coordinate=
        Touch position. Defaults to event's + target's position (if available), otherwise (0, 0).
        opt_eventProperties: Object=
        Event properties to be mixed into the + BrowserEvent.
        Returns
        The returnValue of the event: false if preventDefault() was + called on it, true otherwise.
        Parameters
        e: goog.testing.events.Event
        The event.
        Returns
        Whether this is the Gecko/Mac's Meta-C/V/X, which + is broken and requires special handling.

        Mixins a listenable into the given object. This turns the object + into a goog.events.Listenable. This is useful, for example, when + you need to mock a implementation of listenable and still want it + to work with goog.events.

        Parameters
        obj: !Object
        The object to mixin into.

        A static helper function that sets the mouse position to the event.

        Parameters
        event: Event
        A simulated native event.
        opt_coords: goog.math.Coordinate=
        Mouse position. Defaults to event's + target's position (if available), otherwise (0, 0).
        \ No newline at end of file diff --git a/docs/namespace_goog_testing_jsunit.html b/docs/namespace_goog_testing_jsunit.html new file mode 100644 index 0000000..df7f963 --- /dev/null +++ b/docs/namespace_goog_testing_jsunit.html @@ -0,0 +1 @@ +goog.testing.jsunit

        Namespace goog.testing.jsunit

        code »
        Show:

        Global Properties

        Base path for JsUnit app files, relative to Closure's base path.

        Filename for the core JS Unit script.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_testing_mockmatchers.html b/docs/namespace_goog_testing_mockmatchers.html new file mode 100644 index 0000000..a44ee24 --- /dev/null +++ b/docs/namespace_goog_testing_mockmatchers.html @@ -0,0 +1,9 @@ +goog.testing.mockmatchers

        Namespace goog.testing.mockmatchers

        code »

        Classes

        goog.testing.mockmatchers.ArgumentMatcher
        A simple interface for executing argument matching.
        goog.testing.mockmatchers.IgnoreArgument
        A matcher that always returns true.
        goog.testing.mockmatchers.InstanceOf
        A matcher that verifies that an argument is an instance of a given class.
        goog.testing.mockmatchers.ObjectEquals
        A matcher that verifies that the argument is an object that equals the given + expected object, using a deep comparison.
        goog.testing.mockmatchers.RegexpMatch
        A matcher that verifies that an argument matches a given RegExp.
        goog.testing.mockmatchers.SaveArgument
        A matcher that saves the argument that it is verifying so that your unit test + can perform extra tests with this argument later.
        goog.testing.mockmatchers.TypeOf
        A matcher that verifies that an argument is of a given type (e.g.
        Show:

        Global Functions

        code »goog.testing.mockmatchers.flexibleArrayMatcher ( expectedArr, arr, opt_expectation )boolean

        A function that checks to see if an array matches a given set of + expectations. The expectations array can be a mix of ArgumentMatcher + implementations and values. True will be returned if values are identical or + if a matcher returns a positive result.

        Parameters
        expectedArr: Array
        An array of expectations which can be either + values to check for equality or ArgumentMatchers.
        arr: Array
        The array to match.
        opt_expectation: ?goog.testing.MockExpectation=
        The expectation + for this match.
        Returns
        Whether or not the given array matches the expectations.

        Global Properties

        An instance of the IgnoreArgument matcher. Returns true for all matches.

        A matcher that verifies that an argument is an array.

        A matcher that verifies that an argument is a array-like. A NodeList is an + example of a collection that is very close to an array.

        A matcher that verifies that an argument is a boolean.

        A matcher that verifies that an argument is a date-like.

        A matcher that verifies that an argument is a function.

        A matcher that verifies that an argument is like a DOM node.

        A matcher that verifies that an argument is a number.

        A matcher that verifies that an argument is an object.

        A matcher that verifies that an argument is a string.

        \ No newline at end of file diff --git a/docs/namespace_goog_testing_stacktrace.html b/docs/namespace_goog_testing_stacktrace.html new file mode 100644 index 0000000..6425540 --- /dev/null +++ b/docs/namespace_goog_testing_stacktrace.html @@ -0,0 +1,29 @@ +goog.testing.stacktrace

        Namespace goog.testing.stacktrace

        code »

        Classes

        goog.testing.stacktrace.Frame
        Class representing one stack frame.
        Show:

        Global Functions

        Converts an array of CallSite (elements of a stack trace in V8) to an array + of Frames.

        Parameters
        stack: !Array
        The stack as an array of CallSites.
        Returns
        The stack as an array of + Frames.

        Brings the stack trace into a common format across browsers.

        Parameters
        stack: string
        Browser-specific stack trace.
        Returns
        Same stack trace in common format.

        Function to deobfuscate function names.

        Creates a stack trace by following the call chain. Based on + goog.debug.getStacktrace.

        Returns
        Stack frames.

        Converts the stack frames into canonical format. Chops the beginning and the + end of it which come from the testing environment, not from the test itself.

        Parameters
        frames: !Array.<goog.testing.stacktrace.Frame>
        The frames.
        Returns
        Canonical, pretty printed stack trace.

        Gets the native stack trace if available otherwise follows the call chain.

        Returns
        The stack trace in canonical format.

        Returns the native stack trace.

        Escapes the special character in HTML.

        Parameters
        text: string
        Plain text.
        Returns
        Escaped text.

        Deobfuscates a compiled function name with the function passed to + #setDeobfuscateFunctionName. Returns the original function name if + the deobfuscator hasn't been set.

        Parameters
        name: string
        The function name to deobfuscate.
        Returns
        The deobfuscated function name.

        Parses a long firefox stack frame.

        Parameters
        frameStr: string
        The stack frame as string.
        Returns
        Stack frame object.

        Parses one stack frame.

        Parameters
        frameStr: string
        The stack frame as string.
        Returns
        Stack frame object or null if the + parsing failed.

        Parses the browser's native stack trace.

        Parameters
        stack: string
        Stack trace.
        Returns
        Stack frames. The + unrecognized frames will be nulled out.

        Sets function to deobfuscate function names.

        Parameters
        fn: function(string): string
        function to deobfuscate function names.

        Global Properties

        RegExp pattern for an URL + line number + column number in V8. + The URL is either in submatch 1 or submatch 2.

        RegExp pattern for function call in the Firefox stack trace. + Creates 2 submatches with function name (optional) and arguments.

        Regular expression for parsing one stack frame in Firefox.

        Regular expression for finding the function name in its source.

        RegExp pattern for JavaScript identifiers. We don't support Unicode + identifiers defined in ECMAScript v3.

        RegExp pattern for function call in a IE stack trace. This expression allows + for identifiers like 'Anonymous function', 'eval code', and 'Global code'.

        Regular expression for parsing a stack frame in IE.

        Maximum number of steps while the call chain is followed.

        Maximum length of a string that can be matched with a RegExp on + Firefox 3x. Exceeding this approximate length will cause string.match + to exceed Firefox's stack quota. This situation can be encountered + when goog.globalEval is invoked with a long argument; such as + when loading a module.

        RegExp pattern for an anonymous function call in an Opera stack frame. + Creates 2 (optional) submatches: the context object and function name.

        RegExp pattern for a function call in an Opera stack frame. + Creates 4 (optional) submatches: the function name (if not anonymous), + the aliased context object and function name (if anonymous), and the + function call arguments.

        Regular expression for parsing on stack frame in Opera 11.68 - 12.17. + Newer versions of Opera use V8 and stack frames should match against + goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_.

        RegExp pattern for an URL + position inside the file.

        RegExp pattern for function name alias in the V8 stack trace.

        RegExp pattern for the context of a function call in a V8 stack trace. + Creates an optional submatch for the namespace identifier including the + "new" keyword for constructor calls (e.g. "new foo.Bar").

        RegExp pattern for function call in the V8 stack trace. Creates 3 submatches + with context object (optional), function name and function alias (optional).

        RegExp pattern for function names and constructor calls in the V8 stack + trace.

        Regular expression for parsing one stack frame in V8. For more information + on V8 stack frame formats, see + https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.

        \ No newline at end of file diff --git a/docs/namespace_goog_testing_watchers.html b/docs/namespace_goog_testing_watchers.html new file mode 100644 index 0000000..de31439 --- /dev/null +++ b/docs/namespace_goog_testing_watchers.html @@ -0,0 +1 @@ +goog.testing.watchers

        Namespace goog.testing.watchers

        code »
        Show:

        Global Functions

        Fires clock reset watching functions.

        Enqueues a function to be called when the clock used for setTimeout is reset.

        Parameters
        fn

        Global Properties

        \ No newline at end of file diff --git a/docs/namespace_goog_uri.html b/docs/namespace_goog_uri.html index 2bf8bb8..4570658 100644 --- a/docs/namespace_goog_uri.html +++ b/docs/namespace_goog_uri.html @@ -1 +1 @@ -goog.uri

        Namespace goog.uri

        code »
        Show:
        \ No newline at end of file +goog.uri

        Namespace goog.uri

        code »
        Show:
        \ No newline at end of file diff --git a/docs/namespace_goog_uri_utils.html b/docs/namespace_goog_uri_utils.html index b643ff8..b41e0cb 100644 --- a/docs/namespace_goog_uri_utils.html +++ b/docs/namespace_goog_uri_utils.html @@ -1,4 +1,4 @@ -goog.uri.utils

        Namespace goog.uri.utils

        code »

        Enumerations

        goog.uri.utils.CharCode_
        Character codes inlined to avoid object allocations due to charCode.
        goog.uri.utils.ComponentIndex
        The index of each URI component in the return value of goog.uri.utils.split.
        goog.uri.utils.StandardQueryParam
        Standard supported query parameters.
        Show:

        Type Definitions

        An array representing a set of query parameters with alternating keys +goog.uri.utils

        Namespace goog.uri.utils

        code »

        Enumerations

        goog.uri.utils.CharCode_
        Character codes inlined to avoid object allocations due to charCode.
        goog.uri.utils.ComponentIndex
        The index of each URI component in the return value of goog.uri.utils.split.
        goog.uri.utils.StandardQueryParam
        Standard supported query parameters.
        Show:

        Type Definitions

        An array representing a set of query parameters with alternating keys and values. Keys are assumed to be URI encoded already and live at even indices. See @@ -14,18 +14,18 @@ // Multi-valued param: &house=LosAngeles&house=NewYork&house=null 'house', ['LosAngeles', 'NewYork', null] ]; -
        Supported query parameter values by the parameter serializing utilities. +
        Supported query parameter values by the parameter serializing utilities. If a value is null or undefined, the key-value pair is skipped, as an easy way to omit parameters conditionally. Non-array parameters are converted to a string and URI encoded. Array values are expanded into multiple - &key=value pairs, with each element stringized and URI-encoded.

        Global Functions

        Appends key=value pairs to an array, supporting multi-valued objects.

        Parameters
        key: string
        The key prefix.
        value: goog.uri.utils.QueryValue
        The value to serialize.
        pairs: !Array.<string>
        The array to which the 'key=value' strings - should be appended.
        code »goog.uri.utils.appendParam ( uri, key, opt_value )string

        Appends a single URI parameter. + &key=value pairs, with each element stringized and URI-encoded.

        Global Functions

        Appends key=value pairs to an array, supporting multi-valued objects.

        Parameters
        key: string
        The key prefix.
        value: goog.uri.utils.QueryValue
        The value to serialize.
        pairs: !Array.<string>
        The array to which the 'key=value' strings + should be appended.
        code »goog.uri.utils.appendParam ( uri, key, opt_value )string

        Appends a single URI parameter. Repeated calls to this can exhibit quadratic behavior in IE6 due to the way string append works, though it should be limited given the 2kb limit.

        Parameters
        uri: string
        The original URI, which may already have query data.
        key: string
        The key, which must already be URI encoded.
        opt_value: *=
        The value, which will be stringized and encoded (assumed not already to be encoded). If omitted, undefined, or null, the - key will be added as a valueless parameter.
        Returns
        The URI with the query parameter added.

        Appends URI parameters to an existing URI. + key will be added as a valueless parameter.Returns

        The URI with the query parameter added.

        Appends URI parameters to an existing URI. The variable arguments may contain alternating keys and values. Keys are assumed to be already URI encoded. The values should not be URI-encoded, @@ -44,69 +44,70 @@ A single call to this function will not exhibit quadratic behavior in IE, whereas multiple repeated calls may, although the effect is limited by - fact that URL's generally can't exceed 2kb.

        Parameters
        uri: string
        The original URI, which may already have query data.
        var_args: ...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)
        An array or argument list conforming to goog.uri.utils.QueryArray.
        Returns
        The URI with all query parameters added.

        Appends query parameters from a map.

        Parameters
        uri: string
        The original URI, which may already have query data.
        map: Object
        An object where keys are URI-encoded parameter keys, + fact that URL's generally can't exceed 2kb.
        Parameters
        uri: string
        The original URI, which may already have query data.
        var_args: ...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)
        An array or argument list conforming to goog.uri.utils.QueryArray.
        Returns
        The URI with all query parameters added.

        Appends query parameters from a map.

        Parameters
        uri: string
        The original URI, which may already have query data.
        map: Object
        An object where keys are URI-encoded parameter keys, and the values are arbitrary types or arrays. Keys with a null value - are dropped.
        Returns
        The new parameters.

        Generates a URI path using a given URI and a path with checks to + are dropped.

        Returns
        The new parameters.

        Generates a URI path using a given URI and a path with checks to prevent consecutive "//". The baseUri passed in must not contain query or fragment identifiers. The path to append may not contain query or - fragment identifiers.

        Parameters
        baseUri: string
        URI to use as the base.
        path: string
        Path to append.
        Returns
        Updated URI.

        Appends a URI and query data in a string buffer with special preconditions. + fragment identifiers.

        Parameters
        baseUri: string
        URI to use as the base.
        path: string
        Path to append.
        Returns
        Updated URI.

        Appends a URI and query data in a string buffer with special preconditions. Internal implementation utility, performing very few object allocations.

        Parameters
        buffer: !Array
        A string buffer. The first element must be the base URI, and may have a fragment identifier. If the array contains more than one element, the second element must be an ampersand, and may be overwritten, depending on the base URI. Undefined elements - are treated as empty-string.
        Returns
        The concatenated URI and query data.

        Asserts that there are no fragment or query identifiers, only in uncompiled + are treated as empty-string.Returns

        The concatenated URI and query data.

        Asserts that there are no fragment or query identifiers, only in uncompiled mode.

        Parameters
        uri: string
        The URI to examine.
        code »goog.uri.utils.buildFromEncodedParts ( opt_scheme, opt_userInfo, opt_domain, opt_port, opt_path, opt_queryData, opt_fragment )string

        Builds a URI string from already-encoded parts. No encoding is performed. Any component may be omitted as either null or undefined.

        Parameters
        opt_scheme: ?string=
        The scheme such as 'http'.
        opt_userInfo: ?string=
        The user name before the '@'.
        opt_domain: ?string=
        The domain such as 'www.google.com', already URI-encoded.
        opt_port: (string|number|null)=
        The port number.
        opt_path: ?string=
        The path, already URI-encoded. If it is not - empty, it must begin with a slash.
        opt_queryData: ?string=
        The URI-encoded query data.
        opt_fragment: ?string=
        The URI-encoded fragment identifier.
        Returns
        The fully combined URI.
        code »goog.uri.utils.buildQueryData ( keysAndValues, opt_startIndex )string

        Builds a query data string from a sequence of alternating keys and values. + empty, it must begin with a slash.

        opt_queryData: ?string=
        The URI-encoded query data.
        opt_fragment: ?string=
        The URI-encoded fragment identifier.Returns
        The fully combined URI.
        code »goog.uri.utils.buildQueryData ( keysAndValues, opt_startIndex )string

        Builds a query data string from a sequence of alternating keys and values. Currently generates "&key&" for empty args.

        Parameters
        keysAndValues: goog.uri.utils.QueryArray
        Alternating keys and - values. See the typedef.
        opt_startIndex: number=
        A start offset into the arary, defaults to 0.
        Returns
        The encoded query string, in the for 'a=1&b=2'.

        Builds a buffer of query data from a map.

        Parameters
        buffer: !Array
        A string buffer to append to. The + values. See the typedef.
        opt_startIndex: number=
        A start offset into the arary, defaults to 0.
        Returns
        The encoded query string, in the form 'a=1&b=2'.

        Builds a buffer of query data from a map.

        Parameters
        buffer: !Array
        A string buffer to append to. The first element appended will be an '&', and may be replaced by the caller.
        map: Object.<goog.uri.utils.QueryValue>
        An object where keys are URI-encoded parameter keys, and the values conform to the contract - specified in the goog.uri.utils.QueryValue typedef.
        Returns
        The buffer argument.
        code »goog.uri.utils.buildQueryDataBuffer_ ( buffer, keysAndValues, opt_startIndex )!Array

        Builds a buffer of query data from a sequence of alternating keys and values.

        Parameters
        buffer: !Array
        A string buffer to append to. The + specified in the goog.uri.utils.QueryValue typedef.
        Returns
        The buffer argument.
        code »goog.uri.utils.buildQueryDataBuffer_ ( buffer, keysAndValues, opt_startIndex )!Array

        Builds a buffer of query data from a sequence of alternating keys and values.

        Parameters
        buffer: !Array
        A string buffer to append to. The first element appended will be an '&', and may be replaced by the caller.
        keysAndValues: (goog.uri.utils.QueryArray|Arguments)
        An array with - alternating keys and values -- see the typedef.
        opt_startIndex: number=
        A start offset into the arary, defaults to 0.
        Returns
        The buffer argument.

        Builds a query data string from a map. + alternating keys and values -- see the typedef.

        opt_startIndex: number=
        A start offset into the arary, defaults to 0.Returns
        The buffer argument.

        Builds a query data string from a map. Currently generates "&key&" for empty args.

        Parameters
        map: Object
        An object where keys are URI-encoded parameter keys, and the values are arbitrary types or arrays. Keys with a null value - are dropped.
        Returns
        The encoded query string, in the for 'a=1&b=2'.
        Parameters
        uri: ?string
        A possibly null string.
        Returns
        The string URI-decoded, or null if uri is null.
        code »goog.uri.utils.findParam_ ( uri, startIndex, keyEncoded, hashOrEndIndex )number

        Finds the next instance of a query parameter with the specified name. + are dropped.Returns

        The encoded query string, in the form 'a=1&b=2'.
        code »goog.uri.utils.decodeIfPossible_ ( uri, opt_preserveReserved )?string
        Parameters
        uri: ?string
        A possibly null string.
        opt_preserveReserved: boolean=
        If true, percent-encoding of RFC-3986 + reserved characters will not be removed.
        Returns
        The string URI-decoded, or null if uri is null.
        code »goog.uri.utils.findParam_ ( uri, startIndex, keyEncoded, hashOrEndIndex )number

        Finds the next instance of a query parameter with the specified name. Does not instantiate any objects.

        Parameters
        uri: string
        The URI to search. May contain a fragment identifier if opt_hashIndex is specified.
        startIndex: number
        The index to begin searching for the key at. A match may be found even if this is one character after the ampersand.
        keyEncoded: string
        The URI-encoded key.
        hashOrEndIndex: number
        Index to stop looking at. If a hash mark is present, it should be its index, otherwise it should be the length of the string.
        Returns
        The position of the first character in the key's name, - immediately after either a question mark or a dot.

        Gets a URI component by index. + immediately after either a question mark or a dot.

        Gets a URI component by index. It is preferred to use the getPathEncoded() variety of functions ahead, since they are more readable.

        Parameters
        componentIndex: goog.uri.utils.ComponentIndex
        The component index.
        uri: string
        The URI to examine.
        Returns
        The still-encoded component, or null if the component - is not present.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded domain, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The domain name still encoded, or null if none.

        Gets the effective scheme for the URL. If the URL is relative then the - scheme is derived from the page's location.

        Parameters
        uri: string
        The URI to examine.
        Returns
        The protocol or scheme, always lower case.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded fragment identifier, or null if none. Does - not include the hash mark.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The fragment identifier, or null if none. Does not - include the hash mark itself.

        Extracts everything up to the port of the URI.

        Parameters
        uri: string
        The URI string.
        Returns
        Everything up to and including the port.

        Gets the first value of a query parameter.

        Parameters
        uri: string
        The URI to process. May contain a fragment.
        keyEncoded: string
        The URI-encoded key. Case-sensitive.
        Returns
        The first value of the parameter (URI-decoded), or null - if the parameter is not found.

        Gets all values of a query parameter.

        Parameters
        uri: string
        The URI to process. May contain a framgnet.
        keyEncoded: string
        The URI-encoded key. Case-snsitive.
        Returns
        All URI-decoded values with the given key. - If the key is not found, this will have length 0, but never be null.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded path, or null if none. Includes the leading - slash, if any.

        Extracts the path of the URL and everything after.

        Parameters
        uri: string
        The URI string.
        Returns
        The URI, starting at the path and including the query - parameters and fragment identifier.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The path still encoded, or null if none. Includes the - leading slash, if any.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The port number, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The query data still encoded, or null if none. Does not - include the question mark itself.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The protocol or scheme, or null if none. Does not - include trailing colons or slashes.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded user info, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The user name still encoded, or null if none.
        code »goog.uri.utils.hasParam ( uri, keyEncoded )boolean

        Determines if the URI contains a specific key. + is not present.

        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded domain, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The domain name still encoded, or null if none.

        Gets the effective scheme for the URL. If the URL is relative then the + scheme is derived from the page's location.

        Parameters
        uri: string
        The URI to examine.
        Returns
        The protocol or scheme, always lower case.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded fragment identifier, or null if none. Does + not include the hash mark.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The fragment identifier, or null if none. Does not + include the hash mark itself.

        Extracts everything up to the port of the URI.

        Parameters
        uri: string
        The URI string.
        Returns
        Everything up to and including the port.

        Gets the first value of a query parameter.

        Parameters
        uri: string
        The URI to process. May contain a fragment.
        keyEncoded: string
        The URI-encoded key. Case-sensitive.
        Returns
        The first value of the parameter (URI-decoded), or null + if the parameter is not found.

        Gets all values of a query parameter.

        Parameters
        uri: string
        The URI to process. May contain a framgnet.
        keyEncoded: string
        The URI-encoded key. Case-snsitive.
        Returns
        All URI-decoded values with the given key. + If the key is not found, this will have length 0, but never be null.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded path, or null if none. Includes the leading + slash, if any.

        Extracts the path of the URL and everything after.

        Parameters
        uri: string
        The URI string.
        Returns
        The URI, starting at the path and including the query + parameters and fragment identifier.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The path still encoded, or null if none. Includes the + leading slash, if any.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The port number, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The query data still encoded, or null if none. Does not + include the question mark itself.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The protocol or scheme, or null if none. Does not + include trailing colons or slashes.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The decoded user info, or null if none.
        Parameters
        uri: string
        The URI to examine.
        Returns
        The user name still encoded, or null if none.
        code »goog.uri.utils.hasParam ( uri, keyEncoded )boolean

        Determines if the URI contains a specific key. Performs no object instantiations.

        Parameters
        uri: string
        The URI to process. May contain a fragment - identifier.
        keyEncoded: string
        The URI-encoded key. Case-sensitive.
        Returns
        Whether the key is present.

        Ensures that two URI's have the exact same domain, scheme, and port. + identifier.

        keyEncoded: string
        The URI-encoded key. Case-sensitive.Returns
        Whether the key is present.

        Ensures that two URI's have the exact same domain, scheme, and port. Unlike the version in goog.Uri, this checks protocol, and therefore is - suitable for checking against the browser's same-origin policy.

        Parameters
        uri1: string
        The first URI.
        uri2: string
        The second URI.
        Returns
        Whether they have the same domain and port.

        Sets the zx parameter of a URI to a random value.

        Parameters
        uri: string
        Any URI.
        Returns
        That URI with the "zx" parameter added or replaced to - contain a random string.

        Check to see if the user is being phished.

        Gets the URI with the fragment identifier removed.

        Parameters
        uri: string
        The URI to examine.
        Returns
        Everything preceding the hash mark.

        Removes all instances of a query parameter.

        Parameters
        uri: string
        The URI to process. Must not contain a fragment.
        keyEncoded: string
        The URI-encoded key.
        Returns
        The URI with all instances of the parameter removed.
        Parameters
        uri: string
        The URI to examine.
        fragment: ?string
        The encoded fragment identifier, or null if none. - Does not include the hash mark itself.
        Returns
        The URI with the fragment set.
        code »goog.uri.utils.setParam ( uri, keyEncoded, value )string

        Replaces all existing definitions of a parameter with a single definition. + suitable for checking against the browser's same-origin policy.

        Parameters
        uri1: string
        The first URI.
        uri2: string
        The second URI.
        Returns
        Whether they have the same scheme, domain and port.

        Sets the zx parameter of a URI to a random value.

        Parameters
        uri: string
        Any URI.
        Returns
        That URI with the "zx" parameter added or replaced to + contain a random string.

        Check to see if the user is being phished.

        Gets the URI with the fragment identifier removed.

        Parameters
        uri: string
        The URI to examine.
        Returns
        Everything preceding the hash mark.

        Removes all instances of a query parameter.

        Parameters
        uri: string
        The URI to process. Must not contain a fragment.
        keyEncoded: string
        The URI-encoded key.
        Returns
        The URI with all instances of the parameter removed.
        Parameters
        uri: string
        The URI to examine.
        fragment: ?string
        The encoded fragment identifier, or null if none. + Does not include the hash mark itself.
        Returns
        The URI with the fragment set.
        code »goog.uri.utils.setParam ( uri, keyEncoded, value )string

        Replaces all existing definitions of a parameter with a single definition. Repeated calls to this can exhibit quadratic behavior due to the need to find existing instances and reconstruct the string, though it should be limited given the 2kb limit. Consider using appendParams to append multiple parameters in bulk.

        Parameters
        uri: string
        The original URI, which may already have query data.
        keyEncoded: string
        The key, which must already be URI encoded.
        value: *
        The value, which will be stringized and encoded (assumed - not already to be encoded).
        Returns
        The URI with the query parameter added.

        Splits a URI into its component parts. + not already to be encoded).Returns

        The URI with the query parameter added.

        Replaces the path.

        Parameters
        uri: string
        URI to use as the base.
        path: string
        New path.
        Returns
        Updated URI.

        Splits a URI into its component parts. Each component can be accessed via the component indices; for example:

        @@ -115,7 +116,7 @@
              Each component that is present will contain the encoded value, whereas
              components that are not present will be undefined or empty, depending
              on the browser's regular expression implementation.  Never null, since
        -     arbitrary strings may still look like path names.

        Global Properties

        Regular expression for finding a hash mark or end of string.

        Safari has a nasty bug where if you have an http URL with a username, e.g., + arbitrary strings may still look like path names.

        Global Properties

        Regular expression for finding a hash mark or end of string.

        Safari has a nasty bug where if you have an http URL with a username, e.g., http://evil.com%2F@google.com/ Safari will report that window.location.href is http://evil.com/google.com/ @@ -189,4 +190,4 @@ $5 = /pub/ietf/uri/ path $6 = query without ? $7 = Related fragment without # -

        Regexp to find trailing question marks and ampersands.

        \ No newline at end of file +

        Regexp to find trailing question marks and ampersands.

        \ No newline at end of file diff --git a/docs/namespace_goog_userAgent.html b/docs/namespace_goog_userAgent.html index 6630a3f..f36462f 100644 --- a/docs/namespace_goog_userAgent.html +++ b/docs/namespace_goog_userAgent.html @@ -1,38 +1,35 @@ -goog.userAgent

        Namespace goog.userAgent

        code »
        Show:

        Global Functions

        Deprecated: Use goog.string.compareVersions.

        Compares two version numbers.

        Parameters
        v1: string
        Version of first item.
        v2: string
        Version of second item.
        Returns
        1 if first argument is higher +goog.userAgent

        Namespace goog.userAgent

        code »
        Show:

        Global Functions

        Deprecated: Use goog.string.compareVersions.

        Compares two version numbers.

        Parameters
        v1: string
        Version of first item.
        v2: string
        Version of second item.
        Returns
        1 if first argument is higher 0 if arguments are equal - -1 if second argument is higher.
        Returns
        the platform (operating system) the user agent is running + -1 if second argument is higher.
        Returns
        the platform (operating system) the user agent is running on. Default to empty string because navigator.platform may not be defined - (on Rhino, for example).
        Returns
        The string that describes the version number of the user - agent.
        Returns
        Returns the document mode (for testing).
        Returns
        The native navigator object.

        Returns the userAgent string for the current browser. - Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a - navigator object off the global scope. In that case we return null.

        Returns
        The userAgent string or null if there is none.

        Initialize the goog.userAgent constants that define which platform the user - agent is running on.

        Initializer for goog.userAgent. - - This is a named function so that it can be stripped via the jscompiler - option for stripping types.

        Deprecated alias to goog.userAgent.isDocumentModeOrHigher.

        Parameters
        version: number
        The version to check.
        Returns
        Whether the IE effective document mode is higher or the - same as the given version.

        Whether the IE effective document mode is higher or the same as the given + (on Rhino, for example).

        Returns
        The string that describes the version number of the user + agent.
        Returns
        Returns the document mode (for testing).

        TODO(nnaze): Change type to "Navigator" and update compilation targets.

        Returns
        The native navigator object.

        Returns the userAgent string for the current browser.

        Returns
        The userAgent string.

        Initialize the goog.userAgent constants that define which platform the user + agent is running on.

        Deprecated: Use goog.userAgent.isDocumentModeOrHigher().

        Deprecated alias to goog.userAgent.isDocumentModeOrHigher.

        Parameters
        version: number
        The version to check.
        Returns
        Whether the IE effective document mode is higher or the + same as the given version.

        Whether the IE effective document mode is higher or the same as the given document mode version. NOTE: Only for IE, return false for another browser.

        Parameters
        documentMode: number
        The document mode version to check.
        Returns
        Whether the IE effective document mode is higher or the - same as the given version.
        Deprecated: Use goog.userAgent.isVersionOrHigher().

        Deprecated alias to goog.userAgent.isVersionOrHigher.

        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the user agent version is higher or the same as - the given version.

        Whether the user agent version is higher or the same as the given version. + same as the given version.

        Whether the user agent is running on a mobile device. + + This is a separate function so that the logic can be tested. + + TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().

        Returns
        Whether the user agent is running on a mobile device.
        Deprecated: Use goog.userAgent.isVersionOrHigher().

        Deprecated alias to goog.userAgent.isVersionOrHigher.

        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the user agent version is higher or the same as + the given version.

        Whether the user agent version is higher or the same as the given version. NOTE: When checking the version numbers for Firefox and Safari, be sure to use the engine's version, not the browser's version number. For example, Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11. Opera and Internet Explorer versions match the product release number.

        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the user agent version is higher or the same as - the given version.

        Global Properties

        Whether the user agent is running on Android.

        Whether we know the browser engine at compile-time.

        For IE version < 7, documentMode is undefined, so attempt to use the + the given version.

        Global Properties

        Whether the user agent is running on Android.

        Whether we know the browser engine at compile-time.

        For IE version < 7, documentMode is undefined, so attempt to use the CSS1Compat property to see if we are in standards mode. If we are in standards mode, treat the browser version as the document mode. Otherwise, - IE is emulating version 5.

        Whether the user agent is Gecko. Gecko is the rendering engine used by - Mozilla, Mozilla Firefox, Camino and many more.

        Whether the user agent is Internet Explorer. This includes other browsers - using Trident as its rendering engine. For example AOL and Netscape 8

        Whether the user agent is running on an iPad.

        Whether the user agent is running on an iPhone.

        Whether the user agent is running on a Linux operating system.

        Whether the user agent is running on a Macintosh operating system.

        Whether the user agent is running on a mobile device.

        Whether the user agent is Opera.

        The platform (operating system) the user agent is running on. Default to + IE is emulating version 5.

        Whether the user agent is Gecko. Gecko is the rendering engine used by + Mozilla, Firefox, and others.

        Whether the user agent is Internet Explorer.

        Whether the user agent is running on an iPad.

        Whether the user agent is running on an iPhone.

        Whether the user agent is running on a Linux operating system.

        Whether the user agent is running on a Macintosh operating system.

        Whether the user agent is running on a mobile device. + + TODO(nnaze): Consider deprecating MOBILE when labs.userAgent + is promoted as the gecko/webkit logic is likely inaccurate.

        Whether the user agent is Opera.

        The platform (operating system) the user agent is running on. Default to empty string because navigator.platform may not be defined (on Rhino, for - example).

        Deprecated: Use goog.userAgent.product.SAFARI instead. - TODO(nicksantos): Delete this from goog.userAgent.

        Used while transitioning code to use WEBKIT instead.

        The version of the user agent. This is a string because it might contain - 'b' (as in beta) as well as multiple dots.

        Whether the user agent is WebKit. WebKit is the rendering engine that - Safari, Android and others use.

        Whether the user agent is running on a Windows operating system.

        Whether the user agent is running on a X11 windowing system.

        Whether the user agent is running on Android.

        Whether the user agent string denotes Gecko. Gecko is the rendering - engine used by Mozilla, Mozilla Firefox, Camino and many more.

        Whether the user agent is running on an iPad.

        Whether the user agent is running on an iPhone.

        Whether the user agent string denotes Internet Explorer. This includes - other browsers using Trident as its rendering engine. For example AOL - and Netscape 8

        Whether the user agent is running on a Linux operating system.

        Whether the user agent is running on a Macintosh operating system.

        Whether the user agent string denotes a mobile device.

        Whether the user agent string denotes Opera.

        Whether the user agent string denotes WebKit. WebKit is the rendering - engine that Safari, Android and others use.

        Whether the user agent is running on a Windows operating system.

        Whether the user agent is running on a X11 windowing system.

        Deprecated: Use goog.userAgent.product.SAFARI instead. + TODO(nicksantos): Delete this from goog.userAgent.

        Used while transitioning code to use WEBKIT instead.

        The version of the user agent. This is a string because it might contain + 'b' (as in beta) as well as multiple dots.

        Whether the user agent is WebKit. WebKit is the rendering engine that + Safari, Android and others use.

        Whether the user agent is running on a Windows operating system.

        Whether the user agent is running on a X11 windowing system.

        Whether the user agent is running on Android.

        Whether the user agent is running on an iPad.

        Whether the user agent is running on an iPhone.

        Whether the user agent is running on a Linux operating system.

        Whether the user agent is running on a Macintosh operating system.

        Whether the user agent is running on a Windows operating system.

        Whether the user agent is running on a X11 windowing system.

        Cache for goog.userAgent.isVersionOrHigher. Calls to compareVersions are surprisingly expensive and, as a browser's - version number is unlikely to change during a session, we cache the results.

        Compiler Constants

        \ No newline at end of file + version number is unlikely to change during a session, we cache the results.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_goog_userAgent_product.html b/docs/namespace_goog_userAgent_product.html index f06d154..4ad8688 100644 --- a/docs/namespace_goog_userAgent_product.html +++ b/docs/namespace_goog_userAgent_product.html @@ -6,4 +6,4 @@ http://developer.yahoo.com/yui/articles/gbs/

        Whether the user agent product version is higher or the same as the given version.

        Parameters
        version: (string|number)
        The version to check.
        Returns
        Whether the user agent product version is higher or the same as the given version.

        Global Properties

        Whether the code is running on the default browser on an Android phone.

        Whether the code is running on the Camino web browser.

        Whether the code is running on the Chrome web browser.

        Whether the code is running on the Firefox web browser.

        Whether the code is running on an IE web browser.

        Whether the code is running on an iPad.

        Whether the code is running on an iPhone or iPod touch.

        Whether the code is running on the Opera web browser.

        Whether we know the product type at compile-time.

        Whether the code is running on the Safari web browser.

        The version of the user agent. This is a string because it might contain - 'b' (as in beta) as well as multiple dots.

        Whether the code is running on the default browser on an Android phone.

        Whether the code is running on the Camino web browser.

        Whether the code is running on the Chrome web browser.

        Whether the code is running on the Firefox web browser.

        Whether the code is running on an iPad

        Whether the code is running on an iPhone or iPod touch.

        Whether the code is running on the Safari web browser.

        Compiler Constants

        \ No newline at end of file + 'b' (as in beta) as well as multiple dots.

        Whether the code is running on the default browser on an Android phone.

        Whether the code is running on the Camino web browser.

        Whether the code is running on the Chrome web browser.

        Whether the code is running on the Firefox web browser.

        Whether the code is running on an iPad

        Whether the code is running on an iPhone or iPod touch.

        Whether the code is running on the Safari web browser.

        Compiler Constants

        \ No newline at end of file diff --git a/docs/namespace_webdriver.html b/docs/namespace_webdriver.html index c4413ef..562a2c6 100644 --- a/docs/namespace_webdriver.html +++ b/docs/namespace_webdriver.html @@ -1,4 +1,30 @@ -webdriver

        Namespace webdriver

        code »

        Interfaces

        webdriver.CommandExecutor
        Handles the execution of webdriver.Command objects.

        Classes

        webdriver.AbstractBuilder
        Creates new webdriver.WebDriver clients.
        webdriver.ActionSequence
        Class for defining sequences of complex user interactions.
        webdriver.Alert
        Represents a modal dialog such as alert, confirm, or - prompt.
        webdriver.Builder
        No Description.
        webdriver.Capabilities
        No Description.
        webdriver.Command
        Describes a command to be executed by the WebDriverJS framework.
        webdriver.EventEmitter
        Object that can emit events for others to listen for.
        webdriver.FirefoxDomExecutor
        No Description.
        webdriver.Locator
        An element locator.
        webdriver.Session
        Contains information about a WebDriver session.
        webdriver.UnhandledAlertError
        An error returned to indicate that there is an unhandled modal dialog on the - current page.
        webdriver.WebDriver
        Creates a new WebDriver client, which provides control over a browser.
        webdriver.WebElement
        Represents a DOM element.

        Enumerations

        webdriver.Browser
        Recognized browser names.
        webdriver.Button
        Enumeration of the buttons used in the advanced interactions API.
        webdriver.Capability
        Common webdriver capability keys.
        webdriver.CommandName
        Enumeration of predefined names command names that all command processors - will support.
        webdriver.Key
        Representations of pressable keys that aren't text.
        Show:
        \ No newline at end of file +webdriver

        namespace webdriver

        Types

        ActionSequence

        Class for defining sequences of complex user interactions.

        +
        Alert

        Represents a modal dialog such as alert, confirm, or +prompt.

        +
        AlertPromise

        AlertPromise is a promise that will be fulfilled with an Alert.

        +
        Browser

        Recognized browser names.

        +
        Button

        Enumeration of the buttons used in the advanced interactions API.

        +
        Capabilities

        No description.

        +
        Capability

        Common webdriver capability keys.

        +
        Command

        Describes a command to be executed by the WebDriverJS framework.

        +
        CommandExecutor

        Handles the execution of WebDriver commands.

        +
        CommandName

        Enumeration of predefined names command names that all command processors +will support.

        +
        EventEmitter

        Object that can emit events for others to listen for.

        +
        FileDetector

        Used with WebElement#sendKeys on file +input elements (<input type="file">) to detect when the entered key +sequence defines the path to a file.

        +
        Key

        Representations of pressable keys that aren't text.

        +
        Locator

        An element locator.

        +
        Serializable

        Defines an object that can be asynchronously serialized to its WebDriver +wire representation.

        +
        Session

        Contains information about a WebDriver session.

        +
        TouchSequence

        Class for defining sequences of user touch interactions.

        +
        UnhandledAlertError

        An error returned to indicate that there is an unhandled modal dialog on the +current page.

        +
        WebDriver

        Creates a new WebDriver client, which provides control over a browser.

        +
        WebElement

        Represents a DOM element.

        +
        WebElementPromise

        WebElementPromise is a promise that will be fulfilled with a WebElement.

        +

        Type Definitions

        webdriver.ProxyConfig{proxyType: string}

        Describes how a proxy should be configured for a WebDriver session. +Proxy configuration object, as defined by the WebDriver wire protocol.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_By.html b/docs/namespace_webdriver_By.html index 342af00..dbadc13 100644 --- a/docs/namespace_webdriver_By.html +++ b/docs/namespace_webdriver_By.html @@ -1,26 +1,56 @@ -webdriver.By

        Namespace webdriver.By

        code »

        A collection of factory functions for creating webdriver.Locator - instances.

        Show:

        Type Definitions

        code »webdriver.By.Hash : ({className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})
        Short-hand expressions for the primary element locator strategies. - For example the following two statements are equivalent: -
        - var e1 = driver.findElement(webdriver.By.id('foo'));
        - var e2 = driver.findElement({id: 'foo'});
        - 
        - -

        Care should be taken when using JavaScript minifiers (such as the - Closure compiler), as locator hashes will always be parsed using - the un-obfuscated properties listed below.

        Global Functions

        Locates elements that have a specific class name. The returned locator - is equivalent to searching for elements with the CSS selector ".clazz".

        Parameters
        className: string
        The class name to search for.
        Returns
        The new locator.

        Locates elements using a CSS selector. For browsers that do not support - CSS selectors, WebDriver implementations may return an - invalid selector error. An - implementation may, however, emulate the CSS selector API.

        Parameters
        selector: string
        The CSS selector to use.
        Returns
        The new locator.

        Locates an element by its ID.

        Parameters
        id: string
        The ID to search for.
        Returns
        The new locator.

        Locates an elements by evaluating a - JavaScript expression. - The result of this expression must be an element or list of elements.

        Parameters
        script: !(string|Function)
        The script to execute.
        var_args: ...*
        The arguments to pass to the script.
        Returns
        A new, - JavaScript-based locator function.

        Locates link elements whose visible - text matches the given string.

        Parameters
        text: string
        The link text to search for.
        Returns
        The new locator.

        Locates elements whose name attribute has the given value.

        Parameters
        name: string
        The name attribute to search for.
        Returns
        The new locator.

        Locates link elements whose visible - text contains the given substring.

        Parameters
        text: string
        The substring to check for in a link's visible text.
        Returns
        The new locator.

        Locates elements with a given tag name. The returned locator is - equivalent to using the getElementsByTagName DOM function.

        Parameters
        text: string
        The substring to check for in a link's visible text.
        Returns
        The new locator.

        Locates elements matching a XPath selector. Care should be taken when - using an XPath selector with a webdriver.WebElement as WebDriver - will respect the context in the specified in the selector. For example, - given the selector "//div", WebDriver will search from the - document root regardless of whether the locator was used with a - WebElement.

        Parameters
        xpath: string
        The XPath selector to use.
        Returns
        The new locator.
        \ No newline at end of file +webdriver.By

        namespace webdriver.By

        A collection of factory functions for creating webdriver.Locator +instances.

        +

        Functions

        className(className)code »

        Locates elements that have a specific class name. The returned locator +is equivalent to searching for elements with the CSS selector ".clazz".

        +
        Parameters
        classNamestring

        The class name to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        css(selector)code »

        Locates elements using a CSS selector. For browsers that do not support +CSS selectors, WebDriver implementations may return an +invalid selector error. An +implementation may, however, emulate the CSS selector API.

        +
        Parameters
        selectorstring

        The CSS selector to use.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        id(id)code »

        Locates an element by its ID.

        +
        Parameters
        idstring

        The ID to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        js(script, var_args)code »

        Locates an elements by evaluating a +JavaScript expression. +The result of this expression must be an element or list of elements.

        +
        Parameters
        script(string|Function)

        The script to execute.

        +
        var_args...*

        The arguments to pass to the script.

        +
        Returns
        function(webdriver.WebDriver): webdriver.promise.Promise

        A new, +JavaScript-based locator function.

        +

        linkText(text)code »

        Locates link elements whose visible +text matches the given string.

        +
        Parameters
        textstring

        The link text to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        name(name)code »

        Locates elements whose name attribute has the given value.

        +
        Parameters
        namestring

        The name attribute to search for.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        partialLinkText(text)code »

        Locates link elements whose visible +text contains the given substring.

        +
        Parameters
        textstring

        The substring to check for in a link's visible text.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        tagName(text)code »

        Locates elements with a given tag name. The returned locator is +equivalent to using the +getElementsByTagName +DOM function.

        +
        Parameters
        textstring

        The substring to check for in a link's visible text.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        xpath(xpath)code »

        Locates elements matching a XPath selector. Care should be taken when +using an XPath selector with a webdriver.WebElement as WebDriver +will respect the context in the specified in the selector. For example, +given the selector "//div", WebDriver will search from the +document root regardless of whether the locator was used with a +WebElement.

        +
        Parameters
        xpathstring

        The XPath selector to use.

        +
        Returns
        webdriver.Locator

        The new locator.

        +

        Type Definitions

        By.Hash({className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        Short-hand expressions for the primary element locator strategies. +For example the following two statements are equivalent:

        +
        var e1 = driver.findElement(webdriver.By.id('foo'));
        +var e2 = driver.findElement({id: 'foo'});
        +
        +

        Care should be taken when using JavaScript minifiers (such as the +Closure compiler), as locator hashes will always be parsed using +the un-obfuscated properties listed.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_http.html b/docs/namespace_webdriver_http.html index 19bb8c3..7cdabfe 100644 --- a/docs/namespace_webdriver_http.html +++ b/docs/namespace_webdriver_http.html @@ -1,4 +1,6 @@ -webdriver.http

        Namespace webdriver.http

        code »

        Interfaces

        webdriver.http.Client
        Interface used for sending individual HTTP requests to the server.

        Classes

        webdriver.http.CorsClient
        Communicates with a WebDriver server, which may be on a different domain, - using the cross-origin resource sharing - (CORS) extension to WebDriver's JSON wire protocol.
        webdriver.http.Executor
        A command executor that communicates with a server using the WebDriver - command protocol.
        webdriver.http.Request
        Describes a partial HTTP request.
        webdriver.http.Response
        Represents a HTTP response.
        webdriver.http.XhrClient
        A HTTP client that sends requests using XMLHttpRequests.
        Show:

        Global Functions

        Converts a headers object to a HTTP header block string.

        Parameters
        headers: !Object.<string>
        The headers object to convert.
        Returns
        The headers as a string.
        \ No newline at end of file +webdriver.http

        namespace webdriver.http

        Types

        Client

        Interface used for sending individual HTTP requests to the server.

        +
        Executor

        A command executor that communicates with a server using the WebDriver +command protocol.

        +
        Request

        Describes a partial HTTP request.

        +
        Response

        Represents a HTTP response.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_logging.html b/docs/namespace_webdriver_logging.html index 2cd469f..34e6fce 100644 --- a/docs/namespace_webdriver_logging.html +++ b/docs/namespace_webdriver_logging.html @@ -1,4 +1,70 @@ -webdriver.logging

        Namespace webdriver.logging

        code »

        Classes

        webdriver.logging.Entry
        A single log entry.

        Enumerations

        webdriver.logging.Level
        Logging levels.
        webdriver.logging.LevelName
        Log level names from WebDriver's JSON wire protocol.
        webdriver.logging.Type
        Common log types.
        Show:

        Type Definitions

        Global Functions

        Converts a level name or value to a webdriver.logging.Level value. - If the name/value is not recognized, webdriver.logging.Level.ALL - will be returned.

        Parameters
        nameOrValue: (number|string)
        The log level name, or value, to - convert .
        Returns
        The converted level.
        \ No newline at end of file +webdriver.logging

        namespace webdriver.logging

        Defines WebDriver's logging system. The logging system is +broken into major components: local and remote logging.

        +

        The local logging API, which is anchored by the +Logger class, is similar to Java's +logging API. Loggers, retrieved by webdriver.logging.getLogger, use +hierarchical, dot-delimited namespaces +(e.g. "" > "webdriver" > "webdriver.logging"). Recorded log messages are +represented by the LogRecord class. You +can capture log records by +attaching a handler function +to the desired logger. For convenience, you can quickly enable logging to +the console by simply calling +webdriver.logging.installConsoleHandler().

        +

        The remote logging API +allows you to retrieve logs from a remote WebDriver server. This API uses the +Preferences class to define desired log levels prior to create a +WebDriver session:

        +
        var prefs = new webdriver.logging.Preferences();
        +prefs.setLevel(webdriver.logging.Type.BROWSER,
        +               webdriver.logging.Level.DEBUG);
        +
        +var caps = webdriver.Capabilities.chrome();
        +caps.setLoggingPrefs(prefs);
        +// ...
        +
        +

        Remote log entries are represented by the Entry class and may be +retrieved via webdriver.WebDriver.Logs:

        +
        driver.manage().logs().get(webdriver.logging.Type.BROWSER)
        +    .then(function(entries) {
        +       entries.forEach(function(entry) {
        +         console.log('[%s] %s', entry.level.name, entry.message);
        +       });
        +    });
        +
        +

        NOTE: Only a few browsers support the remote logging API (notably +Firefox and Chrome). Firefox supports basic logging functionality, while +Chrome exposes robust +performance logging +options. Remote logging is still considered a non-standard feature, and the +APIs exposed by this module for it are non-frozen. Once logging is officially +defined by the W3C WebDriver spec, this +module will be updated to use a consistent API for local and remote logging.

        +

        Functions

        addConsoleHandler(opt_logger)code »

        Adds the console handler to the given logger. The console handler will log +all messages using the JavaScript Console API.

        +
        Parameters
        opt_logger?webdriver.logging.Logger=

        The logger to add the handler to; defaults +to the root logger.

        +

        getLevel(nameOrValue)code »

        Converts a level name or value to a webdriver.logging.Level value. +If the name/value is not recognized, webdriver.logging.Level.ALL +will be returned.

        +
        Parameters
        nameOrValue(number|string)

        The log level name, or value, to +convert .

        +
        Returns
        webdriver.logging.Level

        The converted level.

        +

        getLogger(opt_name)code »

        Finds a named logger.

        +
        Parameters
        opt_namestring=

        The dot-delimited logger name, such as +"webdriver.logging.Logger". Defaults to the name of the root logger.

        +
        Returns
        webdriver.logging.Logger

        The named logger.

        +

        installConsoleHandler()code »

        Installs the console log handler on the root logger.

        +

        removeConsoleHandler(opt_logger)code »

        Removes the console log handler from the given logger.

        +
        Parameters
        opt_logger?webdriver.logging.Logger=

        The logger to remove the handler from; defaults +to the root logger.

        +

        Types

        Entry

        A single log entry recorded by a WebDriver component, such as a remote +WebDriver server.

        +
        Level

        The Level class defines a set of standard logging levels that +can be used to control logging output.

        +
        LogRecord

        LogRecord objects are used to pass logging requests between +the logging framework and individual log Handlers.

        +
        Logger

        The Logger is an object used for logging debug messages.

        +
        Preferences

        Describes the log preferences for a WebDriver session.

        +
        Type

        No description.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_process.html b/docs/namespace_webdriver_process.html index eafcd04..b8d1b2e 100644 --- a/docs/namespace_webdriver_process.html +++ b/docs/namespace_webdriver_process.html @@ -5,4 +5,4 @@ object.

        Sets an environment value. If the new value is either null or undefined, the environment variable will be cleared.

        Parameters
        name: string
        The value to set.
        value: *
        The new value; will be coerced to a string.

        Global Properties

        Whether the current environment is using Node's native process object.

        The global process object to use. Will either be Node's global process object, or an approximation of it for use in a browser - environment.

        \ No newline at end of file + environment. \ No newline at end of file diff --git a/docs/namespace_webdriver_promise.html b/docs/namespace_webdriver_promise.html index e8f69c3..e172abd 100644 --- a/docs/namespace_webdriver_promise.html +++ b/docs/namespace_webdriver_promise.html @@ -1,79 +1,165 @@ -webdriver.promise

        Namespace webdriver.promise

        code »

        Classes

        webdriver.promise.CanceledTaskError_
        Special error used to signal when a task is canceled because a previous - task in the same frame failed.
        webdriver.promise.ControlFlow
        Handles the execution of scheduled tasks, each of which may be an - asynchronous operation.
        webdriver.promise.Deferred
        Represents a value that will be resolved at some point in the future.
        webdriver.promise.Frame_
        An execution frame within a webdriver.promise.ControlFlow.
        webdriver.promise.Node_
        A single node in an webdriver.promise.ControlFlow's task tree.
        webdriver.promise.Promise
        Represents the eventual value of a completed operation.
        webdriver.promise.Task_
        A task to be executed by a webdriver.promise.ControlFlow.
        Show:

        Global Functions

        Given an array of promises, will return a promise that will be fulfilled - with the fulfillment values of the input array's values. If any of the - input array's promises are rejected, the returned promise will be rejected - with the same reason.

        Parameters
        arr: !Array
        An array of - promises to wait on.
        Returns
        A promise that is - fulfilled with an array containing the fulfilled values of the - input array, or rejected with the same reason as the first - rejected value.
        code »webdriver.promise.asap ( value, callback, opt_errback )

        Invokes the appropriate callback function as soon as a promised - value is resolved. This function is similar to - webdriver.promise.when, except it does not return a new promise.

        Parameters
        value: *
        The value to observe.
        callback: Function
        The function to call when the value is - resolved successfully.
        opt_errback: Function=
        The function to call when the value is - rejected.

        Wraps a function that is assumed to be a node-style callback as its final - argument. This callback takes two arguments: an error value (which will be - null if the call succeeded), and the success value as the second argument. - If the call fails, the returned promise will be rejected, otherwise it will - be resolved with the result.

        Parameters
        fn: !Function
        The function to wrap.
        Returns
        A promise that will be resolved with the - result of the provided function's callback.
        Returns
        The currently active control flow.

        Creates a new control flow. The provided callback will be invoked as the - first task within the new flow, with the flow as its sole argument. Returns - a promise that resolves to the callback result.

        Parameters
        callback: function(!webdriver.promise.ControlFlow)
        The entry point - to the newly created flow.
        Returns
        A promise that resolves to the callback - result.

        Creates a new deferred object.

        Parameters
        opt_canceller: Function=
        Function to call when cancelling the - computation of this instance's value.
        Returns
        The new deferred object.

        Creates a promise that will be resolved at a set time in the future.

        Parameters
        ms: number
        The amount of time, in milliseconds, to wait before - resolving the promise.
        Returns
        The promise.
        code »<TYPE, SELF> webdriver.promise.filter ( arr, fn, opt_self )

        Calls a function for each element in an array, and if the function returns - true adds the element to a new array. - -

        If the return value of the filter function is a promise, this function - will wait for it to be fulfilled before determining whether to insert the - element into the new array. - -

        If the filter function throws or returns a rejected promise, the promise - returned by this function will be rejected with the same reason. Only the - first failure will be reported; all subsequent errors will be silently - ignored.

        Parameters
        arr: !(Array.<TYPE>|webdriver.promise.Promise)
        The - array to iterator over, or a promise that will resolve to said array.
        fn: function(this: SELF, TYPE, number, !Array.<TYPE>): (boolean|webdriver.promise.Promise.<boolean>)
        The function - to call for each element in the array.
        opt_self: SELF=
        The object to be used as the value of 'this' within - fn.

        Creates a promise that has been resolved with the given value.

        Parameters
        opt_value: *=
        The resolved value.
        Returns
        The resolved promise.
        Parameters
        obj: !(Array|Object)
        the object to resolve.
        Returns
        A promise that will be resolved with the - input object once all of its values have been fully resolved.
        Parameters
        value: *
        The value to fully resolve. If a promise, assumed to - already be resolved.
        Returns
        A promise for a fully resolved version - of the input value.

        Returns a promise that will be resolved with the input value in a - fully-resolved state. If the value is an array, each element will be fully - resolved. Likewise, if the value is an object, all keys will be fully - resolved. In both cases, all nested arrays and objects will also be - fully resolved. All fields are resolved in place; the returned promise will - resolve on value and not a copy. - - Warning: This function makes no checks against objects that contain - cyclical references: -

        
        -   var value = {};
        -   value['self'] = value;
        -   webdriver.promise.fullyResolved(value);  // Stack overflow.
        - 
        Parameters
        value: *
        The value to fully resolve.
        Returns
        A promise for a fully resolved version - of the input value.

        Tests if a value is an Error-like object. This is more than an straight - instanceof check since the value may originate from another context.

        Parameters
        value: *
        The value to test.
        Returns
        Whether the value is an error.

        Determines whether a value should be treated as a promise. - Any object whose "then" property is a function will be considered a promise.

        Parameters
        value: *
        The value to test.
        Returns
        Whether the value is a promise.
        code »<TYPE, SELF> webdriver.promise.map ( arr, fn, opt_self )

        Calls a function for each element in an array and inserts the result into a - new array, which is used as the fulfillment value of the promise returned - by this function. - -

        If the return value of the mapping function is a promise, this function - will wait for it to be fulfilled before inserting it into the new array. - -

        If the mapping function throws or returns a rejected promise, the - promise returned by this function will be rejected with the same reason. - Only the first failure will be reported; all subsequent errors will be - silently ignored.

        Parameters
        arr: !(Array.<TYPE>|webdriver.promise.Promise)
        The - array to iterator over, or a promise that will resolve to said array.
        fn: function(this: SELF, TYPE, number, !Array.<TYPE>): ?
        The - function to call for each element in the array. This function should - expect three arguments (the element, the index, and the array itself.
        opt_self: SELF=
        The object to be used as the value of 'this' within - fn.

        Creates a promise that has been rejected with the given reason.

        Parameters
        opt_reason: *=
        The rejection reason; may be any value, but is - usually an Error or a string.
        Returns
        The rejected promise.

        Changes the default flow to use when no others are active.

        Parameters
        flow: !webdriver.promise.ControlFlow
        The new default flow.
        Throws
        Error
        If the default flow is not currently active.
        code »webdriver.promise.when ( value, opt_callback, opt_errback )!webdriver.promise.Promise

        Registers an observer on a promised value, returning a new promise - that will be resolved when the value is. If value is not a promise, - then the return promise will be immediately resolved.

        Parameters
        value: *
        The value to observe.
        opt_callback: Function=
        The function to call when the value is - resolved successfully.
        opt_errback: Function=
        The function to call when the value is - rejected.
        Returns
        A new promise.

        Global Properties

        A stack of active control flows, with the top of the stack used to schedule - commands. When there are multiple flows on the stack, the flow at index N - represents a callback triggered within a task owned by the flow at index - N-1.

        The default flow to use if no others are active.

        \ No newline at end of file +webdriver.promise

        namespace webdriver.promise

        A promise implementation based on the CommonJS promise/A and +promise/B proposals. For more information, see +http://wiki.commonjs.org/wiki/Promises.

        +

        Functions

        <T> all(arr)code »

        Given an array of promises, will return a promise that will be fulfilled +with the fulfillment values of the input array's values. If any of the +input array's promises are rejected, the returned promise will be rejected +with the same reason.

        +
        Parameters
        arrArray<(T|webdriver.promise.Promise<T>)>

        An array of +promises to wait on.

        +
        Returns
        webdriver.promise.Promise<Array<T>>

        A promise that is +fulfilled with an array containing the fulfilled values of the +input array, or rejected with the same reason as the first +rejected value.

        +

        asap(value, callback, opt_errback)code »

        Invokes the appropriate callback function as soon as a promised +value is resolved. This function is similar to +webdriver.promise.when, except it does not return a new promise.

        +
        Parameters
        value*

        The value to observe.

        +
        callbackFunction

        The function to call when the value is +resolved successfully.

        +
        opt_errback?Function=

        The function to call when the value is +rejected.

        +

        captureStackTrace(name, msg, topFn)code »

        Generates an error to capture the current stack trace.

        +
        Parameters
        namestring

        Error name for this stack trace.

        +
        msgstring

        Message to record.

        +
        topFnFunction

        The function that should appear at the top of the +stack; only applicable in V8.

        +
        Returns
        Error

        The generated error.

        +

        checkedNodeCall(fn, var_args)code »

        Wraps a function that expects a node-style callback as its final +argument. This callback expects two arguments: an error value (which will be +null if the call succeeded), and the success value as the second argument. +The callback will the resolve or reject the returned promise, based on its arguments.

        +
        Parameters
        fnFunction

        The function to wrap.

        +
        var_args...?

        The arguments to apply to the function, excluding the +final callback.

        +
        Returns
        webdriver.promise.Promise

        A promise that will be resolved with the +result of the provided function's callback.

        +

        consume(generatorFn, opt_self, var_args)code »

        Consumes a GeneratorFunction. Each time the generator yields a +promise, this function will wait for it to be fulfilled before feeding the +fulfilled value back into next. Likewise, if a yielded promise is +rejected, the rejection error will be passed to throw.

        +

        Example 1: the Fibonacci Sequence.

        +
        promise.consume(function* fibonacci() {
        +  var n1 = 1, n2 = 1;
        +  for (var i = 0; i < 4; ++i) {
        +    var tmp = yield n1 + n2;
        +    n1 = n2;
        +    n2 = tmp;
        +  }
        +  return n1 + n2;
        +}).then(function(result) {
        +  console.log(result);  // 13
        +});
        +
        +

        Example 2: a generator that throws.

        +
        promise.consume(function* () {
        +  yield promise.delayed(250).then(function() {
        +    throw Error('boom');
        +  });
        +}).thenCatch(function(e) {
        +  console.log(e.toString());  // Error: boom
        +});
        +
        +
        Parameters
        generatorFnFunction

        The generator function to execute.

        +
        opt_self?Object=

        The object to use as "this" when invoking the +initial generator.

        +
        var_args...*

        Any arguments to pass to the initial generator.

        +
        Returns
        webdriver.promise.Promise<?>

        A promise that will resolve to the +generator's final result.

        +
        Throws
        TypeError

        If the given function is not a generator.

        +

        controlFlow()code »

        Returns
        webdriver.promise.ControlFlow

        The currently active control flow.

        +

        createFlow(callback)code »

        Creates a new control flow. The provided callback will be invoked as the +first task within the new flow, with the flow as its sole argument. Returns +a promise that resolves to the callback result.

        +
        Parameters
        callbackfunction(webdriver.promise.ControlFlow): ?

        The entry point +to the newly created flow.

        +
        Returns
        webdriver.promise.Promise

        A promise that resolves to the callback +result.

        +

        <T> defer()code »

        Creates a new deferred object.

        +
        Returns
        webdriver.promise.Deferred<T>

        The new deferred object.

        +

        delayed(ms)code »

        Creates a promise that will be resolved at a set time in the future.

        +
        Parameters
        msnumber

        The amount of time, in milliseconds, to wait before +resolving the promise.

        +
        Returns
        webdriver.promise.Promise

        The promise.

        +

        <TYPE, SELF> filter(arr, fn, opt_self)code »

        Calls a function for each element in an array, and if the function returns +true adds the element to a new array.

        +

        If the return value of the filter function is a promise, this function +will wait for it to be fulfilled before determining whether to insert the +element into the new array.

        +

        If the filter function throws or returns a rejected promise, the promise +returned by this function will be rejected with the same reason. Only the +first failure will be reported; all subsequent errors will be silently +ignored.

        +
        Parameters
        arr(Array<TYPE>|webdriver.promise.Promise<Array<TYPE>>)

        The +array to iterator over, or a promise that will resolve to said array.

        +
        fnfunction(this: SELF, TYPE, number, Array<TYPE>): ?(boolean|webdriver.promise.Promise<boolean>)

        The function +to call for each element in the array.

        +
        opt_self?SELF=

        The object to be used as the value of 'this' within +fn.

        +

        <T> fulfilled(opt_value)code »

        Creates a promise that has been resolved with the given value.

        +
        Parameters
        opt_value?T=

        The resolved value.

        +
        Returns
        webdriver.promise.Promise<T>

        The resolved promise.

        +

        fullyResolved(value)code »

        Returns a promise that will be resolved with the input value in a +fully-resolved state. If the value is an array, each element will be fully +resolved. Likewise, if the value is an object, all keys will be fully +resolved. In both cases, all nested arrays and objects will also be +fully resolved. All fields are resolved in place; the returned promise will +resolve on value and not a copy.

        +

        Warning: This function makes no checks against objects that contain +cyclical references:

        +
        var value = {};
        +value['self'] = value;
        +promise.fullyResolved(value);  // Stack overflow.
        +
        +
        Parameters
        value*

        The value to fully resolve.

        +
        Returns
        webdriver.promise.Promise

        A promise for a fully resolved version +of the input value.

        +

        isGenerator(fn)code »

        Tests is a function is a generator.

        +
        Parameters
        fnFunction

        The function to test.

        +
        Returns
        boolean

        Whether the function is a generator.

        +

        isPromise(value)code »

        Determines whether a value should be treated as a promise. +Any object whose "then" property is a function will be considered a promise.

        +
        Parameters
        value*

        The value to test.

        +
        Returns
        boolean

        Whether the value is a promise.

        +

        <TYPE, SELF> map(arr, fn, opt_self)code »

        Calls a function for each element in an array and inserts the result into a +new array, which is used as the fulfillment value of the promise returned +by this function.

        +

        If the return value of the mapping function is a promise, this function +will wait for it to be fulfilled before inserting it into the new array.

        +

        If the mapping function throws or returns a rejected promise, the +promise returned by this function will be rejected with the same reason. +Only the first failure will be reported; all subsequent errors will be +silently ignored.

        +
        Parameters
        arr(Array<TYPE>|webdriver.promise.Promise<Array<TYPE>>)

        The +array to iterator over, or a promise that will resolve to said array.

        +
        fnfunction(this: SELF, TYPE, number, Array<TYPE>): ?

        The +function to call for each element in the array. This function should +expect three arguments (the element, the index, and the array itself.

        +
        opt_self?SELF=

        The object to be used as the value of 'this' within +fn.

        +

        <T> rejected(opt_reason)code »

        Creates a promise that has been rejected with the given reason.

        +
        Parameters
        opt_reason*=

        The rejection reason; may be any value, but is +usually an Error or a string.

        +
        Returns
        webdriver.promise.Promise<T>

        The rejected promise.

        +

        setDefaultFlow(flow)code »

        Changes the default flow to use when no others are active.

        +
        Parameters
        flowwebdriver.promise.ControlFlow

        The new default flow.

        +
        Throws
        Error

        If the default flow is not currently active.

        +

        when(value, opt_callback, opt_errback)code »

        Registers an observer on a promised value, returning a new promise +that will be resolved when the value is. If value is not a promise, +then the return promise will be immediately resolved.

        +
        Parameters
        value*

        The value to observe.

        +
        opt_callback?Function=

        The function to call when the value is +resolved successfully.

        +
        opt_errback?Function=

        The function to call when the value is +rejected.

        +
        Returns
        webdriver.promise.Promise

        A new promise.

        +

        Compiler Constants

        LONG_STACK_TRACESboolean

        Whether to append traces of then to rejection +errors.

        +

        Types

        CancellationError

        Error used when the computation of a promise is cancelled.

        +
        ControlFlow

        Handles the execution of scheduled tasks, each of which may be an +asynchronous operation.

        +
        Deferred

        Represents a value that will be resolved at some point in the future.

        +
        Promise

        Represents the eventual value of a completed operation.

        +
        Thenable

        Thenable is a promise-like object with a then method which may be +used to schedule callbacks on a promised value.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_stacktrace.html b/docs/namespace_webdriver_stacktrace.html index 3eb1898..bd526fd 100644 --- a/docs/namespace_webdriver_stacktrace.html +++ b/docs/namespace_webdriver_stacktrace.html @@ -1,39 +1,17 @@ -webdriver.stacktrace

        Namespace webdriver.stacktrace

        code »

        Classes

        webdriver.stacktrace.Frame
        Class representing one stack frame.
        webdriver.stacktrace.Snapshot
        Stores a snapshot of the stack trace at the time this instance was created.
        Show:

        Global Functions

        code »webdriver.stacktrace.format ( error )!(Error|goog.testing.JsUnitException)

        Formats an error's stack trace.

        Parameters
        error: !(Error|goog.testing.JsUnitException)
        The error to format.
        Returns
        The formatted error.

        Gets the native stack trace if available otherwise follows the call chain. - The generated trace will exclude all frames up to and including the call to - this function.

        Returns
        The frames of the stack trace.

        Get an error's stack trace with the error string trimmed. - V8 prepends the string representation of an error to its stack trace. - This function trims the string so that the stack trace can be parsed - consistently with the other JS engines.

        Parameters
        error: !(Error|goog.testing.JsUnitException)
        The error.
        Returns
        The stack trace string.

        Parses a long firefox stack frame.

        Parameters
        frameStr: string
        The stack frame as string.
        Returns
        Stack frame object.

        Parses one stack frame.

        Parameters
        frameStr: string
        The stack frame as string.
        Returns
        Stack frame object or null if the - parsing failed.

        Parses an Error object's stack trace.

        Parameters
        stack: string
        The stack trace.
        Returns
        Stack frames. The - unrecognized frames will be nulled out.

        Global Properties

        Representation of an anonymous frame in a stack trace generated by - goog.testing.stacktrace.

        Whether the current browser supports stack traces.

        Whether the current environment supports the Error.captureStackTrace - function (as of 10/17/2012, only V8).

        RegExp pattern for function call in a Chakra (IE) stack trace. This - expression allows for identifiers like 'Anonymous function', 'eval code', - and 'Global code'.

        Regular expression for parsing on stack frame in Chakra (IE).

        Pattern for a function call in a Closure stack trace. Creates three optional - submatches: the context, function name, and alias.

        Regular expression for parsing a stack frame generated by Closure's - goog.testing.stacktrace.

        Pattern for a matching the type on a fully-qualified name. Forms an - optional sub-match on the type. For example, in "foo.bar.baz", will match on - "foo.bar".

        RegExp pattern for function call in the Firefox stack trace. - Creates a submatch for the function name.

        RegExp pattern for function names in the Firefox stack trace. - Firefox has extended identifiers to deal with inner functions and anonymous - functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9

        Regular expression for parsing one stack frame in Firefox.

        RegExp pattern for JavaScript identifiers. We don't support Unicode - identifiers defined in ECMAScript v3.

        Maximum length of a string that can be matched with a RegExp on - Firefox 3x. Exceeding this approximate length will cause string.match - to exceed Firefox's stack quota. This situation can be encountered - when goog.globalEval is invoked with a long argument; such as - when loading a module.

        RegExp pattern for an anonymous function call in an Opera stack frame. - Creates 2 (optional) submatches: the context object and function name.

        RegExp pattern for a function call in an Opera stack frame. - Creates 3 (optional) submatches: the function name (if not anonymous), - the aliased context object and the function name (if anonymous).

        Regular expression for parsing on stack frame in Opera 11.68+

        Pattern for matching a fully qualified name. Will create two sub-matches: - the type (optional), and the name. For example, in "foo.bar.baz", will - match on ["foo.bar", "baz"].

        Placeholder for an unparsable frame in a stack trace generated by - goog.testing.stacktrace.

        RegExp pattern for an URL + position inside the file.

        RegExp pattern for function name alias in the V8 stack trace.

        RegExp pattern for the context of a function call in V8. Creates two - submatches, only one of which will ever match: either the namespace - identifier (with optional "new" keyword in the case of a constructor call), - or just the "new " phrase for a top level constructor call.

        RegExp pattern for function call in the V8 stack trace. - Creates 3 submatches with context object (optional), function name and - function alias (optional).

        RegExp pattern for function names and constructor calls in the V8 stack - trace.

        RegExp pattern for a location string in a V8 stack frame. Creates two - submatches for the location, one for enclosed in parentheticals and on - where the location appears alone (which will only occur if the location is - the only information in the frame).

        Regular expression for parsing one stack frame in V8.

        \ No newline at end of file +webdriver.stacktrace

        namespace webdriver.stacktrace

        Functions

        format(error)code »

        Formats an error's stack trace.

        +
        Parameters
        errorError

        The error to format.

        +
        Returns
        Error

        The formatted error.

        +

        get()code »

        Gets the native stack trace if available otherwise follows the call chain. +The generated trace will exclude all frames up to and including the call to +this function.

        +
        Returns
        Array<webdriver.stacktrace.Frame>

        The frames of the stack trace.

        +

        getStack(error)code »

        Get an error's stack trace with the error string trimmed. +V8 prepends the string representation of an error to its stack trace. +This function trims the string so that the stack trace can be parsed +consistently with the other JS engines.

        +
        Parameters
        errorError

        The error.

        +
        Returns
        string

        The stack trace string.

        +

        Properties

        BROWSER_SUPPORTEDboolean

        Whether the current browser supports stack traces.

        +

        Types

        Frame

        Class representing one stack frame.

        +
        Snapshot

        Stores a snapshot of the stack trace at the time this instance was created.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_testing.html b/docs/namespace_webdriver_testing.html index e077f7b..07bbe37 100644 --- a/docs/namespace_webdriver_testing.html +++ b/docs/namespace_webdriver_testing.html @@ -1 +1,7 @@ -webdriver.testing

        Namespace webdriver.testing

        code »

        Classes

        webdriver.testing.Assertion
        Utility for performing assertions against a given value.
        webdriver.testing.ContainsMatcher
        Accepts strins or array-like structures that contain value.
        webdriver.testing.NegatedAssertion
        An assertion that negates any applied matchers.
        Show:

        Global Functions

        Creates a new assertion.

        Parameters
        value: *
        The value to perform an assertion on.
        Returns
        The new assertion.
        \ No newline at end of file +webdriver.testing
        \ No newline at end of file diff --git a/docs/namespace_webdriver_testing_assert.html b/docs/namespace_webdriver_testing_assert.html index 91e04b6..cc00e42 100644 --- a/docs/namespace_webdriver_testing_assert.html +++ b/docs/namespace_webdriver_testing_assert.html @@ -1,3 +1,9 @@ -webdriver.testing.assert

        Namespace webdriver.testing.assert

        code »

        Creates a new assertion.

        Main

        assert ( value )!webdriver.testing.Assertion
        Parameters
        value: *
        The value to perform an assertion on.
        Returns
        The new assertion.
        Show:

        Global Functions

        Registers a new assertion to expose from the - webdriver.testing.Assertion prototype.

        Parameters
        name: string
        The assertion name.
        matcherTemplate: (function(new: goog.labs.testing.Matcher, *)|{matches: function(*): boolean, describe: function(): string})
        Either the - matcher constructor to use, or an object literal defining a matcher.
        \ No newline at end of file +webdriver.testing.assert

        namespace webdriver.testing.assert

        Creates a new assertion.

        +

        webdriver.testing.assert(value)

        Parameters
        value*

        The value to perform an assertion on.

        +
        Returns
        webdriver.testing.Assertion

        The new assertion.

        +

        Functions

        register(name, matcherTemplate)code »

        Registers a new assertion to expose from the +webdriver.testing.Assertion prototype.

        +
        Parameters
        namestring

        The assertion name.

        +
        matcherTemplate(function(new: goog.labs.testing.Matcher, *): ?|{describe: function(): string, matches: function(*): boolean})

        Either the +matcher constructor to use, or an object literal defining a matcher.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_testing_asserts.html b/docs/namespace_webdriver_testing_asserts.html index 1050173..7b59d1b 100644 --- a/docs/namespace_webdriver_testing_asserts.html +++ b/docs/namespace_webdriver_testing_asserts.html @@ -1,10 +1,18 @@ -webdriver.testing.asserts

        Namespace webdriver.testing.asserts

        code »
        Show:

        Global Functions

        code »webdriver.testing.asserts.assertThat ( failureMessageOrActualValue, actualValueOrMatcher, opt_matcher )!webdriver.promise.Promise
        Deprecated: Use webdriver.testing.asserts.assert instead.

        Asserts that a matcher accepts a given value. This function has two - signatures based on the number of arguments: - - Two arguments: - assertThat(actualValue, matcher) - Three arguments: - assertThat(failureMessage, actualValue, matcher)

        Parameters
        failureMessageOrActualValue: *
        Either a failure message or the value - to apply to the given matcher.
        actualValueOrMatcher: *
        Either the value to apply to the given - matcher, or the matcher itself.
        opt_matcher: goog.labs.testing.Matcher=
        The matcher to use; - ignored unless this function is invoked with three arguments.
        Returns
        The assertion result.

        Creates an equality matcher.

        Parameters
        expected: *
        The expected value.
        Returns
        The new matcher.
        \ No newline at end of file +webdriver.testing.asserts

        namespace webdriver.testing.asserts

        Functions

        assertThat(failureMessageOrActualValue, actualValueOrMatcher, opt_matcher)code »

        deprecated

        Asserts that a matcher accepts a given value. This function has two +signatures based on the number of arguments:

        +

        Two arguments: +assertThat(actualValue, matcher) +Three arguments: +assertThat(failureMessage, actualValue, matcher)

        +
        Deprecated

        Use webdriver.testing.asserts.assert instead.

        +
        Parameters
        failureMessageOrActualValue*

        Either a failure message or the value +to apply to the given matcher.

        +
        actualValueOrMatcher*

        Either the value to apply to the given +matcher, or the matcher itself.

        +
        opt_matcher?goog.labs.testing.Matcher=

        The matcher to use; +ignored unless this function is invoked with three arguments.

        +
        Returns
        webdriver.promise.Promise

        The assertion result.

        +

        equalTo(expected)code »

        Creates an equality matcher.

        +
        Parameters
        expected*

        The expected value.

        +
        Returns
        goog.labs.testing.Matcher

        The new matcher.

        +
        \ No newline at end of file diff --git a/docs/namespace_webdriver_until.html b/docs/namespace_webdriver_until.html new file mode 100644 index 0000000..fdc7557 --- /dev/null +++ b/docs/namespace_webdriver_until.html @@ -0,0 +1,82 @@ +webdriver.until

        namespace webdriver.until

        Functions

        ableToSwitchToFrame(frame)code »

        Creates a condition that will wait until the input driver is able to switch +to the designated frame. The target frame may be specified as

        +
        1. a numeric index into +window.frames +for the currently selected frame.
        2. a webdriver.WebElement, which must reference a FRAME or IFRAME +element on the current page.
        3. a locator which may be used to first locate a FRAME or IFRAME on the +current page before attempting to switch to it.
        +

        Upon successful resolution of this condition, the driver will be left +focused on the new frame.

        +
        Parameters
        frame(number|webdriver.WebElement|webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The frame identifier.

        +
        Returns
        webdriver.until.Condition<boolean>

        A new condition.

        +

        alertIsPresent()code »

        Creates a condition that waits for an alert to be opened. Upon success, the +returned promise will be fulfilled with the handle for the opened alert.

        +
        Returns
        webdriver.until.Condition<webdriver.Alert>

        The new condition.

        +

        elementIsDisabled(element)code »

        Creates a condition that will wait for the given element to be disabled.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsEnabled(element)code »

        Creates a condition that will wait for the given element to be enabled.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsNotSelected(element)code »

        Creates a condition that will wait for the given element to be deselected.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsNotVisible(element)code »

        Creates a condition that will wait for the given element to be in the DOM, +yet not visible to the user.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsSelected(element)code »

        Creates a condition that will wait for the given element to be selected.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementIsVisible(element)code »

        Creates a condition that will wait for the given element to become visible.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementLocated(locator)code »

        Creates a condition that will loop until an element is +found with the given locator.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +to use.

        +

        elementTextContains(element, substr)code »

        Creates a condition that will wait for the given element's +visible text to contain the given +substring.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        substrstring

        The substring to search for.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementTextIs(element, text)code »

        Creates a condition that will wait for the given element's +visible text to match the given +text exactly.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        textstring

        The expected text.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementTextMatches(element, regex)code »

        Creates a condition that will wait for the given element's +visible text to match a regular +expression.

        +
        Parameters
        elementwebdriver.WebElement

        The element to test.

        +
        regexRegExp

        The regular expression to test against.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        elementsLocated(locator)code »

        Creates a condition that will loop until at least one element is +found with the given locator.

        +
        Parameters
        locator(webdriver.Locator|{className: string}|{css: string}|{id: string}|{js: string}|{linkText: string}|{name: string}|{partialLinkText: string}|{tagName: string}|{xpath: string})

        The locator +to use.

        +

        stalenessOf(element)code »

        Creates a condition that will wait for the given element to become stale. An +element is considered stale once it is removed from the DOM, or a new page +has loaded.

        +
        Parameters
        elementwebdriver.WebElement

        The element that should become stale.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleContains(substr)code »

        Creates a condition that will wait for the current page's title to contain +the given substring.

        +
        Parameters
        substrstring

        The substring that should be present in the page +title.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleIs(title)code »

        Creates a condition that will wait for the current page's title to match the +given value.

        +
        Parameters
        titlestring

        The expected page title.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        titleMatches(regex)code »

        Creates a condition that will wait for the current page's title to match the +given regular expression.

        +
        Parameters
        regexRegExp

        The regular expression to test against.

        +
        Returns
        webdriver.until.Condition<boolean>

        The new condition.

        +

        Types

        Condition

        Defines a condition to

        +
        \ No newline at end of file diff --git a/docs/source/_base.js.src.html b/docs/source/_base.js.src.html index a8ca370..7fa1c82 100644 --- a/docs/source/_base.js.src.html +++ b/docs/source/_base.js.src.html @@ -1 +1 @@ -_base.js

        _base.js

        1// Copyright 2012 Selenium committers
        2// Copyright 2012 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview The base module responsible for bootstrapping the Closure
        18 * library and providing a means of loading Closure-based modules.
        19 *
        20 * <p>Each script loaded by this module will be granted access to this module's
        21 * {@code require} function; all required non-native modules must be specified
        22 * relative to <em>this</em> module.
        23 *
        24 * <p>This module will load all scripts from the "lib" subdirectory, unless the
        25 * SELENIUM_DEV_MODE environment variable has been set to 1, in which case all
        26 * scripts will be loaded from the Selenium client containing this script.
        27 */
        28
        29'use strict';
        30
        31var fs = require('fs'),
        32 path = require('path'),
        33 vm = require('vm');
        34
        35
        36/**
        37 * If this script was loaded from the Selenium project repo, it will operate in
        38 * development mode, adjusting how it loads Closure-based dependencies.
        39 * @type {boolean}
        40 */
        41var devMode = (function() {
        42 var buildDescFile = path.join(__dirname, '..', 'build.desc');
        43 return fs.existsSync(buildDescFile);
        44})();
        45
        46
        47/** @return {boolean} Whether this script was loaded in dev mode. */
        48function isDevMode() {
        49 return devMode;
        50}
        51
        52
        53/**
        54 * @type {string} Path to Closure's base file, relative to this module.
        55 * @const
        56 */
        57var CLOSURE_BASE_FILE_PATH = (function() {
        58 var relativePath = isDevMode() ?
        59 '../../../third_party/closure/goog/base.js' :
        60 './lib/goog/base.js';
        61 return path.join(__dirname, relativePath);
        62})();
        63
        64
        65/**
        66 * @type {string} Path to Closure's base file, relative to this module.
        67 * @const
        68 */
        69var DEPS_FILE_PATH = (function() {
        70 var relativePath = isDevMode() ?
        71 '../../../javascript/deps.js' :
        72 './lib/goog/deps.js';
        73 return path.join(__dirname, relativePath);
        74})();
        75
        76
        77
        78/**
        79 * Synchronously loads a script into the protected Closure context.
        80 * @param {string} src Path to the file to load.
        81 */
        82function loadScript(src) {
        83 src = path.normalize(src);
        84 var contents = fs.readFileSync(src, 'utf8');
        85 vm.runInContext(contents, closure, src);
        86}
        87
        88
        89/**
        90 * The protected context to host the Closure library.
        91 * @type {!Object}
        92 * @const
        93 */
        94var closure = vm.createContext({
        95 console: console,
        96 setTimeout: setTimeout,
        97 setInterval: setInterval,
        98 clearTimeout: clearTimeout,
        99 clearInterval: clearInterval,
        100 process: process,
        101 require: require,
        102 Buffer: Buffer,
        103 Error: Error,
        104 CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/',
        105 CLOSURE_IMPORT_SCRIPT: function(src) {
        106 loadScript(src);
        107 return true;
        108 },
        109 CLOSURE_NO_DEPS: !isDevMode(),
        110 goog: {}
        111});
        112closure.window = closure;
        113
        114
        115loadScript(CLOSURE_BASE_FILE_PATH);
        116loadScript(DEPS_FILE_PATH);
        117
        118
        119/**
        120 * Loads a symbol by name from the protected Closure context.
        121 * @param {string} symbol The symbol to load.
        122 * @return {?} The loaded symbol, or {@code null} if not found.
        123 * @throws {Error} If the symbol has not been defined.
        124 */
        125function closureRequire(symbol) {
        126 closure.goog.require(symbol);
        127 return closure.goog.getObjectByName(symbol);
        128}
        129
        130
        131// PUBLIC API
        132
        133
        134/**
        135 * Loads a symbol by name from the protected Closure context and exports its
        136 * public API to the provided object. This function relies on Closure code
        137 * conventions to define the public API of an object as those properties whose
        138 * name does not end with "_".
        139 * @param {string} symbol The symbol to load. This must resolve to an object.
        140 * @return {!Object} An object with the exported API.
        141 * @throws {Error} If the symbol has not been defined or does not resolve to
        142 * an object.
        143 */
        144exports.exportPublicApi = function(symbol) {
        145 var src = closureRequire(symbol);
        146 if (typeof src != 'object' || src === null) {
        147 throw Error('"' + symbol + '" must resolve to an object');
        148 }
        149
        150 var dest = {};
        151 Object.keys(src).forEach(function(key) {
        152 if (key[key.length - 1] != '_') {
        153 dest[key] = src[key];
        154 }
        155 });
        156
        157 return dest;
        158};
        159
        160
        161if (isDevMode()) {
        162 exports.closure = closure;
        163}
        164exports.isDevMode = isDevMode;
        165exports.require = closureRequire;
        \ No newline at end of file +_base.js

        _base.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview The base module responsible for bootstrapping the Closure
        20 * library and providing a means of loading Closure-based modules.
        21 *
        22 * <p>Each script loaded by this module will be granted access to this module's
        23 * {@code require} function; all required non-native modules must be specified
        24 * relative to <em>this</em> module.
        25 *
        26 * <p>This module will load all scripts from the "lib" subdirectory, unless the
        27 * SELENIUM_DEV_MODE environment variable has been set to 1, in which case all
        28 * scripts will be loaded from the Selenium client containing this script.
        29 */
        30
        31'use strict';
        32
        33var fs = require('fs'),
        34 path = require('path'),
        35 vm = require('vm');
        36
        37
        38/**
        39 * If this script was loaded from the Selenium project repo, it will operate in
        40 * development mode, adjusting how it loads Closure-based dependencies.
        41 * @type {boolean}
        42 */
        43var devMode = (function() {
        44 var buildDescFile = path.join(__dirname, '..', 'build.desc');
        45 return fs.existsSync(buildDescFile);
        46})();
        47
        48
        49/** @return {boolean} Whether this script was loaded in dev mode. */
        50function isDevMode() {
        51 return devMode;
        52}
        53
        54
        55/**
        56 * @type {string} Path to Closure's base file, relative to this module.
        57 * @const
        58 */
        59var CLOSURE_BASE_FILE_PATH = (function() {
        60 var relativePath = isDevMode() ?
        61 '../../../third_party/closure/goog/base.js' :
        62 './lib/goog/base.js';
        63 return path.join(__dirname, relativePath);
        64})();
        65
        66
        67/**
        68 * @type {string} Path to Closure's base file, relative to this module.
        69 * @const
        70 */
        71var DEPS_FILE_PATH = (function() {
        72 var relativePath = isDevMode() ?
        73 '../../../javascript/deps.js' :
        74 './lib/goog/deps.js';
        75 return path.join(__dirname, relativePath);
        76})();
        77
        78
        79/**
        80 * Maintains a unique context for Closure library-based code.
        81 * @param {boolean=} opt_configureForTesting Whether to configure a fake DOM
        82 * for Closure-testing code that (incorrectly) assumes a DOM is always
        83 * present.
        84 * @constructor
        85 */
        86function Context(opt_configureForTesting) {
        87 var closure = this.closure = vm.createContext({
        88 console: console,
        89 setTimeout: setTimeout,
        90 setInterval: setInterval,
        91 clearTimeout: clearTimeout,
        92 clearInterval: clearInterval,
        93 process: process,
        94 require: require,
        95 Buffer: Buffer,
        96 Error: Error,
        97 TypeError: TypeError,
        98 CLOSURE_BASE_PATH: path.dirname(CLOSURE_BASE_FILE_PATH) + '/',
        99 CLOSURE_IMPORT_SCRIPT: function(src, opt_srcText) {
        100 if (opt_srcText !== undefined) {
        101 // Windows paths use backslashes, which must be properly escaped before
        102 // evaluated with vm.runInContext.
        103 opt_srcText = opt_srcText.replace(/\\/g, '/');
        104 vm.runInContext(opt_srcText, closure, src);
        105 } else {
        106 loadScript(src);
        107 }
        108 return true;
        109 },
        110 CLOSURE_NO_DEPS: !isDevMode(),
        111 CLOSURE_UNCOMPILED_DEFINES: {'goog.json.USE_NATIVE_JSON': true},
        112 goog: {}
        113 });
        114 closure.window = closure.top = closure;
        115
        116 if (opt_configureForTesting) {
        117 closure.document = {
        118 body: {},
        119 createElement: function() { return {}; },
        120 getElementsByTagName: function() { return []; }
        121 };
        122 closure.document.body.ownerDocument = closure.document;
        123 }
        124
        125 loadScript(CLOSURE_BASE_FILE_PATH);
        126 loadScript(DEPS_FILE_PATH);
        127
        128 // Redefine retrieveAndExecModule_ to load modules. Closure's version
        129 // assumes XMLHttpRequest is defined (and by extension that scripts
        130 // are being loaded from a server).
        131 closure.goog.retrieveAndExecModule_ = function(src) {
        132 var normalizedSrc = path.normalize(src);
        133 var contents = fs.readFileSync(normalizedSrc, 'utf8');
        134 contents = closure.goog.wrapModule_(src, contents);
        135 vm.runInContext(contents, closure, normalizedSrc);
        136 };
        137
        138 /**
        139 * Synchronously loads a script into the protected Closure context.
        140 * @param {string} src Path to the file to load.
        141 */
        142 function loadScript(src) {
        143 src = path.normalize(src);
        144 var contents = fs.readFileSync(src, 'utf8');
        145 vm.runInContext(contents, closure, src);
        146 }
        147}
        148
        149
        150var context = new Context();
        151
        152
        153/**
        154 * Loads a symbol by name from the protected Closure context.
        155 * @param {string} symbol The symbol to load.
        156 * @return {?} The loaded symbol, or {@code null} if not found.
        157 * @throws {Error} If the symbol has not been defined.
        158 */
        159function closureRequire(symbol) {
        160 context.closure.goog.require(symbol);
        161 return context.closure.goog.getObjectByName(symbol);
        162}
        163
        164
        165// PUBLIC API
        166
        167
        168/**
        169 * Loads a symbol by name from the protected Closure context and exports its
        170 * public API to the provided object. This function relies on Closure code
        171 * conventions to define the public API of an object as those properties whose
        172 * name does not end with "_".
        173 * @param {string} symbol The symbol to load. This must resolve to an object.
        174 * @return {!Object} An object with the exported API.
        175 * @throws {Error} If the symbol has not been defined or does not resolve to
        176 * an object.
        177 */
        178exports.exportPublicApi = function(symbol) {
        179 var src = closureRequire(symbol);
        180 if (typeof src != 'object' || src === null) {
        181 throw Error('"' + symbol + '" must resolve to an object');
        182 }
        183
        184 var dest = {};
        185 Object.keys(src).forEach(function(key) {
        186 if (key[key.length - 1] != '_') {
        187 dest[key] = src[key];
        188 }
        189 });
        190
        191 return dest;
        192};
        193
        194
        195if (isDevMode()) {
        196 exports.closure = context.closure;
        197}
        198exports.Context = Context;
        199exports.isDevMode = isDevMode;
        200exports.require = closureRequire;
        \ No newline at end of file diff --git a/docs/source/builder.js.src.html b/docs/source/builder.js.src.html index 4ab2772..40be1bc 100644 --- a/docs/source/builder.js.src.html +++ b/docs/source/builder.js.src.html @@ -1 +1 @@ -builder.js

        builder.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15var base = require('./_base'),
        16 executors = require('./executors');
        17
        18var goog = base.require('goog'),
        19 AbstractBuilder = base.require('webdriver.AbstractBuilder'),
        20 Browser = base.require('webdriver.Browser'),
        21 Capability = base.require('webdriver.Capability'),
        22 WebDriver = base.require('webdriver.WebDriver'),
        23 promise = base.require('webdriver.promise');
        24
        25
        26/**
        27 * @param {!webdriver.Capabilities} capabilities The desired capabilities.
        28 * @return {webdriver.WebDriver} A new WebDriver instance or {@code null}
        29 * if the requested browser is not natively supported in Node.
        30 */
        31function createNativeDriver(capabilities) {
        32 switch (capabilities.get(Capability.BROWSER_NAME)) {
        33 case Browser.CHROME:
        34 // Requiring 'chrome' above would create a cycle:
        35 // index -> builder -> chrome -> index
        36 var chrome = require('./chrome');
        37 return chrome.createDriver(capabilities);
        38
        39 case Browser.PHANTOM_JS:
        40 // Requiring 'phantomjs' would create a cycle:
        41 // index -> builder -> phantomjs -> index
        42 var phantomjs = require('./phantomjs');
        43 return phantomjs.createDriver(capabilities);
        44
        45 default:
        46 return null;
        47 }
        48}
        49
        50
        51
        52/**
        53 * Creates new {@link webdriver.WebDriver WebDriver} instances.
        54 * @constructor
        55 * @extends {webdriver.AbstractBuilder}
        56 */
        57var Builder = function() {
        58 goog.base(this);
        59};
        60goog.inherits(Builder, AbstractBuilder);
        61
        62
        63/**
        64 * Sets the proxy configuration to use for WebDriver clients created by this
        65 * builder. Any calls to {@link #withCapabilities} after this function will
        66 * overwrite these settings.
        67 * @param {!proxy.ProxyConfig} config The configuration to use.
        68 * @return {!Builder} A self reference.
        69 */
        70Builder.prototype.setProxy = function(config) {
        71 this.getCapabilities().set(Capability.PROXY, config);
        72 return this;
        73};
        74
        75
        76/**
        77 * Sets Chrome-specific options for drivers created by this builder.
        78 * @param {!chrome.Options} options The ChromeDriver options to use.
        79 * @return {!Builder} A self reference.
        80 */
        81Builder.prototype.setChromeOptions = function(options) {
        82 var newCapabilities = options.toCapabilities(this.getCapabilities());
        83 return /** @type {!Builder} */(this.withCapabilities(newCapabilities));
        84};
        85
        86
        87/**
        88 * @override
        89 */
        90Builder.prototype.build = function() {
        91 var url = this.getServerUrl();
        92
        93 // If a remote server wasn't specified, check for browsers we support
        94 // natively in node before falling back to using the java Selenium server.
        95 if (!url) {
        96 var driver = createNativeDriver(this.getCapabilities());
        97 if (driver) {
        98 return driver;
        99 }
        100
        101 // Nope, fall-back to using the default java server.
        102 url = AbstractBuilder.DEFAULT_SERVER_URL;
        103 }
        104
        105 var executor = executors.createExecutor(url);
        106 return WebDriver.createSession(executor, this.getCapabilities());
        107};
        108
        109
        110// PUBLIC API
        111
        112
        113exports.Builder = Builder;
        \ No newline at end of file +builder.js

        builder.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18var base = require('./_base'),
        19 executors = require('./executors');
        20
        21// Use base.require to avoid circular references between index and this module.
        22var Browser = base.require('webdriver.Browser'),
        23 Capabilities = base.require('webdriver.Capabilities'),
        24 Capability = base.require('webdriver.Capability'),
        25 WebDriver = base.require('webdriver.WebDriver'),
        26 promise = base.require('webdriver.promise');
        27
        28
        29
        30var seleniumServer;
        31
        32/**
        33 * Starts an instance of the Selenium server if not yet running.
        34 * @param {string} jar Path to the server jar to use.
        35 * @return {!webdriver.promise.Promise<string>} A promise for the server's
        36 * addrss once started.
        37 */
        38function startSeleniumServer(jar) {
        39 if (!seleniumServer) {
        40 // Requiring 'chrome' above would create a cycle:
        41 // index -> builder -> chrome -> index
        42 var remote = require('./remote');
        43 seleniumServer = new remote.SeleniumServer(jar);
        44 }
        45 return seleniumServer.start();
        46}
        47
        48
        49/**
        50 * Creates new {@link webdriver.WebDriver WebDriver} instances. The environment
        51 * variables listed below may be used to override a builder's configuration,
        52 * allowing quick runtime changes.
        53 *
        54 * - {@code SELENIUM_BROWSER}: defines the target browser in the form
        55 * {@code browser[:version][:platform]}.
        56 *
        57 * - {@code SELENIUM_REMOTE_URL}: defines the remote URL for all builder
        58 * instances. This environment variable should be set to a fully qualified
        59 * URL for a WebDriver server (e.g. http://localhost:4444/wd/hub). This
        60 * option always takes precedence over {@code SELENIUM_SERVER_JAR}.
        61 *
        62 * - {@code SELENIUM_SERVER_JAR}: defines the path to the
        63 * <a href="http://selenium-release.storage.googleapis.com/index.html">
        64 * standalone Selenium server</a> jar to use. The server will be started the
        65 * first time a WebDriver instance and be killed when the process exits.
        66 *
        67 * Suppose you had mytest.js that created WebDriver with
        68 *
        69 * var driver = new webdriver.Builder()
        70 * .forBrowser('chrome')
        71 * .build();
        72 *
        73 * This test could be made to use Firefox on the local machine by running with
        74 * `SELENIUM_BROWSER=firefox node mytest.js`. Rather than change the code to
        75 * target Google Chrome on a remote machine, you can simply set the
        76 * `SELENIUM_BROWSER` and `SELENIUM_REMOTE_URL` environment variables:
        77 *
        78 * SELENIUM_BROWSER=chrome:36:LINUX \
        79 * SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \
        80 * node mytest.js
        81 *
        82 * You could also use a local copy of the standalone Selenium server:
        83 *
        84 * SELENIUM_BROWSER=chrome:36:LINUX \
        85 * SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \
        86 * node mytest.js
        87 *
        88 * @constructor
        89 */
        90var Builder = function() {
        91
        92 /** @private {webdriver.promise.ControlFlow} */
        93 this.flow_ = null;
        94
        95 /** @private {string} */
        96 this.url_ = '';
        97
        98 /** @private {?string} */
        99 this.proxy_ = null;
        100
        101 /** @private {!webdriver.Capabilities} */
        102 this.capabilities_ = new Capabilities();
        103
        104 /** @private {chrome.Options} */
        105 this.chromeOptions_ = null;
        106
        107 /** @private {firefox.Options} */
        108 this.firefoxOptions_ = null;
        109
        110 /** @private {opera.Options} */
        111 this.operaOptions_ = null;
        112
        113 /** @private {ie.Options} */
        114 this.ieOptions_ = null;
        115
        116 /** @private {safari.Options} */
        117 this.safariOptions_ = null;
        118
        119 /** @private {boolean} */
        120 this.ignoreEnv_ = false;
        121};
        122
        123
        124/**
        125 * Configures this builder to ignore any environment variable overrides and to
        126 * only use the configuration specified through this instance's API.
        127 *
        128 * @return {!Builder} A self reference.
        129 */
        130Builder.prototype.disableEnvironmentOverrides = function() {
        131 this.ignoreEnv_ = true;
        132 return this;
        133};
        134
        135
        136/**
        137 * Sets the URL of a remote WebDriver server to use. Once a remote URL has been
        138 * specified, the builder direct all new clients to that server. If this method
        139 * is never called, the Builder will attempt to create all clients locally.
        140 *
        141 * As an alternative to this method, you may also set the `SELENIUM_REMOTE_URL`
        142 * environment variable.
        143 *
        144 * @param {string} url The URL of a remote server to use.
        145 * @return {!Builder} A self reference.
        146 */
        147Builder.prototype.usingServer = function(url) {
        148 this.url_ = url;
        149 return this;
        150};
        151
        152
        153/**
        154 * @return {string} The URL of the WebDriver server this instance is configured
        155 * to use.
        156 */
        157Builder.prototype.getServerUrl = function() {
        158 return this.url_;
        159};
        160
        161
        162/**
        163 * Sets the URL of the proxy to use for the WebDriver's HTTP connections.
        164 * If this method is never called, the Builder will create a connection without
        165 * a proxy.
        166 *
        167 * @param {string} proxy The URL of a proxy to use.
        168 * @return {!Builder} A self reference.
        169 */
        170Builder.prototype.usingWebDriverProxy = function(proxy) {
        171 this.proxy_ = proxy;
        172 return this;
        173};
        174
        175
        176/**
        177 * @return {string} The URL of the proxy server to use for the WebDriver's HTTP
        178 * connections.
        179 */
        180Builder.prototype.getWebDriverProxy = function() {
        181 return this.proxy_;
        182};
        183
        184
        185/**
        186 * Sets the desired capabilities when requesting a new session. This will
        187 * overwrite any previously set capabilities.
        188 * @param {!(Object|webdriver.Capabilities)} capabilities The desired
        189 * capabilities for a new session.
        190 * @return {!Builder} A self reference.
        191 */
        192Builder.prototype.withCapabilities = function(capabilities) {
        193 this.capabilities_ = new Capabilities(capabilities);
        194 return this;
        195};
        196
        197
        198/**
        199 * Returns the base set of capabilities this instance is currently configured
        200 * to use.
        201 * @return {!webdriver.Capabilities} The current capabilities for this builder.
        202 */
        203Builder.prototype.getCapabilities = function() {
        204 return this.capabilities_;
        205};
        206
        207
        208/**
        209 * Configures the target browser for clients created by this instance.
        210 * Any calls to {@link #withCapabilities} after this function will
        211 * overwrite these settings.
        212 *
        213 * You may also define the target browser using the {@code SELENIUM_BROWSER}
        214 * environment variable. If set, this environment variable should be of the
        215 * form `browser[:[version][:platform]]`.
        216 *
        217 * @param {(string|webdriver.Browser)} name The name of the target browser;
        218 * common defaults are available on the {@link webdriver.Browser} enum.
        219 * @param {string=} opt_version A desired version; may be omitted if any
        220 * version should be used.
        221 * @param {string=} opt_platform The desired platform; may be omitted if any
        222 * version may be used.
        223 * @return {!Builder} A self reference.
        224 */
        225Builder.prototype.forBrowser = function(name, opt_version, opt_platform) {
        226 this.capabilities_.set(Capability.BROWSER_NAME, name);
        227 this.capabilities_.set(Capability.VERSION, opt_version || null);
        228 this.capabilities_.set(Capability.PLATFORM, opt_platform || null);
        229 return this;
        230};
        231
        232
        233/**
        234 * Sets the proxy configuration to use for WebDriver clients created by this
        235 * builder. Any calls to {@link #withCapabilities} after this function will
        236 * overwrite these settings.
        237 * @param {!webdriver.ProxyConfig} config The configuration to use.
        238 * @return {!Builder} A self reference.
        239 */
        240Builder.prototype.setProxy = function(config) {
        241 this.capabilities_.setProxy(config);
        242 return this;
        243};
        244
        245
        246/**
        247 * Sets the logging preferences for the created session. Preferences may be
        248 * changed by repeated calls, or by calling {@link #withCapabilities}.
        249 * @param {!(webdriver.logging.Preferences|Object.<string, string>)} prefs The
        250 * desired logging preferences.
        251 * @return {!Builder} A self reference.
        252 */
        253Builder.prototype.setLoggingPrefs = function(prefs) {
        254 this.capabilities_.setLoggingPrefs(prefs);
        255 return this;
        256};
        257
        258
        259/**
        260 * Sets whether native events should be used.
        261 * @param {boolean} enabled Whether to enable native events.
        262 * @return {!Builder} A self reference.
        263 */
        264Builder.prototype.setEnableNativeEvents = function(enabled) {
        265 this.capabilities_.setEnableNativeEvents(enabled);
        266 return this;
        267};
        268
        269
        270/**
        271 * Sets how elements should be scrolled into view for interaction.
        272 * @param {number} behavior The desired scroll behavior: either 0 to align with
        273 * the top of the viewport or 1 to align with the bottom.
        274 * @return {!Builder} A self reference.
        275 */
        276Builder.prototype.setScrollBehavior = function(behavior) {
        277 this.capabilities_.setScrollBehavior(behavior);
        278 return this;
        279};
        280
        281
        282/**
        283 * Sets the default action to take with an unexpected alert before returning
        284 * an error.
        285 * @param {string} beahvior The desired behavior; should be "accept", "dismiss",
        286 * or "ignore". Defaults to "dismiss".
        287 * @return {!Builder} A self reference.
        288 */
        289Builder.prototype.setAlertBehavior = function(behavior) {
        290 this.capabilities_.setAlertBehavior(behavior);
        291 return this;
        292};
        293
        294
        295/**
        296 * Sets Chrome specific {@linkplain selenium-webdriver/chrome.Options options}
        297 * for drivers created by this builder. Any logging or proxy settings defined
        298 * on the given options will take precedence over those set through
        299 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
        300 *
        301 * @param {!chrome.Options} options The ChromeDriver options to use.
        302 * @return {!Builder} A self reference.
        303 */
        304Builder.prototype.setChromeOptions = function(options) {
        305 this.chromeOptions_ = options;
        306 return this;
        307};
        308
        309
        310/**
        311 * Sets Firefox specific {@linkplain selenium-webdriver/firefox.Options options}
        312 * for drivers created by this builder. Any logging or proxy settings defined
        313 * on the given options will take precedence over those set through
        314 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
        315 *
        316 * @param {!firefox.Options} options The FirefoxDriver options to use.
        317 * @return {!Builder} A self reference.
        318 */
        319Builder.prototype.setFirefoxOptions = function(options) {
        320 this.firefoxOptions_ = options;
        321 return this;
        322};
        323
        324
        325/**
        326 * Sets Opera specific {@linkplain selenium-webdriver/opera.Options options} for
        327 * drivers created by this builder. Any logging or proxy settings defined on the
        328 * given options will take precedence over those set through
        329 * {@link #setLoggingPrefs} and {@link #setProxy}, respectively.
        330 *
        331 * @param {!opera.Options} options The OperaDriver options to use.
        332 * @return {!Builder} A self reference.
        333 */
        334Builder.prototype.setOperaOptions = function(options) {
        335 this.operaOptions_ = options;
        336 return this;
        337};
        338
        339
        340/**
        341 * Sets Internet Explorer specific
        342 * {@linkplain selenium-webdriver/ie.Options options} for drivers created by
        343 * this builder. Any proxy settings defined on the given options will take
        344 * precedence over those set through {@link #setProxy}.
        345 *
        346 * @param {!ie.Options} options The IEDriver options to use.
        347 * @return {!Builder} A self reference.
        348 */
        349Builder.prototype.setIeOptions = function(options) {
        350 this.ieOptions_ = options;
        351 return this;
        352};
        353
        354
        355/**
        356 * Sets Safari specific {@linkplain selenium-webdriver/safari.Options options}
        357 * for drivers created by this builder. Any logging settings defined on the
        358 * given options will take precedence over those set through
        359 * {@link #setLoggingPrefs}.
        360 *
        361 * @param {!safari.Options} options The Safari options to use.
        362 * @return {!Builder} A self reference.
        363 */
        364Builder.prototype.setSafariOptions = function(options) {
        365 this.safariOptions_ = options;
        366 return this;
        367};
        368
        369
        370/**
        371 * Sets the control flow that created drivers should execute actions in. If
        372 * the flow is never set, or is set to {@code null}, it will use the active
        373 * flow at the time {@link #build()} is called.
        374 * @param {webdriver.promise.ControlFlow} flow The control flow to use, or
        375 * {@code null} to
        376 * @return {!Builder} A self reference.
        377 */
        378Builder.prototype.setControlFlow = function(flow) {
        379 this.flow_ = flow;
        380 return this;
        381};
        382
        383
        384/**
        385 * Creates a new WebDriver client based on this builder's current
        386 * configuration.
        387 *
        388 * @return {!webdriver.WebDriver} A new WebDriver instance.
        389 * @throws {Error} If the current configuration is invalid.
        390 */
        391Builder.prototype.build = function() {
        392 // Create a copy for any changes we may need to make based on the current
        393 // environment.
        394 var capabilities = new Capabilities(this.capabilities_);
        395
        396 var browser;
        397 if (!this.ignoreEnv_ && process.env.SELENIUM_BROWSER) {
        398 browser = process.env.SELENIUM_BROWSER.split(/:/, 3);
        399 capabilities.set(Capability.BROWSER_NAME, browser[0]);
        400 capabilities.set(Capability.VERSION, browser[1] || null);
        401 capabilities.set(Capability.PLATFORM, browser[2] || null);
        402 }
        403
        404 browser = capabilities.get(Capability.BROWSER_NAME);
        405
        406 if (typeof browser !== 'string') {
        407 throw TypeError(
        408 'Target browser must be a string, but is <' + (typeof browser) + '>;' +
        409 ' did you forget to call forBrowser()?');
        410 }
        411
        412 if (browser === 'ie') {
        413 browser = Browser.INTERNET_EXPLORER;
        414 }
        415
        416 // Apply browser specific overrides.
        417 if (browser === Browser.CHROME && this.chromeOptions_) {
        418 capabilities.merge(this.chromeOptions_.toCapabilities());
        419
        420 } else if (browser === Browser.FIREFOX && this.firefoxOptions_) {
        421 capabilities.merge(this.firefoxOptions_.toCapabilities());
        422
        423 } else if (browser === Browser.INTERNET_EXPLORER && this.ieOptions_) {
        424 capabilities.merge(this.ieOptions_.toCapabilities());
        425
        426 } else if (browser === Browser.OPERA && this.operaOptions_) {
        427 capabilities.merge(this.operaOptions_.toCapabilities());
        428
        429 } else if (browser === Browser.SAFARI && this.safariOptions_) {
        430 capabilities.merge(this.safariOptions_.toCapabilities());
        431 }
        432
        433 // Check for a remote browser.
        434 var url = this.url_;
        435 if (!this.ignoreEnv_) {
        436 if (process.env.SELENIUM_REMOTE_URL) {
        437 url = process.env.SELENIUM_REMOTE_URL;
        438 } else if (process.env.SELENIUM_SERVER_JAR) {
        439 url = startSeleniumServer(process.env.SELENIUM_SERVER_JAR);
        440 }
        441 }
        442
        443 if (url) {
        444 var executor = executors.createExecutor(url, this.proxy_);
        445 return WebDriver.createSession(executor, capabilities, this.flow_);
        446 }
        447
        448 // Check for a native browser.
        449 switch (browser) {
        450 case Browser.CHROME:
        451 // Requiring 'chrome' above would create a cycle:
        452 // index -> builder -> chrome -> index
        453 var chrome = require('./chrome');
        454 return new chrome.Driver(capabilities, null, this.flow_);
        455
        456 case Browser.FIREFOX:
        457 // Requiring 'firefox' above would create a cycle:
        458 // index -> builder -> firefox -> index
        459 var firefox = require('./firefox');
        460 return new firefox.Driver(capabilities, this.flow_);
        461
        462 case Browser.INTERNET_EXPLORER:
        463 // Requiring 'ie' above would create a cycle:
        464 // index -> builder -> ie -> index
        465 var ie = require('./ie');
        466 return new ie.Driver(capabilities, this.flow_);
        467
        468 case Browser.OPERA:
        469 // Requiring 'opera' would create a cycle:
        470 // index -> builder -> opera -> index
        471 var opera = require('./opera');
        472 return new opera.Driver(capabilities, this.flow_);
        473
        474 case Browser.PHANTOM_JS:
        475 // Requiring 'phantomjs' would create a cycle:
        476 // index -> builder -> phantomjs -> index
        477 var phantomjs = require('./phantomjs');
        478 return new phantomjs.Driver(capabilities, this.flow_);
        479
        480 case Browser.SAFARI:
        481 // Requiring 'safari' would create a cycle:
        482 // index -> builder -> safari -> index
        483 var safari = require('./safari');
        484 return new safari.Driver(capabilities, this.flow_);
        485
        486 default:
        487 throw new Error('Do not know how to build driver: ' + browser
        488 + '; did you forget to call usingServer(url)?');
        489 }
        490};
        491
        492
        493// PUBLIC API
        494
        495
        496exports.Builder = Builder;
        \ No newline at end of file diff --git a/docs/source/chrome.js.src.html b/docs/source/chrome.js.src.html index 6d4103c..112dd44 100644 --- a/docs/source/chrome.js.src.html +++ b/docs/source/chrome.js.src.html @@ -1 +1 @@ -chrome.js

        chrome.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16'use strict';
        17
        18var fs = require('fs'),
        19 util = require('util');
        20
        21var webdriver = require('./index'),
        22 executors = require('./executors'),
        23 io = require('./io'),
        24 portprober = require('./net/portprober'),
        25 remote = require('./remote');
        26
        27
        28/**
        29 * Name of the ChromeDriver executable.
        30 * @type {string}
        31 * @const
        32 */
        33var CHROMEDRIVER_EXE =
        34 process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
        35
        36
        37/**
        38 * Creates {@link remote.DriverService} instances that manage a ChromeDriver
        39 * server.
        40 * @param {string=} opt_exe Path to the server executable to use. If omitted,
        41 * the builder will attempt to locate the chromedriver on the current
        42 * PATH.
        43 * @throws {Error} If provided executable does not exist, or the chromedriver
        44 * cannot be found on the PATH.
        45 * @constructor
        46 */
        47var ServiceBuilder = function(opt_exe) {
        48 /** @private {string} */
        49 this.exe_ = opt_exe || io.findInPath(CHROMEDRIVER_EXE, true);
        50 if (!this.exe_) {
        51 throw Error(
        52 'The ChromeDriver could not be found on the current PATH. Please ' +
        53 'download the latest version of the ChromeDriver from ' +
        54 'http://chromedriver.storage.googleapis.com/index.html and ensure ' +
        55 'it can be found on your PATH.');
        56 }
        57
        58 if (!fs.existsSync(this.exe_)) {
        59 throw Error('File does not exist: ' + this.exe_);
        60 }
        61
        62 /** @private {!Array.<string>} */
        63 this.args_ = [];
        64 this.stdio_ = 'ignore';
        65};
        66
        67
        68/** @private {number} */
        69ServiceBuilder.prototype.port_ = 0;
        70
        71
        72/** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
        73ServiceBuilder.prototype.stdio_ = 'ignore';
        74
        75
        76/** @private {Object.<string, string>} */
        77ServiceBuilder.prototype.env_ = null;
        78
        79
        80/**
        81 * Sets the port to start the ChromeDriver on.
        82 * @param {number} port The port to use, or 0 for any free port.
        83 * @return {!ServiceBuilder} A self reference.
        84 * @throws {Error} If the port is invalid.
        85 */
        86ServiceBuilder.prototype.usingPort = function(port) {
        87 if (port < 0) {
        88 throw Error('port must be >= 0: ' + port);
        89 }
        90 this.port_ = port;
        91 return this;
        92};
        93
        94
        95/**
        96 * Sets the path of the log file the driver should log to. If a log file is
        97 * not specified, the driver will log to stderr.
        98 * @param {string} path Path of the log file to use.
        99 * @return {!ServiceBuilder} A self reference.
        100 */
        101ServiceBuilder.prototype.loggingTo = function(path) {
        102 this.args_.push('--log-path=' + path);
        103 return this;
        104};
        105
        106
        107/**
        108 * Enables verbose logging.
        109 * @return {!ServiceBuilder} A self reference.
        110 */
        111ServiceBuilder.prototype.enableVerboseLogging = function() {
        112 this.args_.push('--verbose');
        113 return this;
        114};
        115
        116
        117/**
        118 * Sets the number of threads the driver should use to manage HTTP requests.
        119 * By default, the driver will use 4 threads.
        120 * @param {number} n The number of threads to use.
        121 * @return {!ServiceBuilder} A self reference.
        122 */
        123ServiceBuilder.prototype.setNumHttpThreads = function(n) {
        124 this.args_.push('--http-threads=' + n);
        125 return this;
        126};
        127
        128
        129/**
        130 * Sets the base path for WebDriver REST commands (e.g. "/wd/hub").
        131 * By default, the driver will accept commands relative to "/".
        132 * @param {string} path The base path to use.
        133 * @return {!ServiceBuilder} A self reference.
        134 */
        135ServiceBuilder.prototype.setUrlBasePath = function(path) {
        136 this.args_.push('--url-base=' + path);
        137 return this;
        138};
        139
        140
        141/**
        142 * Defines the stdio configuration for the driver service. See
        143 * {@code child_process.spawn} for more information.
        144 * @param {(string|!Array.<string|number|!Stream|null|undefined>)} config The
        145 * configuration to use.
        146 * @return {!ServiceBuilder} A self reference.
        147 */
        148ServiceBuilder.prototype.setStdio = function(config) {
        149 this.stdio_ = config;
        150 return this;
        151};
        152
        153
        154/**
        155 * Defines the environment to start the server under. This settings will be
        156 * inherited by every browser session started by the server.
        157 * @param {!Object.<string, string>} env The environment to use.
        158 * @return {!ServiceBuilder} A self reference.
        159 */
        160ServiceBuilder.prototype.withEnvironment = function(env) {
        161 this.env_ = env;
        162 return this;
        163};
        164
        165
        166/**
        167 * Creates a new DriverService using this instance's current configuration.
        168 * @return {remote.DriverService} A new driver service using this instance's
        169 * current configuration.
        170 * @throws {Error} If the driver exectuable was not specified and a default
        171 * could not be found on the current PATH.
        172 */
        173ServiceBuilder.prototype.build = function() {
        174 var port = this.port_ || portprober.findFreePort();
        175 var args = this.args_.concat(); // Defensive copy.
        176
        177 return new remote.DriverService(this.exe_, {
        178 port: port,
        179 args: webdriver.promise.when(port, function(port) {
        180 return args.concat('--port=' + port);
        181 }),
        182 env: this.env_,
        183 stdio: this.stdio_
        184 });
        185};
        186
        187
        188/** @type {remote.DriverService} */
        189var defaultService = null;
        190
        191
        192/**
        193 * Sets the default service to use for new ChromeDriver instances.
        194 * @param {!remote.DriverService} service The service to use.
        195 * @throws {Error} If the default service is currently running.
        196 */
        197function setDefaultService(service) {
        198 if (defaultService && defaultService.isRunning()) {
        199 throw Error(
        200 'The previously configured ChromeDriver service is still running. ' +
        201 'You must shut it down before you may adjust its configuration.');
        202 }
        203 defaultService = service;
        204}
        205
        206
        207/**
        208 * Returns the default ChromeDriver service. If such a service has not been
        209 * configured, one will be constructed using the default configuration for
        210 * a ChromeDriver executable found on the system PATH.
        211 * @return {!remote.DriverService} The default ChromeDriver service.
        212 */
        213function getDefaultService() {
        214 if (!defaultService) {
        215 defaultService = new ServiceBuilder().build();
        216 }
        217 return defaultService;
        218}
        219
        220
        221/**
        222 * @type {string}
        223 * @const
        224 */
        225var OPTIONS_CAPABILITY_KEY = 'chromeOptions';
        226
        227
        228/**
        229 * Class for managing ChromeDriver specific options.
        230 * @constructor
        231 */
        232var Options = function() {
        233 /** @private {!Array.<string>} */
        234 this.args_ = [];
        235
        236 /** @private {!Array.<(string|!Buffer)>} */
        237 this.extensions_ = [];
        238};
        239
        240
        241/**
        242 * Extracts the ChromeDriver specific options from the given capabilities
        243 * object.
        244 * @param {!webdriver.Capabilities} capabilities The capabilities object.
        245 * @return {!Options} The ChromeDriver options.
        246 */
        247Options.fromCapabilities = function(capabilities) {
        248 var options = new Options();
        249
        250 var o = capabilities.get(OPTIONS_CAPABILITY_KEY);
        251 if (o instanceof Options) {
        252 options = o;
        253 } else if (o) {
        254 options.
        255 addArguments(o.args || []).
        256 addExtensions(o.extensions || []).
        257 detachDriver(!!o.detach).
        258 setChromeBinaryPath(o.binary).
        259 setChromeLogFile(o.logFile).
        260 setLocalState(o.localState).
        261 setUserPreferences(o.prefs);
        262 }
        263
        264 if (capabilities.has(webdriver.Capability.PROXY)) {
        265 options.setProxy(capabilities.get(webdriver.Capability.PROXY));
        266 }
        267
        268 if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {
        269 options.setLoggingPreferences(
        270 capabilities.get(webdriver.Capability.LOGGING_PREFS));
        271 }
        272
        273 return options;
        274};
        275
        276
        277/**
        278 * Add additional command line arguments to use when launching the Chrome
        279 * browser. Each argument may be specified with or without the "--" prefix
        280 * (e.g. "--foo" and "foo"). Arguments with an associated value should be
        281 * delimited by an "=": "foo=bar".
        282 * @param {...(string|!Array.<string>)} var_args The arguments to add.
        283 * @return {!Options} A self reference.
        284 */
        285Options.prototype.addArguments = function(var_args) {
        286 this.args_ = this.args_.concat.apply(this.args_, arguments);
        287 return this;
        288};
        289
        290
        291/**
        292 * Add additional extensions to install when launching Chrome. Each extension
        293 * should be specified as the path to the packed CRX file, or a Buffer for an
        294 * extension.
        295 * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The
        296 * extensions to add.
        297 * @return {!Options} A self reference.
        298 */
        299Options.prototype.addExtensions = function(var_args) {
        300 this.extensions_ = this.extensions_.concat.apply(
        301 this.extensions_, arguments);
        302 return this;
        303};
        304
        305
        306/**
        307 * Sets the path to the Chrome binary to use. On Mac OS X, this path should
        308 * reference the actual Chrome executable, not just the application binary
        309 * (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").
        310 *
        311 * The binary path be absolute or relative to the chromedriver server
        312 * executable, but it must exist on the machine that will launch Chrome.
        313 *
        314 * @param {string} path The path to the Chrome binary to use.
        315 * @return {!Options} A self reference.
        316 */
        317Options.prototype.setChromeBinaryPath = function(path) {
        318 this.binary_ = path;
        319 return this;
        320};
        321
        322
        323/**
        324 * Sets whether to leave the started Chrome browser running if the controlling
        325 * ChromeDriver service is killed before {@link webdriver.WebDriver#quit()} is
        326 * called.
        327 * @param {boolean} detach Whether to leave the browser running if the
        328 * chromedriver service is killed before the session.
        329 * @return {!Options} A self reference.
        330 */
        331Options.prototype.detachDriver = function(detach) {
        332 this.detach_ = detach;
        333 return this;
        334};
        335
        336
        337/**
        338 * Sets the user preferences for Chrome's user profile. See the "Preferences"
        339 * file in Chrome's user data directory for examples.
        340 * @param {!Object} prefs Dictionary of user preferences to use.
        341 * @return {!Options} A self reference.
        342 */
        343Options.prototype.setUserPreferences = function(prefs) {
        344 this.prefs_ = prefs;
        345 return this;
        346};
        347
        348
        349/**
        350 * Sets the logging preferences for the new session.
        351 * @param {!webdriver.logging.Preferences} prefs The logging preferences.
        352 * @return {!Options} A self reference.
        353 */
        354Options.prototype.setLoggingPreferences = function(prefs) {
        355 this.logPrefs_ = prefs;
        356 return this;
        357};
        358
        359
        360/**
        361 * Sets preferences for the "Local State" file in Chrome's user data
        362 * directory.
        363 * @param {!Object} state Dictionary of local state preferences.
        364 * @return {!Options} A self reference.
        365 */
        366Options.prototype.setLocalState = function(state) {
        367 this.localState_ = state;
        368 return this;
        369};
        370
        371
        372/**
        373 * Sets the path to Chrome's log file. This path should exist on the machine
        374 * that will launch Chrome.
        375 * @param {string} path Path to the log file to use.
        376 * @return {!Options} A self reference.
        377 */
        378Options.prototype.setChromeLogFile = function(path) {
        379 this.logFile_ = path;
        380 return this;
        381};
        382
        383
        384/**
        385 * Sets the proxy settings for the new session.
        386 * @param {ProxyConfig} proxy The proxy configuration to use.
        387 * @return {!Options} A self reference.
        388 */
        389Options.prototype.setProxy = function(proxy) {
        390 this.proxy_ = proxy;
        391 return this;
        392};
        393
        394
        395/**
        396 * Converts this options instance to a {@link webdriver.Capabilities} object.
        397 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge
        398 * these options into, if any.
        399 * @return {!webdriver.Capabilities} The capabilities.
        400 */
        401Options.prototype.toCapabilities = function(opt_capabilities) {
        402 var capabilities = opt_capabilities || webdriver.Capabilities.chrome();
        403 capabilities.
        404 set(webdriver.Capability.PROXY, this.proxy_).
        405 set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_).
        406 set(OPTIONS_CAPABILITY_KEY, this);
        407 return capabilities;
        408};
        409
        410
        411/**
        412 * Converts this instance to its JSON wire protocol representation. Note this
        413 * function is an implementation not intended for general use.
        414 * @return {{args: !Array.<string>,
        415 * binary: (string|undefined),
        416 * detach: boolean,
        417 * extensions: !Array.<string>,
        418 * localState: (Object|undefined),
        419 * logFile: (string|undefined),
        420 * prefs: (Object|undefined)}} The JSON wire protocol representation
        421 * of this instance.
        422 */
        423Options.prototype.toJSON = function() {
        424 return {
        425 args: this.args_,
        426 binary: this.binary_,
        427 detach: !!this.detach_,
        428 extensions: this.extensions_.map(function(extension) {
        429 if (Buffer.isBuffer(extension)) {
        430 return extension.toString('base64');
        431 }
        432 return fs.readFileSync(extension, 'base64');
        433 }),
        434 localState: this.localState_,
        435 logFile: this.logFile_,
        436 prefs: this.prefs_
        437 };
        438};
        439
        440
        441/**
        442 * Creates a new ChromeDriver session.
        443 * @param {(webdriver.Capabilities|Options)=} opt_options The session options.
        444 * @param {remote.DriverService=} opt_service The session to use; will use
        445 * the {@link getDefaultService default service} by default.
        446 * @return {!webdriver.WebDriver} A new WebDriver instance.
        447 */
        448function createDriver(opt_options, opt_service) {
        449 var service = opt_service || getDefaultService();
        450 var executor = executors.createExecutor(service.start());
        451
        452 var options = opt_options || new Options();
        453 if (opt_options instanceof webdriver.Capabilities) {
        454 // Extract the Chrome-specific options so we do not send unnecessary
        455 // data across the wire.
        456 options = Options.fromCapabilities(options);
        457 }
        458
        459 return webdriver.WebDriver.createSession(
        460 executor, options.toCapabilities());
        461}
        462
        463
        464
        465// PUBLIC API
        466
        467
        468exports.ServiceBuilder = ServiceBuilder;
        469exports.Options = Options;
        470exports.createDriver = createDriver;
        471exports.getDefaultService = getDefaultService;
        472exports.setDefaultService = setDefaultService;
        \ No newline at end of file +chrome.js

        chrome.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a {@linkplain Driver WebDriver} client for the Chrome
        20 * web browser. Before using this module, you must download the latest
        21 * [ChromeDriver release] and ensure it can be found on your system [PATH].
        22 *
        23 * There are three primary classes exported by this module:
        24 *
        25 * 1. {@linkplain ServiceBuilder}: configures the
        26 * {@link selenium-webdriver/remote.DriverService remote.DriverService}
        27 * that manages the [ChromeDriver] child process.
        28 *
        29 * 2. {@linkplain Options}: defines configuration options for each new Chrome
        30 * session, such as which {@linkplain Options#setProxy proxy} to use,
        31 * what {@linkplain Options#addExtensions extensions} to install, or
        32 * what {@linkplain Options#addArguments command-line switches} to use when
        33 * starting the browser.
        34 *
        35 * 3. {@linkplain Driver}: the WebDriver client; each new instance will control
        36 * a unique browser session with a clean user profile (unless otherwise
        37 * configured through the {@link Options} class).
        38 *
        39 * __Customizing the ChromeDriver Server__ <a id="custom-server"></a>
        40 *
        41 * By default, every Chrome session will use a single driver service, which is
        42 * started the first time a {@link Driver} instance is created and terminated
        43 * when this process exits. The default service will inherit its environment
        44 * from the current process and direct all output to /dev/null. You may obtain
        45 * a handle to this default service using
        46 * {@link #getDefaultService getDefaultService()} and change its configuration
        47 * with {@link #setDefaultService setDefaultService()}.
        48 *
        49 * You may also create a {@link Driver} with its own driver service. This is
        50 * useful if you need to capture the server's log output for a specific session:
        51 *
        52 * var chrome = require('selenium-webdriver/chrome');
        53 *
        54 * var service = new chrome.ServiceBuilder()
        55 * .loggingTo('/my/log/file.txt')
        56 * .enableVerboseLogging()
        57 * .build();
        58 *
        59 * var options = new chrome.Options();
        60 * // configure browser options ...
        61 *
        62 * var driver = new chrome.Driver(options, service);
        63 *
        64 * Users should only instantiate the {@link Driver} class directly when they
        65 * need a custom driver service configuration (as shown above). For normal
        66 * operation, users should start Chrome using the
        67 * {@link selenium-webdriver.Builder}.
        68 *
        69 * __Working with Android__ <a id="android"></a>
        70 *
        71 * The [ChromeDriver][android] supports running tests on the Chrome browser as
        72 * well as [WebView apps][webview] starting in Android 4.4 (KitKat). In order to
        73 * work with Android, you must first start the adb
        74 *
        75 * adb start-server
        76 *
        77 * By default, adb will start on port 5037. You may change this port, but this
        78 * will require configuring a [custom server](#custom-server) that will connect
        79 * to adb on the {@linkplain ServiceBuilder#setAdbPort correct port}:
        80 *
        81 * var service = new chrome.ServiceBuilder()
        82 * .setAdbPort(1234)
        83 * build();
        84 * // etc.
        85 *
        86 * The ChromeDriver may be configured to launch Chrome on Android using
        87 * {@link Options#androidChrome()}:
        88 *
        89 * var driver = new Builder()
        90 * .forBrowser('chrome')
        91 * .setChromeOptions(new chrome.Options().androidChrome())
        92 * .build();
        93 *
        94 * Alternatively, you can configure the ChromeDriver to launch an app with a
        95 * Chrome-WebView by setting the {@linkplain Options#androidActivity
        96 * androidActivity} option:
        97 *
        98 * var driver = new Builder()
        99 * .forBrowser('chrome')
        100 * .setChromeOptions(new chrome.Options()
        101 * .androidPackage('com.example')
        102 * .androidActivity('com.example.Activity'))
        103 * .build();
        104 *
        105 * [Refer to the ChromeDriver site] for more information on using the
        106 * [ChromeDriver with Android][android].
        107 *
        108 * [ChromeDriver]: https://sites.google.com/a/chromium.org/chromedriver/
        109 * [ChromeDriver release]: http://chromedriver.storage.googleapis.com/index.html
        110 * [PATH]: http://en.wikipedia.org/wiki/PATH_%28variable%29
        111 * [android]: https://sites.google.com/a/chromium.org/chromedriver/getting-started/getting-started---android
        112 * [webview]: https://developer.chrome.com/multidevice/webview/overview
        113 */
        114
        115'use strict';
        116
        117var fs = require('fs'),
        118 util = require('util');
        119
        120var webdriver = require('./index'),
        121 executors = require('./executors'),
        122 http = require('./http'),
        123 io = require('./io'),
        124 portprober = require('./net/portprober'),
        125 remote = require('./remote');
        126
        127
        128/**
        129 * Name of the ChromeDriver executable.
        130 * @type {string}
        131 * @const
        132 */
        133var CHROMEDRIVER_EXE =
        134 process.platform === 'win32' ? 'chromedriver.exe' : 'chromedriver';
        135
        136
        137/**
        138 * Custom command names supported by ChromeDriver.
        139 * @enum {string}
        140 */
        141var Command = {
        142 LAUNCH_APP: 'launchApp'
        143};
        144
        145
        146/**
        147 * Creates a command executor with support for ChromeDriver's custom commands.
        148 * @param {!webdriver.promise.Promise<string>} url The server's URL.
        149 * @return {!webdriver.CommandExecutor} The new command executor.
        150 */
        151function createExecutor(url) {
        152 return new executors.DeferredExecutor(url.then(function(url) {
        153 var client = new http.HttpClient(url);
        154 var executor = new http.Executor(client);
        155 executor.defineCommand(
        156 Command.LAUNCH_APP,
        157 'POST', '/session/:sessionId/chromium/launch_app');
        158 return executor;
        159 }));
        160}
        161
        162
        163/**
        164 * Creates {@link selenium-webdriver/remote.DriverService} instances that manage
        165 * a [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/)
        166 * server in a child process.
        167 *
        168 * @param {string=} opt_exe Path to the server executable to use. If omitted,
        169 * the builder will attempt to locate the chromedriver on the current
        170 * PATH.
        171 * @throws {Error} If provided executable does not exist, or the chromedriver
        172 * cannot be found on the PATH.
        173 * @constructor
        174 */
        175var ServiceBuilder = function(opt_exe) {
        176 /** @private {string} */
        177 this.exe_ = opt_exe || io.findInPath(CHROMEDRIVER_EXE, true);
        178 if (!this.exe_) {
        179 throw Error(
        180 'The ChromeDriver could not be found on the current PATH. Please ' +
        181 'download the latest version of the ChromeDriver from ' +
        182 'http://chromedriver.storage.googleapis.com/index.html and ensure ' +
        183 'it can be found on your PATH.');
        184 }
        185
        186 if (!fs.existsSync(this.exe_)) {
        187 throw Error('File does not exist: ' + this.exe_);
        188 }
        189
        190 /** @private {!Array.<string>} */
        191 this.args_ = [];
        192 this.stdio_ = 'ignore';
        193};
        194
        195
        196/** @private {string} */
        197ServiceBuilder.prototype.path_ = null;
        198
        199/** @private {number} */
        200ServiceBuilder.prototype.port_ = 0;
        201
        202
        203/** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
        204ServiceBuilder.prototype.stdio_ = 'ignore';
        205
        206
        207/** @private {Object.<string, string>} */
        208ServiceBuilder.prototype.env_ = null;
        209
        210
        211/**
        212 * Sets the port to start the ChromeDriver on.
        213 * @param {number} port The port to use, or 0 for any free port.
        214 * @return {!ServiceBuilder} A self reference.
        215 * @throws {Error} If the port is invalid.
        216 */
        217ServiceBuilder.prototype.usingPort = function(port) {
        218 if (port < 0) {
        219 throw Error('port must be >= 0: ' + port);
        220 }
        221 this.port_ = port;
        222 return this;
        223};
        224
        225
        226/**
        227 * Sets which port adb is listening to. _The ChromeDriver will connect to adb
        228 * if an {@linkplain Options#androidPackage Android session} is requested, but
        229 * adb **must** be started beforehand._
        230 *
        231 * @param {number} port Which port adb is running on.
        232 * @return {!ServiceBuilder} A self reference.
        233 */
        234ServiceBuilder.prototype.setAdbPort = function(port) {
        235 this.args_.push('--adb-port=' + port);
        236 return this;
        237};
        238
        239
        240/**
        241 * Sets the path of the log file the driver should log to. If a log file is
        242 * not specified, the driver will log to stderr.
        243 * @param {string} path Path of the log file to use.
        244 * @return {!ServiceBuilder} A self reference.
        245 */
        246ServiceBuilder.prototype.loggingTo = function(path) {
        247 this.args_.push('--log-path=' + path);
        248 return this;
        249};
        250
        251
        252/**
        253 * Enables verbose logging.
        254 * @return {!ServiceBuilder} A self reference.
        255 */
        256ServiceBuilder.prototype.enableVerboseLogging = function() {
        257 this.args_.push('--verbose');
        258 return this;
        259};
        260
        261
        262/**
        263 * Sets the number of threads the driver should use to manage HTTP requests.
        264 * By default, the driver will use 4 threads.
        265 * @param {number} n The number of threads to use.
        266 * @return {!ServiceBuilder} A self reference.
        267 */
        268ServiceBuilder.prototype.setNumHttpThreads = function(n) {
        269 this.args_.push('--http-threads=' + n);
        270 return this;
        271};
        272
        273
        274/**
        275 * Sets the base path for WebDriver REST commands (e.g. "/wd/hub").
        276 * By default, the driver will accept commands relative to "/".
        277 * @param {string} path The base path to use.
        278 * @return {!ServiceBuilder} A self reference.
        279 */
        280ServiceBuilder.prototype.setUrlBasePath = function(path) {
        281 this.args_.push('--url-base=' + path);
        282 this.path_ = path;
        283 return this;
        284};
        285
        286
        287/**
        288 * Defines the stdio configuration for the driver service. See
        289 * {@code child_process.spawn} for more information.
        290 * @param {(string|!Array.<string|number|!Stream|null|undefined>)} config The
        291 * configuration to use.
        292 * @return {!ServiceBuilder} A self reference.
        293 */
        294ServiceBuilder.prototype.setStdio = function(config) {
        295 this.stdio_ = config;
        296 return this;
        297};
        298
        299
        300/**
        301 * Defines the environment to start the server under. This settings will be
        302 * inherited by every browser session started by the server.
        303 * @param {!Object.<string, string>} env The environment to use.
        304 * @return {!ServiceBuilder} A self reference.
        305 */
        306ServiceBuilder.prototype.withEnvironment = function(env) {
        307 this.env_ = env;
        308 return this;
        309};
        310
        311
        312/**
        313 * Creates a new DriverService using this instance's current configuration.
        314 * @return {remote.DriverService} A new driver service using this instance's
        315 * current configuration.
        316 * @throws {Error} If the driver exectuable was not specified and a default
        317 * could not be found on the current PATH.
        318 */
        319ServiceBuilder.prototype.build = function() {
        320 var port = this.port_ || portprober.findFreePort();
        321 var args = this.args_.concat(); // Defensive copy.
        322
        323 return new remote.DriverService(this.exe_, {
        324 loopback: true,
        325 path: this.path_,
        326 port: port,
        327 args: webdriver.promise.when(port, function(port) {
        328 return args.concat('--port=' + port);
        329 }),
        330 env: this.env_,
        331 stdio: this.stdio_
        332 });
        333};
        334
        335
        336/** @type {remote.DriverService} */
        337var defaultService = null;
        338
        339
        340/**
        341 * Sets the default service to use for new ChromeDriver instances.
        342 * @param {!remote.DriverService} service The service to use.
        343 * @throws {Error} If the default service is currently running.
        344 */
        345function setDefaultService(service) {
        346 if (defaultService && defaultService.isRunning()) {
        347 throw Error(
        348 'The previously configured ChromeDriver service is still running. ' +
        349 'You must shut it down before you may adjust its configuration.');
        350 }
        351 defaultService = service;
        352}
        353
        354
        355/**
        356 * Returns the default ChromeDriver service. If such a service has not been
        357 * configured, one will be constructed using the default configuration for
        358 * a ChromeDriver executable found on the system PATH.
        359 * @return {!remote.DriverService} The default ChromeDriver service.
        360 */
        361function getDefaultService() {
        362 if (!defaultService) {
        363 defaultService = new ServiceBuilder().build();
        364 }
        365 return defaultService;
        366}
        367
        368
        369/**
        370 * @type {string}
        371 * @const
        372 */
        373var OPTIONS_CAPABILITY_KEY = 'chromeOptions';
        374
        375
        376/**
        377 * Class for managing ChromeDriver specific options.
        378 * @constructor
        379 * @extends {webdriver.Serializable}
        380 */
        381var Options = function() {
        382 webdriver.Serializable.call(this);
        383
        384 /** @private {!Object} */
        385 this.options_ = {};
        386
        387 /** @private {!Array.<(string|!Buffer)>} */
        388 this.extensions_ = [];
        389
        390 /** @private {?webdriver.logging.Preferences} */
        391 this.logPrefs_ = null;
        392
        393 /** @private {?webdriver.ProxyConfig} */
        394 this.proxy_ = null;
        395};
        396util.inherits(Options, webdriver.Serializable);
        397
        398
        399/**
        400 * Extracts the ChromeDriver specific options from the given capabilities
        401 * object.
        402 * @param {!webdriver.Capabilities} capabilities The capabilities object.
        403 * @return {!Options} The ChromeDriver options.
        404 */
        405Options.fromCapabilities = function(capabilities) {
        406 var options = new Options();
        407
        408 var o = capabilities.get(OPTIONS_CAPABILITY_KEY);
        409 if (o instanceof Options) {
        410 options = o;
        411 } else if (o) {
        412 options.
        413 addArguments(o.args || []).
        414 addExtensions(o.extensions || []).
        415 detachDriver(o.detach).
        416 excludeSwitches(o.excludeSwitches || []).
        417 setChromeBinaryPath(o.binary).
        418 setChromeLogFile(o.logPath).
        419 setChromeMinidumpPath(o.minidumpPath).
        420 setLocalState(o.localState).
        421 setMobileEmulation(o.mobileEmulation).
        422 setUserPreferences(o.prefs).
        423 setPerfLoggingPrefs(o.perfLoggingPrefs);
        424 }
        425
        426 if (capabilities.has(webdriver.Capability.PROXY)) {
        427 options.setProxy(capabilities.get(webdriver.Capability.PROXY));
        428 }
        429
        430 if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {
        431 options.setLoggingPrefs(
        432 capabilities.get(webdriver.Capability.LOGGING_PREFS));
        433 }
        434
        435 return options;
        436};
        437
        438
        439/**
        440 * Add additional command line arguments to use when launching the Chrome
        441 * browser. Each argument may be specified with or without the "--" prefix
        442 * (e.g. "--foo" and "foo"). Arguments with an associated value should be
        443 * delimited by an "=": "foo=bar".
        444 * @param {...(string|!Array.<string>)} var_args The arguments to add.
        445 * @return {!Options} A self reference.
        446 */
        447Options.prototype.addArguments = function(var_args) {
        448 var args = this.options_.args || [];
        449 args = args.concat.apply(args, arguments);
        450 if (args.length) {
        451 this.options_.args = args;
        452 }
        453 return this;
        454};
        455
        456
        457/**
        458 * List of Chrome command line switches to exclude that ChromeDriver by default
        459 * passes when starting Chrome. Do not prefix switches with "--".
        460 *
        461 * @param {...(string|!Array<string>)} var_args The switches to exclude.
        462 * @return {!Options} A self reference.
        463 */
        464Options.prototype.excludeSwitches = function(var_args) {
        465 var switches = this.options_.excludeSwitches || [];
        466 switches = switches.concat.apply(switches, arguments);
        467 if (switches.length) {
        468 this.options_.excludeSwitches = switches;
        469 }
        470 return this;
        471};
        472
        473
        474/**
        475 * Add additional extensions to install when launching Chrome. Each extension
        476 * should be specified as the path to the packed CRX file, or a Buffer for an
        477 * extension.
        478 * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The
        479 * extensions to add.
        480 * @return {!Options} A self reference.
        481 */
        482Options.prototype.addExtensions = function(var_args) {
        483 this.extensions_ = this.extensions_.concat.apply(this.extensions_, arguments);
        484 return this;
        485};
        486
        487
        488/**
        489 * Sets the path to the Chrome binary to use. On Mac OS X, this path should
        490 * reference the actual Chrome executable, not just the application binary
        491 * (e.g. "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").
        492 *
        493 * The binary path be absolute or relative to the chromedriver server
        494 * executable, but it must exist on the machine that will launch Chrome.
        495 *
        496 * @param {string} path The path to the Chrome binary to use.
        497 * @return {!Options} A self reference.
        498 */
        499Options.prototype.setChromeBinaryPath = function(path) {
        500 this.options_.binary = path;
        501 return this;
        502};
        503
        504
        505/**
        506 * Sets whether to leave the started Chrome browser running if the controlling
        507 * ChromeDriver service is killed before {@link webdriver.WebDriver#quit()} is
        508 * called.
        509 * @param {boolean} detach Whether to leave the browser running if the
        510 * chromedriver service is killed before the session.
        511 * @return {!Options} A self reference.
        512 */
        513Options.prototype.detachDriver = function(detach) {
        514 this.options_.detach = detach;
        515 return this;
        516};
        517
        518
        519/**
        520 * Sets the user preferences for Chrome's user profile. See the "Preferences"
        521 * file in Chrome's user data directory for examples.
        522 * @param {!Object} prefs Dictionary of user preferences to use.
        523 * @return {!Options} A self reference.
        524 */
        525Options.prototype.setUserPreferences = function(prefs) {
        526 this.options_.prefs = prefs;
        527 return this;
        528};
        529
        530
        531/**
        532 * Sets the logging preferences for the new session.
        533 * @param {!webdriver.logging.Preferences} prefs The logging preferences.
        534 * @return {!Options} A self reference.
        535 */
        536Options.prototype.setLoggingPrefs = function(prefs) {
        537 this.logPrefs_ = prefs;
        538 return this;
        539};
        540
        541
        542/**
        543 * Sets the performance logging preferences. Options include:
        544 *
        545 * - `enableNetwork`: Whether or not to collect events from Network domain.
        546 * - `enablePage`: Whether or not to collect events from Page domain.
        547 * - `enableTimeline`: Whether or not to collect events from Timeline domain.
        548 * Note: when tracing is enabled, Timeline domain is implicitly disabled,
        549 * unless `enableTimeline` is explicitly set to true.
        550 * - `tracingCategories`: A comma-separated string of Chrome tracing categories
        551 * for which trace events should be collected. An unspecified or empty
        552 * string disables tracing.
        553 * - `bufferUsageReportingInterval`: The requested number of milliseconds
        554 * between DevTools trace buffer usage events. For example, if 1000, then
        555 * once per second, DevTools will report how full the trace buffer is. If a
        556 * report indicates the buffer usage is 100%, a warning will be issued.
        557 *
        558 * @param {{enableNetwork: boolean,
        559 * enablePage: boolean,
        560 * enableTimeline: boolean,
        561 * tracingCategories: string,
        562 * bufferUsageReportingInterval: number}} prefs The performance
        563 * logging preferences.
        564 * @return {!Options} A self reference.
        565 */
        566Options.prototype.setPerfLoggingPrefs = function(prefs) {
        567 this.options_.perfLoggingPrefs = prefs;
        568 return this;
        569};
        570
        571
        572/**
        573 * Sets preferences for the "Local State" file in Chrome's user data
        574 * directory.
        575 * @param {!Object} state Dictionary of local state preferences.
        576 * @return {!Options} A self reference.
        577 */
        578Options.prototype.setLocalState = function(state) {
        579 this.options_.localState = state;
        580 return this;
        581};
        582
        583
        584/**
        585 * Sets the name of the activity hosting a Chrome-based Android WebView. This
        586 * option must be set to connect to an [Android WebView](
        587 * https://sites.google.com/a/chromium.org/chromedriver/getting-started/getting-started---android)
        588 *
        589 * @param {string} name The activity name.
        590 * @return {!Options} A self reference.
        591 */
        592Options.prototype.androidActivity = function(name) {
        593 this.options_.androidActivity = name;
        594 return this;
        595};
        596
        597
        598/**
        599 * Sets the device serial number to connect to via ADB. If not specified, the
        600 * ChromeDriver will select an unused device at random. An error will be
        601 * returned if all devices already have active sessions.
        602 *
        603 * @param {string} serial The device serial number to connect to.
        604 * @return {!Options} A self reference.
        605 */
        606Options.prototype.androidDeviceSerial = function(serial) {
        607 this.options_.androidDeviceSerial = serial;
        608 return this;
        609};
        610
        611
        612/**
        613 * Configures the ChromeDriver to launch Chrome on Android via adb. This
        614 * function is shorthand for
        615 * {@link #androidPackage options.androidPackage('com.android.chrome')}.
        616 * @return {!Options} A self reference.
        617 */
        618Options.prototype.androidChrome = function() {
        619 return this.androidPackage('com.android.chrome');
        620};
        621
        622
        623/**
        624 * Sets the package name of the Chrome or WebView app.
        625 *
        626 * @param {?string} pkg The package to connect to, or `null` to disable Android
        627 * and switch back to using desktop Chrome.
        628 * @return {!Options} A self reference.
        629 */
        630Options.prototype.androidPackage = function(pkg) {
        631 this.options_.androidPackage = pkg;
        632 return this;
        633};
        634
        635
        636/**
        637 * Sets the process name of the Activity hosting the WebView (as given by `ps`).
        638 * If not specified, the process name is assumed to be the same as
        639 * {@link #androidPackage}.
        640 *
        641 * @param {string} processName The main activity name.
        642 * @return {!Options} A self reference.
        643 */
        644Options.prototype.androidProcess = function(processName) {
        645 this.options_.androidProcess = processName;
        646 return this;
        647};
        648
        649
        650/**
        651 * Sets whether to connect to an already-running instead of the specified
        652 * {@linkplain #androidProcess app} instead of launching the app with a clean
        653 * data directory.
        654 *
        655 * @param {boolean} useRunning Whether to connect to a running instance.
        656 * @return {!Options} A self reference.
        657 */
        658Options.prototype.androidUseRunningApp = function(useRunning) {
        659 this.options_.androidUseRunningApp = useRunning;
        660 return this;
        661};
        662
        663
        664/**
        665 * Sets the path to Chrome's log file. This path should exist on the machine
        666 * that will launch Chrome.
        667 * @param {string} path Path to the log file to use.
        668 * @return {!Options} A self reference.
        669 */
        670Options.prototype.setChromeLogFile = function(path) {
        671 this.options_.logPath = path;
        672 return this;
        673};
        674
        675
        676/**
        677 * Sets the directory to store Chrome minidumps in. This option is only
        678 * supported when ChromeDriver is running on Linux.
        679 * @param {string} path The directory path.
        680 * @return {!Options} A self reference.
        681 */
        682Options.prototype.setChromeMinidumpPath = function(path) {
        683 this.options_.minidumpPath = path;
        684 return this;
        685};
        686
        687
        688/**
        689 * Configures Chrome to emulate a mobile device. For more information, refer to
        690 * the ChromeDriver project page on [mobile emulation][em]. Configuration
        691 * options include:
        692 *
        693 * - `deviceName`: The name of a pre-configured [emulated device][devem]
        694 * - `width`: screen width, in pixels
        695 * - `height`: screen height, in pixels
        696 * - `pixelRatio`: screen pixel ratio
        697 *
        698 * __Example 1: Using a Pre-configured Device__
        699 *
        700 * var options = new chrome.Options().setMobileEmulation(
        701 * {deviceName: 'Google Nexus 5'});
        702 *
        703 * var driver = new chrome.Driver(options);
        704 *
        705 * __Example 2: Using Custom Screen Configuration__
        706 *
        707 * var options = new chrome.Options().setMobileEmulation({
        708 * width: 360,
        709 * height: 640,
        710 * pixelRatio: 3.0
        711 * });
        712 *
        713 * var driver = new chrome.Driver(options);
        714 *
        715 *
        716 * [em]: https://sites.google.com/a/chromium.org/chromedriver/mobile-emulation
        717 * [devem]: https://developer.chrome.com/devtools/docs/device-mode
        718 *
        719 * @param {?({deviceName: string}|
        720 * {width: number, height: number, pixelRatio: number})} config The
        721 * mobile emulation configuration, or `null` to disable emulation.
        722 * @return {!Options} A self reference.
        723 */
        724Options.prototype.setMobileEmulation = function(config) {
        725 this.options_.mobileEmulation = config;
        726 return this;
        727};
        728
        729
        730/**
        731 * Sets the proxy settings for the new session.
        732 * @param {webdriver.ProxyConfig} proxy The proxy configuration to use.
        733 * @return {!Options} A self reference.
        734 */
        735Options.prototype.setProxy = function(proxy) {
        736 this.proxy_ = proxy;
        737 return this;
        738};
        739
        740
        741/**
        742 * Converts this options instance to a {@link webdriver.Capabilities} object.
        743 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge
        744 * these options into, if any.
        745 * @return {!webdriver.Capabilities} The capabilities.
        746 */
        747Options.prototype.toCapabilities = function(opt_capabilities) {
        748 var capabilities = opt_capabilities || webdriver.Capabilities.chrome();
        749 capabilities.
        750 set(webdriver.Capability.PROXY, this.proxy_).
        751 set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_).
        752 set(OPTIONS_CAPABILITY_KEY, this);
        753 return capabilities;
        754};
        755
        756
        757/**
        758 * Converts this instance to its JSON wire protocol representation. Note this
        759 * function is an implementation not intended for general use.
        760 * @return {{args: !Array.<string>,
        761 * binary: (string|undefined),
        762 * detach: boolean,
        763 * extensions: !Array.<(string|!webdriver.promise.Promise.<string>)>,
        764 * localState: (Object|undefined),
        765 * logPath: (string|undefined),
        766 * prefs: (Object|undefined)}} The JSON wire protocol representation
        767 * of this instance.
        768 * @override
        769 */
        770Options.prototype.serialize = function() {
        771 var json = {};
        772 for (var key in this.options_) {
        773 if (this.options_[key] != null) {
        774 json[key] = this.options_[key];
        775 }
        776 }
        777 if (this.extensions_.length) {
        778 json.extensions = this.extensions_.map(function(extension) {
        779 if (Buffer.isBuffer(extension)) {
        780 return extension.toString('base64');
        781 }
        782 return webdriver.promise.checkedNodeCall(
        783 fs.readFile, extension, 'base64');
        784 });
        785 }
        786 return json;
        787};
        788
        789
        790/**
        791 * Creates a new WebDriver client for Chrome.
        792 *
        793 * @param {(webdriver.Capabilities|Options)=} opt_config The configuration
        794 * options.
        795 * @param {remote.DriverService=} opt_service The session to use; will use
        796 * the {@linkplain #getDefaultService default service} by default.
        797 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
        798 * {@code null} to use the currently active flow.
        799 * @constructor
        800 * @extends {webdriver.WebDriver}
        801 */
        802var Driver = function(opt_config, opt_service, opt_flow) {
        803 var service = opt_service || getDefaultService();
        804 var executor = createExecutor(service.start());
        805
        806 var capabilities =
        807 opt_config instanceof Options ? opt_config.toCapabilities() :
        808 (opt_config || webdriver.Capabilities.chrome());
        809
        810 var driver = webdriver.WebDriver.createSession(
        811 executor, capabilities, opt_flow);
        812
        813 webdriver.WebDriver.call(
        814 this, driver.getSession(), executor, driver.controlFlow());
        815};
        816util.inherits(Driver, webdriver.WebDriver);
        817
        818
        819/**
        820 * This function is a no-op as file detectors are not supported by this
        821 * implementation.
        822 * @override
        823 */
        824Driver.prototype.setFileDetector = function() {
        825};
        826
        827
        828/**
        829 * Schedules a command to launch Chrome App with given ID.
        830 * @param {string} id ID of the App to launch.
        831 * @return {!webdriver.promise.Promise<void>} A promise that will be resolved
        832 * when app is launched.
        833 */
        834Driver.prototype.launchApp = function(id) {
        835 return this.schedule(
        836 new webdriver.Command(Command.LAUNCH_APP).setParameter('id', id),
        837 'Driver.launchApp()');
        838};
        839
        840
        841// PUBLIC API
        842
        843
        844exports.Driver = Driver;
        845exports.Options = Options;
        846exports.ServiceBuilder = ServiceBuilder;
        847exports.getDefaultService = getDefaultService;
        848exports.setDefaultService = setDefaultService;
        \ No newline at end of file diff --git a/docs/source/error.js.src.html b/docs/source/error.js.src.html index a8aa5b0..89a1a72 100644 --- a/docs/source/error.js.src.html +++ b/docs/source/error.js.src.html @@ -1 +1 @@ -error.js

        error.js

        1// Copyright 2014 Selenium committers
        2// Copyright 2014 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16'use strict';
        17
        18var base = require('./_base');
        19
        20
        21/** @type {function(new: bot.Error)} */
        22exports.Error = base.require('bot.Error');
        23
        24/** @type {bot.ErrorCode.} */
        25exports.ErrorCode = base.require('bot.ErrorCode');
        \ No newline at end of file +error.js

        error.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var base = require('./_base');
        21
        22
        23/** @type {function(new: bot.Error)} */
        24exports.Error = base.require('bot.Error');
        25
        26/** @type {bot.ErrorCode.} */
        27exports.ErrorCode = base.require('bot.ErrorCode');
        \ No newline at end of file diff --git a/docs/source/executors.js.src.html b/docs/source/executors.js.src.html index b44c5ad..27312a2 100644 --- a/docs/source/executors.js.src.html +++ b/docs/source/executors.js.src.html @@ -1 +1 @@ -executors.js

        executors.js

        1// Copyright 2013 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Various utilities for working with
        17 * {@link webdriver.CommandExecutor} implementations.
        18 */
        19
        20var HttpClient = require('./http').HttpClient,
        21 HttpExecutor = require('./http').Executor,
        22 promise = require('./_base').require('webdriver.promise');
        23
        24
        25
        26/**
        27 * Wraps a promised {@link webdriver.CommandExecutor}, ensuring no commands
        28 * are executed until the wrapped executor has been fully built.
        29 * @param {!webdriver.promise.Promise.<!webdriver.CommandExecutor>} delegate The
        30 * promised delegate.
        31 * @constructor
        32 * @implements {webdriver.CommandExecutor}
        33 */
        34var DeferredExecutor = function(delegate) {
        35
        36 /** @override */
        37 this.execute = function(command, callback) {
        38 delegate.then(function(executor) {
        39 executor.execute(command, callback);
        40 }, callback);
        41 };
        42};
        43
        44
        45// PUBLIC API
        46
        47
        48/**
        49 * Creates a command executor that uses WebDriver's JSON wire protocol.
        50 * @param {(string|!webdriver.promise.Promise.<string>)} url The server's URL,
        51 * or a promise that will resolve to that URL.
        52 * @returns {!webdriver.CommandExecutor} The new command executor.
        53 */
        54exports.createExecutor = function(url) {
        55 return new DeferredExecutor(promise.when(url, function(url) {
        56 var client = new HttpClient(url);
        57 return new HttpExecutor(client);
        58 }));
        59};
        \ No newline at end of file +executors.js

        executors.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Various utilities for working with
        20 * {@link webdriver.CommandExecutor} implementations.
        21 */
        22
        23var HttpClient = require('./http').HttpClient,
        24 HttpExecutor = require('./http').Executor,
        25 promise = require('./_base').require('webdriver.promise');
        26
        27
        28
        29/**
        30 * Wraps a promised {@link webdriver.CommandExecutor}, ensuring no commands
        31 * are executed until the wrapped executor has been fully built.
        32 * @param {!webdriver.promise.Promise.<!webdriver.CommandExecutor>} delegate The
        33 * promised delegate.
        34 * @constructor
        35 * @implements {webdriver.CommandExecutor}
        36 */
        37var DeferredExecutor = function(delegate) {
        38
        39 /** @override */
        40 this.execute = function(command, callback) {
        41 delegate.then(function(executor) {
        42 executor.execute(command, callback);
        43 }, callback);
        44 };
        45};
        46
        47
        48// PUBLIC API
        49
        50
        51exports.DeferredExecutor = DeferredExecutor;
        52
        53/**
        54 * Creates a command executor that uses WebDriver's JSON wire protocol.
        55 * @param {(string|!webdriver.promise.Promise.<string>)} url The server's URL,
        56 * or a promise that will resolve to that URL.
        57 * @param {string=} opt_proxy (optional) The URL of the HTTP proxy for the
        58 * client to use.
        59 * @returns {!webdriver.CommandExecutor} The new command executor.
        60 */
        61exports.createExecutor = function(url, opt_proxy) {
        62 return new DeferredExecutor(promise.when(url, function(url) {
        63 var client = new HttpClient(url, null, opt_proxy);
        64 return new HttpExecutor(client);
        65 }));
        66};
        \ No newline at end of file diff --git a/docs/source/firefox/binary.js.src.html b/docs/source/firefox/binary.js.src.html new file mode 100644 index 0000000..e35d854 --- /dev/null +++ b/docs/source/firefox/binary.js.src.html @@ -0,0 +1 @@ +binary.js

        firefox/binary.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Manages Firefox binaries. This module is considered internal;
        20 * users should use {@link selenium-webdriver/firefox}.
        21 */
        22
        23'use strict';
        24
        25var child = require('child_process'),
        26 fs = require('fs'),
        27 path = require('path'),
        28 util = require('util');
        29
        30var Serializable = require('..').Serializable,
        31 promise = require('..').promise,
        32 _base = require('../_base'),
        33 io = require('../io'),
        34 exec = require('../io/exec');
        35
        36
        37
        38/** @const */
        39var NO_FOCUS_LIB_X86 = _base.isDevMode() ?
        40 path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') :
        41 path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ;
        42
        43/** @const */
        44var NO_FOCUS_LIB_AMD64 = _base.isDevMode() ?
        45 path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') :
        46 path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ;
        47
        48var X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so';
        49
        50var foundBinary = null;
        51
        52
        53/**
        54 * Checks the default Windows Firefox locations in Program Files.
        55 * @return {!promise.Promise.<?string>} A promise for the located executable.
        56 * The promise will resolve to {@code null} if Fireox was not found.
        57 */
        58function defaultWindowsLocation() {
        59 var files = [
        60 process.env['PROGRAMFILES'] || 'C:\\Program Files',
        61 process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)'
        62 ].map(function(prefix) {
        63 return path.join(prefix, 'Mozilla Firefox\\firefox.exe');
        64 });
        65 return io.exists(files[0]).then(function(exists) {
        66 return exists ? files[0] : io.exists(files[1]).then(function(exists) {
        67 return exists ? files[1] : null;
        68 });
        69 });
        70}
        71
        72
        73/**
        74 * Locates the Firefox binary for the current system.
        75 * @return {!promise.Promise.<string>} A promise for the located binary. The
        76 * promise will be rejected if Firefox cannot be located.
        77 */
        78function findFirefox() {
        79 if (foundBinary) {
        80 return foundBinary;
        81 }
        82
        83 if (process.platform === 'darwin') {
        84 var osxExe = '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
        85 foundBinary = io.exists(osxExe).then(function(exists) {
        86 return exists ? osxExe : null;
        87 });
        88 } else if (process.platform === 'win32') {
        89 foundBinary = defaultWindowsLocation();
        90 } else {
        91 foundBinary = promise.fulfilled(io.findInPath('firefox'));
        92 }
        93
        94 return foundBinary = foundBinary.then(function(found) {
        95 if (found) {
        96 return found;
        97 }
        98 throw Error('Could not locate Firefox on the current system');
        99 });
        100}
        101
        102
        103/**
        104 * Copies the no focus libs into the given profile directory.
        105 * @param {string} profileDir Path to the profile directory to install into.
        106 * @return {!promise.Promise.<string>} The LD_LIBRARY_PATH prefix string to use
        107 * for the installed libs.
        108 */
        109function installNoFocusLibs(profileDir) {
        110 var x86 = path.join(profileDir, 'x86');
        111 var amd64 = path.join(profileDir, 'amd64');
        112
        113 return mkdir(x86)
        114 .then(copyLib.bind(null, NO_FOCUS_LIB_X86, x86))
        115 .then(mkdir.bind(null, amd64))
        116 .then(copyLib.bind(null, NO_FOCUS_LIB_AMD64, amd64))
        117 .then(function() {
        118 return x86 + ':' + amd64;
        119 });
        120
        121 function mkdir(dir) {
        122 return io.exists(dir).then(function(exists) {
        123 if (!exists) {
        124 return promise.checkedNodeCall(fs.mkdir, dir);
        125 }
        126 });
        127 }
        128
        129 function copyLib(src, dir) {
        130 return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB));
        131 }
        132}
        133
        134
        135/**
        136 * Manages a Firefox subprocess configured for use with WebDriver.
        137 *
        138 * @param {string=} opt_exe Path to the Firefox binary to use. If not
        139 * specified, will attempt to locate Firefox on the current system.
        140 * @constructor
        141 * @extends {Serializable.<string>}
        142 */
        143var Binary = function(opt_exe) {
        144 Serializable.call(this);
        145
        146 /** @private {(string|undefined)} */
        147 this.exe_ = opt_exe;
        148
        149 /** @private {!Array.<string>} */
        150 this.args_ = [];
        151
        152 /** @private {!Object.<string, string>} */
        153 this.env_ = {};
        154 Object.keys(process.env).forEach(function(key) {
        155 this.env_[key] = process.env[key];
        156 }.bind(this));
        157 this.env_['MOZ_CRASHREPORTER_DISABLE'] = '1';
        158 this.env_['MOZ_NO_REMOTE'] = '1';
        159 this.env_['NO_EM_RESTART'] = '1';
        160
        161 /** @private {promise.Promise.<!exec.Command>} */
        162 this.command_ = null;
        163};
        164util.inherits(Binary, Serializable);
        165
        166
        167/**
        168 * Add arguments to the command line used to start Firefox.
        169 * @param {...(string|!Array.<string>)} var_args Either the arguments to add as
        170 * varargs, or the arguments as an array.
        171 */
        172Binary.prototype.addArguments = function(var_args) {
        173 for (var i = 0; i < arguments.length; i++) {
        174 if (util.isArray(arguments[i])) {
        175 this.args_ = this.args_.concat(arguments[i]);
        176 } else {
        177 this.args_.push(arguments[i]);
        178 }
        179 }
        180};
        181
        182
        183/**
        184 * Launches Firefox and eturns a promise that will be fulfilled when the process
        185 * terminates.
        186 * @param {string} profile Path to the profile directory to use.
        187 * @return {!promise.Promise.<!exec.Result>} A promise for the process result.
        188 * @throws {Error} If this instance has already been started.
        189 */
        190Binary.prototype.launch = function(profile) {
        191 if (this.command_) {
        192 throw Error('Firefox is already running');
        193 }
        194
        195 var env = {};
        196 Object.keys(this.env_).forEach(function(key) {
        197 env[key] = this.env_[key];
        198 }.bind(this));
        199 env['XRE_PROFILE_PATH'] = profile;
        200
        201 var args = ['-foreground'].concat(this.args_);
        202
        203 this.command_ = promise.when(this.exe_ || findFirefox(), function(firefox) {
        204 if (process.platform === 'win32' || process.platform === 'darwin') {
        205 return exec(firefox, {args: args, env: env});
        206 }
        207 return installNoFocusLibs(profile).then(function(ldLibraryPath) {
        208 env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH'];
        209 env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB;
        210 return exec(firefox, {args: args, env: env});
        211 });
        212 });
        213
        214 return this.command_.then(function() {
        215 // Don't return the actual command handle, just a promise to signal it has
        216 // been started.
        217 });
        218};
        219
        220
        221/**
        222 * Kills the managed Firefox process.
        223 * @return {!promise.Promise} A promise for when the process has terminated.
        224 */
        225Binary.prototype.kill = function() {
        226 if (!this.command_) {
        227 return promise.defer(); // Not running.
        228 }
        229 return this.command_.then(function(command) {
        230 command.kill();
        231 return command.result();
        232 });
        233};
        234
        235
        236/**
        237 * Returns a promise for the wire representation of this binary. Note: the
        238 * FirefoxDriver only supports passing the path to the binary executable over
        239 * the wire; all command line arguments and environment variables will be
        240 * discarded.
        241 *
        242 * @return {!promise.Promise.<string>} A promise for this binary's wire
        243 * representation.
        244 * @override
        245 */
        246Binary.prototype.serialize = function() {
        247 return promise.when(this.exe_ || findFirefox());
        248};
        249
        250
        251// PUBLIC API
        252
        253
        254exports.Binary = Binary;
        255
        \ No newline at end of file diff --git a/docs/source/firefox/extension.js.src.html b/docs/source/firefox/extension.js.src.html new file mode 100644 index 0000000..aeb0928 --- /dev/null +++ b/docs/source/firefox/extension.js.src.html @@ -0,0 +1 @@ +extension.js

        firefox/extension.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/** @fileoverview Utilities for working with Firefox extensions. */
        19
        20'use strict';
        21
        22var AdmZip = require('adm-zip'),
        23 fs = require('fs'),
        24 path = require('path'),
        25 util = require('util'),
        26 xml = require('xml2js');
        27
        28var promise = require('..').promise,
        29 checkedCall = promise.checkedNodeCall,
        30 io = require('../io');
        31
        32
        33/**
        34 * Thrown when there an add-on is malformed.
        35 * @param {string} msg The error message.
        36 * @constructor
        37 * @extends {Error}
        38 */
        39function AddonFormatError(msg) {
        40 Error.call(this);
        41
        42 Error.captureStackTrace(this, AddonFormatError);
        43
        44 /** @override */
        45 this.name = AddonFormatError.name;
        46
        47 /** @override */
        48 this.message = msg;
        49}
        50util.inherits(AddonFormatError, Error);
        51
        52
        53
        54/**
        55 * Installs an extension to the given directory.
        56 * @param {string} extension Path to the extension to install, as either a xpi
        57 * file or a directory.
        58 * @param {string} dir Path to the directory to install the extension in.
        59 * @return {!promise.Promise.<string>} A promise for the add-on ID once
        60 * installed.
        61 */
        62function install(extension, dir) {
        63 return getDetails(extension).then(function(details) {
        64 function returnId() { return details.id; }
        65
        66 var dst = path.join(dir, details.id);
        67 if (extension.slice(-4) === '.xpi') {
        68 if (!details.unpack) {
        69 return io.copy(extension, dst + '.xpi').then(returnId);
        70 } else {
        71 return checkedCall(fs.readFile, extension).then(function(buff) {
        72 var zip = new AdmZip(buff);
        73 // TODO: find an async library for inflating a zip archive.
        74 new AdmZip(buff).extractAllTo(dst, true);
        75 }).then(returnId);
        76 }
        77 } else {
        78 return io.copyDir(extension, dst).then(returnId);
        79 }
        80 });
        81}
        82
        83
        84/**
        85 * Describes a Firefox add-on.
        86 * @typedef {{id: string, name: string, version: string, unpack: boolean}}
        87 */
        88var AddonDetails;
        89
        90
        91/**
        92 * Extracts the details needed to install an add-on.
        93 * @param {string} addonPath Path to the extension directory.
        94 * @return {!promise.Promise.<!AddonDetails>} A promise for the add-on details.
        95 */
        96function getDetails(addonPath) {
        97 return readManifest(addonPath).then(function(doc) {
        98 var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#');
        99 var rdf = getNamespaceId(
        100 doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
        101
        102 var description = doc[rdf + 'RDF'][rdf + 'Description'][0];
        103 var details = {
        104 id: getNodeText(description, em + 'id'),
        105 name: getNodeText(description, em + 'name'),
        106 version: getNodeText(description, em + 'version'),
        107 unpack: getNodeText(description, em + 'unpack') || false
        108 };
        109
        110 if (typeof details.unpack === 'string') {
        111 details.unpack = details.unpack.toLowerCase() === 'true';
        112 }
        113
        114 if (!details.id) {
        115 throw new AddonFormatError('Could not find add-on ID for ' + addonPath);
        116 }
        117
        118 return details;
        119 });
        120
        121 function getNodeText(node, name) {
        122 return node[name] && node[name][0] || '';
        123 }
        124
        125 function getNamespaceId(doc, url) {
        126 var keys = Object.keys(doc);
        127 if (keys.length !== 1) {
        128 throw new AddonFormatError('Malformed manifest for add-on ' + addonPath);
        129 }
        130
        131 var namespaces = doc[keys[0]].$;
        132 var id = '';
        133 Object.keys(namespaces).some(function(ns) {
        134 if (namespaces[ns] !== url) {
        135 return false;
        136 }
        137
        138 if (ns.indexOf(':') != -1) {
        139 id = ns.split(':')[1] + ':';
        140 }
        141 return true;
        142 });
        143 return id;
        144 }
        145}
        146
        147
        148/**
        149 * Reads the manifest for a Firefox add-on.
        150 * @param {string} addonPath Path to a Firefox add-on as a xpi or an extension.
        151 * @return {!promise.Promise.<!Object>} A promise for the parsed manifest.
        152 */
        153function readManifest(addonPath) {
        154 var manifest;
        155
        156 if (addonPath.slice(-4) === '.xpi') {
        157 manifest = checkedCall(fs.readFile, addonPath).then(function(buff) {
        158 var zip = new AdmZip(buff);
        159 if (!zip.getEntry('install.rdf')) {
        160 throw new AddonFormatError(
        161 'Could not find install.rdf in ' + addonPath);
        162 }
        163 var done = promise.defer();
        164 zip.readAsTextAsync('install.rdf', done.fulfill);
        165 return done.promise;
        166 });
        167 } else {
        168 manifest = checkedCall(fs.stat, addonPath).then(function(stats) {
        169 if (!stats.isDirectory()) {
        170 throw Error(
        171 'Add-on path is niether a xpi nor a directory: ' + addonPath);
        172 }
        173 return checkedCall(fs.readFile, path.join(addonPath, 'install.rdf'));
        174 });
        175 }
        176
        177 return manifest.then(function(content) {
        178 return checkedCall(xml.parseString, content);
        179 });
        180}
        181
        182
        183// PUBLIC API
        184
        185
        186exports.install = install;
        \ No newline at end of file diff --git a/docs/source/firefox/index.js.src.html b/docs/source/firefox/index.js.src.html new file mode 100644 index 0000000..0d9cc52 --- /dev/null +++ b/docs/source/firefox/index.js.src.html @@ -0,0 +1 @@ +index.js

        firefox/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines the {@linkplain Driver WebDriver} client for Firefox.
        20 * Each FirefoxDriver instance will be created with an anonymous profile,
        21 * ensuring browser historys do not share session data (cookies, history, cache,
        22 * offline storage, etc.)
        23 *
        24 * __Customizing the Firefox Profile__
        25 *
        26 * The {@link Profile} class may be used to configure the browser profile used
        27 * with WebDriver, with functions to install additional
        28 * {@linkplain Profile#addExtension extensions}, configure browser
        29 * {@linkplain Profile#setPreference preferences}, and more. For example, you
        30 * may wish to include Firebug:
        31 *
        32 * var firefox = require('selenium-webdriver/firefox');
        33 *
        34 * var profile = new firefox.Profile();
        35 * profile.addExtension('/path/to/firebug.xpi');
        36 * profile.setPreference('extensions.firebug.showChromeErrors', true);
        37 *
        38 * var options = new firefox.Options().setProfile(profile);
        39 * var driver = new firefox.Driver(options);
        40 *
        41 * The {@link Profile} class may also be used to configure WebDriver based on a
        42 * pre-existing browser profile:
        43 *
        44 * var profile = new firefox.Profile(
        45 * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing');
        46 * var options = new firefox.Options().setProfile(profile);
        47 * var driver = new firefox.Driver(options);
        48 *
        49 * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will
        50 * create a copy for it to modify. By extension, there are certain browser
        51 * preferences that are required for WebDriver to function properly and they
        52 * will always be overwritten.
        53 *
        54 * __Using a Custom Firefox Binary__
        55 *
        56 * On Windows and OSX, the FirefoxDriver will search for Firefox in its
        57 * default installation location:
        58 *
        59 * * Windows: C:\Program Files and C:\Program Files (x86).
        60 * * Mac OS X: /Applications/Firefox.app
        61 *
        62 * For Linux, Firefox will be located on the PATH: `$(where firefox)`.
        63 *
        64 * You can configure WebDriver to start use a custom Firefox installation with
        65 * the {@link Binary} class:
        66 *
        67 * var firefox = require('selenium-webdriver/firefox');
        68 * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin');
        69 * var options = new firefox.Options().setBinary(binary);
        70 * var driver = new firefox.Driver(options);
        71 *
        72 * __Remote Testing__
        73 *
        74 * You may customize the Firefox binary and profile when running against a
        75 * remote Selenium server. Your custom profile will be packaged as a zip and
        76 * transfered to the remote host for use. The profile will be transferred
        77 * _once for each new session_. The performance impact should be minimal if
        78 * you've only configured a few extra browser preferences. If you have a large
        79 * profile with several extensions, you should consider installing it on the
        80 * remote host and defining its path via the {@link Options} class. Custom
        81 * binaries are never copied to remote machines and must be referenced by
        82 * installation path.
        83 *
        84 * var options = new firefox.Options()
        85 * .setProfile('/profile/path/on/remote/host')
        86 * .setBinary('/install/dir/on/remote/host/firefox-bin');
        87 *
        88 * var driver = new (require('selenium-webdriver')).Builder()
        89 * .forBrowser('firefox')
        90 * .usingServer('http://127.0.0.1:4444/wd/hub')
        91 * .setFirefoxOptions(options)
        92 * .build();
        93 */
        94
        95'use strict';
        96
        97var url = require('url'),
        98 util = require('util');
        99
        100var Binary = require('./binary').Binary,
        101 Profile = require('./profile').Profile,
        102 decodeProfile = require('./profile').decode,
        103 webdriver = require('..'),
        104 executors = require('../executors'),
        105 httpUtil = require('../http/util'),
        106 io = require('../io'),
        107 net = require('../net'),
        108 portprober = require('../net/portprober');
        109
        110
        111/**
        112 * Configuration options for the FirefoxDriver.
        113 * @constructor
        114 */
        115var Options = function() {
        116 /** @private {Profile} */
        117 this.profile_ = null;
        118
        119 /** @private {Binary} */
        120 this.binary_ = null;
        121
        122 /** @private {webdriver.logging.Preferences} */
        123 this.logPrefs_ = null;
        124
        125 /** @private {webdriver.ProxyConfig} */
        126 this.proxy_ = null;
        127};
        128
        129
        130/**
        131 * Sets the profile to use. The profile may be specified as a
        132 * {@link Profile} object or as the path to an existing Firefox profile to use
        133 * as a template.
        134 *
        135 * @param {(string|!Profile)} profile The profile to use.
        136 * @return {!Options} A self reference.
        137 */
        138Options.prototype.setProfile = function(profile) {
        139 if (typeof profile === 'string') {
        140 profile = new Profile(profile);
        141 }
        142 this.profile_ = profile;
        143 return this;
        144};
        145
        146
        147/**
        148 * Sets the binary to use. The binary may be specified as the path to a Firefox
        149 * executable, or as a {@link Binary} object.
        150 *
        151 * @param {(string|!Binary)} binary The binary to use.
        152 * @return {!Options} A self reference.
        153 */
        154Options.prototype.setBinary = function(binary) {
        155 if (typeof binary === 'string') {
        156 binary = new Binary(binary);
        157 }
        158 this.binary_ = binary;
        159 return this;
        160};
        161
        162
        163/**
        164 * Sets the logging preferences for the new session.
        165 * @param {webdriver.logging.Preferences} prefs The logging preferences.
        166 * @return {!Options} A self reference.
        167 */
        168Options.prototype.setLoggingPreferences = function(prefs) {
        169 this.logPrefs_ = prefs;
        170 return this;
        171};
        172
        173
        174/**
        175 * Sets the proxy to use.
        176 *
        177 * @param {webdriver.ProxyConfig} proxy The proxy configuration to use.
        178 * @return {!Options} A self reference.
        179 */
        180Options.prototype.setProxy = function(proxy) {
        181 this.proxy_ = proxy;
        182 return this;
        183};
        184
        185
        186/**
        187 * Converts these options to a {@link webdriver.Capabilities} instance.
        188 *
        189 * @return {!webdriver.Capabilities} A new capabilities object.
        190 */
        191Options.prototype.toCapabilities = function(opt_remote) {
        192 var caps = webdriver.Capabilities.firefox();
        193 if (this.logPrefs_) {
        194 caps.set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_);
        195 }
        196 if (this.proxy_) {
        197 caps.set(webdriver.Capability.PROXY, this.proxy_);
        198 }
        199 if (this.binary_) {
        200 caps.set('firefox_binary', this.binary_);
        201 }
        202 if (this.profile_) {
        203 caps.set('firefox_profile', this.profile_);
        204 }
        205 return caps;
        206};
        207
        208
        209/**
        210 * A WebDriver client for Firefox.
        211 *
        212 * @param {(Options|webdriver.Capabilities|Object)=} opt_config The
        213 * configuration options for this driver, specified as either an
        214 * {@link Options} or {@link webdriver.Capabilities}, or as a raw hash
        215 * object.
        216 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
        217 * schedule commands through. Defaults to the active flow object.
        218 * @constructor
        219 * @extends {webdriver.WebDriver}
        220 */
        221var Driver = function(opt_config, opt_flow) {
        222 var caps;
        223 if (opt_config instanceof Options) {
        224 caps = opt_config.toCapabilities();
        225 } else {
        226 caps = new webdriver.Capabilities(opt_config);
        227 }
        228
        229 var binary = caps.get('firefox_binary') || new Binary();
        230 if (typeof binary === 'string') {
        231 binary = new Binary(binary);
        232 }
        233
        234 var profile = caps.get('firefox_profile') || new Profile();
        235
        236 caps.set('firefox_binary', null);
        237 caps.set('firefox_profile', null);
        238
        239 /** @private {?string} */
        240 this.profilePath_ = null;
        241
        242 /** @private {!Binary} */
        243 this.binary_ = binary;
        244
        245 var self = this;
        246 var serverUrl = portprober.findFreePort().then(function(port) {
        247 var prepareProfile;
        248 if (typeof profile === 'string') {
        249 prepareProfile = decodeProfile(profile).then(function(dir) {
        250 var profile = new Profile(dir);
        251 profile.setPreference('webdriver_firefox_port', port);
        252 return profile.writeToDisk();
        253 });
        254 } else {
        255 profile.setPreference('webdriver_firefox_port', port);
        256 prepareProfile = profile.writeToDisk();
        257 }
        258
        259 return prepareProfile.then(function(dir) {
        260 self.profilePath_ = dir;
        261 return binary.launch(dir);
        262 }).then(function() {
        263 var serverUrl = url.format({
        264 protocol: 'http',
        265 hostname: net.getLoopbackAddress(),
        266 port: port,
        267 pathname: '/hub'
        268 });
        269
        270 return httpUtil.waitForServer(serverUrl, 45 * 1000).then(function() {
        271 return serverUrl;
        272 });
        273 });
        274 });
        275
        276 var executor = executors.createExecutor(serverUrl);
        277 var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow);
        278
        279 webdriver.WebDriver.call(this, driver.getSession(), executor, opt_flow);
        280};
        281util.inherits(Driver, webdriver.WebDriver);
        282
        283
        284/**
        285 * This function is a no-op as file detectors are not supported by this
        286 * implementation.
        287 * @override
        288 */
        289Driver.prototype.setFileDetector = function() {
        290};
        291
        292
        293/** @override */
        294Driver.prototype.quit = function() {
        295 return this.call(function() {
        296 var self = this;
        297 return Driver.super_.prototype.quit.call(this)
        298 .thenFinally(function() {
        299 return self.binary_.kill();
        300 })
        301 .thenFinally(function() {
        302 if (self.profilePath_) {
        303 return io.rmDir(self.profilePath_);
        304 }
        305 });
        306 }, this);
        307};
        308
        309
        310// PUBLIC API
        311
        312
        313exports.Binary = Binary;
        314exports.Driver = Driver;
        315exports.Options = Options;
        316exports.Profile = Profile;
        \ No newline at end of file diff --git a/docs/source/firefox/profile.js.src.html b/docs/source/firefox/profile.js.src.html new file mode 100644 index 0000000..6ff1c3c --- /dev/null +++ b/docs/source/firefox/profile.js.src.html @@ -0,0 +1 @@ +profile.js

        firefox/profile.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Profile management module. This module is considered internal;
        20 * users should use {@link selenium-webdriver/firefox}.
        21 */
        22
        23'use strict';
        24
        25var AdmZip = require('adm-zip'),
        26 fs = require('fs'),
        27 path = require('path'),
        28 util = require('util'),
        29 vm = require('vm');
        30
        31var Serializable = require('..').Serializable,
        32 promise = require('..').promise,
        33 _base = require('../_base'),
        34 io = require('../io'),
        35 extension = require('./extension');
        36
        37
        38/** @const */
        39var WEBDRIVER_PREFERENCES_PATH = _base.isDevMode()
        40 ? path.join(__dirname, '../../../firefox-driver/webdriver.json')
        41 : path.join(__dirname, '../lib/firefox/webdriver.json');
        42
        43/** @const */
        44var WEBDRIVER_EXTENSION_PATH = _base.isDevMode()
        45 ? path.join(__dirname,
        46 '../../../../build/javascript/firefox-driver/webdriver.xpi')
        47 : path.join(__dirname, '../lib/firefox/webdriver.xpi');
        48
        49/** @const */
        50var WEBDRIVER_EXTENSION_NAME = 'fxdriver@googlecode.com';
        51
        52
        53
        54/** @type {Object} */
        55var defaultPreferences = null;
        56
        57/**
        58 * Synchronously loads the default preferences used for the FirefoxDriver.
        59 * @return {!Object} The default preferences JSON object.
        60 */
        61function getDefaultPreferences() {
        62 if (!defaultPreferences) {
        63 var contents = fs.readFileSync(WEBDRIVER_PREFERENCES_PATH, 'utf8');
        64 defaultPreferences = JSON.parse(contents);
        65 }
        66 return defaultPreferences;
        67}
        68
        69
        70/**
        71 * Parses a user.js file in a Firefox profile directory.
        72 * @param {string} f Path to the file to parse.
        73 * @return {!promise.Promise.<!Object>} A promise for the parsed preferences as
        74 * a JSON object. If the file does not exist, an empty object will be
        75 * returned.
        76 */
        77function loadUserPrefs(f) {
        78 var done = promise.defer();
        79 fs.readFile(f, function(err, contents) {
        80 if (err && err.code === 'ENOENT') {
        81 done.fulfill({});
        82 return;
        83 }
        84
        85 if (err) {
        86 done.reject(err);
        87 return;
        88 }
        89
        90 var prefs = {};
        91 var context = vm.createContext({
        92 'user_pref': function(key, value) {
        93 prefs[key] = value;
        94 }
        95 });
        96
        97 vm.runInContext(contents, context, f);
        98 done.fulfill(prefs);
        99 });
        100 return done.promise;
        101}
        102
        103
        104/**
        105 * Copies the properties of one object into another.
        106 * @param {!Object} a The destination object.
        107 * @param {!Object} b The source object to apply as a mixin.
        108 */
        109function mixin(a, b) {
        110 Object.keys(b).forEach(function(key) {
        111 a[key] = b[key];
        112 });
        113}
        114
        115
        116/**
        117 * @param {!Object} defaults The default preferences to write. Will be
        118 * overridden by user.js preferences in the template directory and the
        119 * frozen preferences required by WebDriver.
        120 * @param {string} dir Path to the directory write the file to.
        121 * @return {!promise.Promise.<string>} A promise for the profile directory,
        122 * to be fulfilled when user preferences have been written.
        123 */
        124function writeUserPrefs(prefs, dir) {
        125 var userPrefs = path.join(dir, 'user.js');
        126 return loadUserPrefs(userPrefs).then(function(overrides) {
        127 mixin(prefs, overrides);
        128 mixin(prefs, getDefaultPreferences()['frozen']);
        129
        130 var contents = Object.keys(prefs).map(function(key) {
        131 return 'user_pref(' + JSON.stringify(key) + ', ' +
        132 JSON.stringify(prefs[key]) + ');';
        133 }).join('\n');
        134
        135 var done = promise.defer();
        136 fs.writeFile(userPrefs, contents, function(err) {
        137 err && done.reject(err) || done.fulfill(dir);
        138 });
        139 return done.promise;
        140 });
        141};
        142
        143
        144/**
        145 * Installs a group of extensions in the given profile directory. If the
        146 * WebDriver extension is not included in this set, the default version
        147 * bundled with this package will be installed.
        148 * @param {!Array.<string>} extensions The extensions to install, as a
        149 * path to an unpacked extension directory or a path to a xpi file.
        150 * @param {string} dir The profile directory to install to.
        151 * @param {boolean=} opt_excludeWebDriverExt Whether to skip installation of
        152 * the default WebDriver extension.
        153 * @return {!promise.Promise.<string>} A promise for the main profile directory
        154 * once all extensions have been installed.
        155 */
        156function installExtensions(extensions, dir, opt_excludeWebDriverExt) {
        157 var hasWebDriver = !!opt_excludeWebDriverExt;
        158 var next = 0;
        159 var extensionDir = path.join(dir, 'extensions');
        160 var done = promise.defer();
        161
        162 return io.exists(extensionDir).then(function(exists) {
        163 if (!exists) {
        164 return promise.checkedNodeCall(fs.mkdir, extensionDir);
        165 }
        166 }).then(function() {
        167 installNext();
        168 return done.promise;
        169 });
        170
        171 function installNext() {
        172 if (!done.isPending()) {
        173 return;
        174 }
        175
        176 if (next >= extensions.length) {
        177 if (hasWebDriver) {
        178 done.fulfill(dir);
        179 } else {
        180 install(WEBDRIVER_EXTENSION_PATH);
        181 }
        182 } else {
        183 install(extensions[next++]);
        184 }
        185 }
        186
        187 function install(ext) {
        188 extension.install(ext, extensionDir).then(function(id) {
        189 hasWebDriver = hasWebDriver || (id === WEBDRIVER_EXTENSION_NAME);
        190 installNext();
        191 }, done.reject);
        192 }
        193}
        194
        195
        196/**
        197 * Decodes a base64 encoded profile.
        198 * @param {string} data The base64 encoded string.
        199 * @return {!promise.Promise.<string>} A promise for the path to the decoded
        200 * profile directory.
        201 */
        202function decode(data) {
        203 return io.tmpFile().then(function(file) {
        204 var buf = new Buffer(data, 'base64');
        205 return promise.checkedNodeCall(fs.writeFile, file, buf).then(function() {
        206 return io.tmpDir();
        207 }).then(function(dir) {
        208 var zip = new AdmZip(file);
        209 zip.extractAllTo(dir); // Sync only? Why?? :-(
        210 return dir;
        211 });
        212 });
        213}
        214
        215
        216
        217/**
        218 * Models a Firefox proifle directory for use with the FirefoxDriver. The
        219 * {@code Proifle} directory uses an in-memory model until {@link #writeToDisk}
        220 * is called.
        221 * @param {string=} opt_dir Path to an existing Firefox profile directory to
        222 * use a template for this profile. If not specified, a blank profile will
        223 * be used.
        224 * @constructor
        225 * @extends {Serializable.<string>}
        226 */
        227var Profile = function(opt_dir) {
        228 Serializable.call(this);
        229
        230 /** @private {!Object} */
        231 this.preferences_ = {};
        232
        233 mixin(this.preferences_, getDefaultPreferences()['mutable']);
        234 mixin(this.preferences_, getDefaultPreferences()['frozen']);
        235
        236 /** @private {boolean} */
        237 this.nativeEventsEnabled_ = true;
        238
        239 /** @private {(string|undefined)} */
        240 this.template_ = opt_dir;
        241
        242 /** @private {number} */
        243 this.port_ = 0;
        244
        245 /** @private {!Array.<string>} */
        246 this.extensions_ = [];
        247};
        248util.inherits(Profile, Serializable);
        249
        250
        251/**
        252 * Registers an extension to be included with this profile.
        253 * @param {string} extension Path to the extension to include, as either an
        254 * unpacked extension directory or the path to a xpi file.
        255 */
        256Profile.prototype.addExtension = function(extension) {
        257 this.extensions_.push(extension);
        258};
        259
        260
        261/**
        262 * Sets a desired preference for this profile.
        263 * @param {string} key The preference key.
        264 * @param {(string|number|boolean)} value The preference value.
        265 * @throws {Error} If attempting to set a frozen preference.
        266 */
        267Profile.prototype.setPreference = function(key, value) {
        268 var frozen = getDefaultPreferences()['frozen'];
        269 if (frozen.hasOwnProperty(key) && frozen[key] !== value) {
        270 throw Error('You may not set ' + key + '=' + JSON.stringify(value)
        271 + '; value is frozen for proper WebDriver functionality ('
        272 + key + '=' + JSON.stringify(frozen[key]) + ')');
        273 }
        274 this.preferences_[key] = value;
        275};
        276
        277
        278/**
        279 * Returns the currently configured value of a profile preference. This does
        280 * not include any defaults defined in the profile's template directory user.js
        281 * file (if a template were specified on construction).
        282 * @param {string} key The desired preference.
        283 * @return {(string|number|boolean|undefined)} The current value of the
        284 * requested preference.
        285 */
        286Profile.prototype.getPreference = function(key) {
        287 return this.preferences_[key];
        288};
        289
        290
        291/**
        292 * @return {number} The port this profile is currently configured to use, or
        293 * 0 if the port will be selected at random when the profile is written
        294 * to disk.
        295 */
        296Profile.prototype.getPort = function() {
        297 return this.port_;
        298};
        299
        300
        301/**
        302 * Sets the port to use for the WebDriver extension loaded by this profile.
        303 * @param {number} port The desired port, or 0 to use any free port.
        304 */
        305Profile.prototype.setPort = function(port) {
        306 this.port_ = port;
        307};
        308
        309
        310/**
        311 * @return {boolean} Whether the FirefoxDriver is configured to automatically
        312 * accept untrusted SSL certificates.
        313 */
        314Profile.prototype.acceptUntrustedCerts = function() {
        315 return !!this.preferences_['webdriver_accept_untrusted_certs'];
        316};
        317
        318
        319/**
        320 * Sets whether the FirefoxDriver should automatically accept untrusted SSL
        321 * certificates.
        322 * @param {boolean} value .
        323 */
        324Profile.prototype.setAcceptUntrustedCerts = function(value) {
        325 this.preferences_['webdriver_accept_untrusted_certs'] = !!value;
        326};
        327
        328
        329/**
        330 * Sets whether to assume untrusted certificates come from untrusted issuers.
        331 * @param {boolean} value .
        332 */
        333Profile.prototype.setAssumeUntrustedCertIssuer = function(value) {
        334 this.preferences_['webdriver_assume_untrusted_issuer'] = !!value;
        335};
        336
        337
        338/**
        339 * @return {boolean} Whether to assume untrusted certs come from untrusted
        340 * issuers.
        341 */
        342Profile.prototype.assumeUntrustedCertIssuer = function() {
        343 return !!this.preferences_['webdriver_assume_untrusted_issuer'];
        344};
        345
        346
        347/**
        348 * Sets whether to use native events with this profile.
        349 * @param {boolean} enabled .
        350 */
        351Profile.prototype.setNativeEventsEnabled = function(enabled) {
        352 this.nativeEventsEnabled_ = enabled;
        353};
        354
        355
        356/**
        357 * Returns whether native events are enabled in this profile.
        358 * @return {boolean} .
        359 */
        360Profile.prototype.nativeEventsEnabled = function() {
        361 return this.nativeEventsEnabled_;
        362};
        363
        364
        365/**
        366 * Writes this profile to disk.
        367 * @param {boolean=} opt_excludeWebDriverExt Whether to exclude the WebDriver
        368 * extension from the generated profile. Used to reduce the size of an
        369 * {@link #encode() encoded profile} since the server will always install
        370 * the extension itself.
        371 * @return {!promise.Promise.<string>} A promise for the path to the new
        372 * profile directory.
        373 */
        374Profile.prototype.writeToDisk = function(opt_excludeWebDriverExt) {
        375 var profileDir = io.tmpDir();
        376 if (this.template_) {
        377 profileDir = profileDir.then(function(dir) {
        378 return io.copyDir(
        379 this.template_, dir, /(parent\.lock|lock|\.parentlock)/);
        380 }.bind(this));
        381 }
        382
        383 // Freeze preferences for async operations.
        384 var prefs = {};
        385 mixin(prefs, this.preferences_);
        386
        387 // Freeze extensions for async operations.
        388 var extensions = this.extensions_.concat();
        389
        390 return profileDir.then(function(dir) {
        391 return writeUserPrefs(prefs, dir);
        392 }).then(function(dir) {
        393 return installExtensions(extensions, dir, !!opt_excludeWebDriverExt);
        394 });
        395};
        396
        397
        398/**
        399 * Encodes this profile as a zipped, base64 encoded directory.
        400 * @return {!promise.Promise.<string>} A promise for the encoded profile.
        401 */
        402Profile.prototype.encode = function() {
        403 return this.writeToDisk(true).then(function(dir) {
        404 var zip = new AdmZip();
        405 zip.addLocalFolder(dir, '');
        406 return io.tmpFile().then(function(file) {
        407 zip.writeZip(file); // Sync! Why oh why :-(
        408 return promise.checkedNodeCall(fs.readFile, file);
        409 });
        410 }).then(function(data) {
        411 return new Buffer(data).toString('base64');
        412 });
        413};
        414
        415
        416/**
        417 * Encodes this profile as a zipped, base64 encoded directory.
        418 * @return {!promise.Promise.<string>} A promise for the encoded profile.
        419 * @override
        420 */
        421Profile.prototype.serialize = function() {
        422 return this.encode();
        423};
        424
        425
        426// PUBLIC API
        427
        428
        429exports.Profile = Profile;
        430exports.decode = decode;
        431exports.loadUserPrefs = loadUserPrefs;
        \ No newline at end of file diff --git a/docs/source/http/index.js.src.html b/docs/source/http/index.js.src.html index 44e2cb2..e3f93fe 100644 --- a/docs/source/http/index.js.src.html +++ b/docs/source/http/index.js.src.html @@ -1 +1 @@ -index.js

        http/index.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines a the {@code webdriver.http.Client} for use with
        17 * NodeJS.
        18 */
        19
        20var http = require('http'),
        21 url = require('url');
        22
        23var base = require('../_base'),
        24 HttpResponse = base.require('webdriver.http.Response');
        25
        26
        27/**
        28 * A {@link webdriver.http.Client} implementation using Node's built-in http
        29 * module.
        30 * @param {string} serverUrl URL for the WebDriver server to send commands to.
        31 * @constructor
        32 * @implements {webdriver.http.Client}
        33 */
        34var HttpClient = function(serverUrl) {
        35 var parsedUrl = url.parse(serverUrl);
        36 if (!parsedUrl.hostname) {
        37 throw new Error('Invalid server URL: ' + serverUrl);
        38 }
        39
        40 /**
        41 * Base options for each request.
        42 * @private {!Object}
        43 */
        44 this.options_ = {
        45 host: parsedUrl.hostname,
        46 path: parsedUrl.pathname,
        47 port: parsedUrl.port
        48 };
        49};
        50
        51
        52/** @override */
        53HttpClient.prototype.send = function(httpRequest, callback) {
        54 var data;
        55 httpRequest.headers['Content-Length'] = 0;
        56 if (httpRequest.method == 'POST' || httpRequest.method == 'PUT') {
        57 data = JSON.stringify(httpRequest.data);
        58 httpRequest.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
        59 httpRequest.headers['Content-Type'] = 'application/json;charset=UTF-8';
        60 }
        61
        62 var path = this.options_.path;
        63 if (path[path.length - 1] === '/' && httpRequest.path[0] === '/') {
        64 path += httpRequest.path.substring(1);
        65 } else {
        66 path += httpRequest.path;
        67 }
        68
        69 sendRequest({
        70 method: httpRequest.method,
        71 host: this.options_.host,
        72 port: this.options_.port,
        73 path: path,
        74 headers: httpRequest.headers
        75 }, callback, data);
        76};
        77
        78
        79/**
        80 * Sends a single HTTP request.
        81 * @param {!Object} options The request options.
        82 * @param {function(Error, !webdriver.http.Response=)} callback The function to
        83 * invoke with the server's response.
        84 * @param {string=} opt_data The data to send with the request.
        85 */
        86var sendRequest = function(options, callback, opt_data) {
        87 var request = http.request(options, function(response) {
        88 if (response.statusCode == 302 || response.statusCode == 303) {
        89 var location = url.parse(response.headers['location']);
        90
        91 if (!location.hostname) {
        92 location.hostname = options.host;
        93 location.port = options.port;
        94 }
        95
        96 request.abort();
        97 sendRequest({
        98 method: 'GET',
        99 host: location.hostname,
        100 path: location.pathname + (location.search || ''),
        101 port: location.port,
        102 headers: {
        103 'Accept': 'application/json; charset=utf-8'
        104 }
        105 }, callback);
        106 return;
        107 }
        108
        109 var body = [];
        110 response.on('data', body.push.bind(body));
        111 response.on('end', function() {
        112 var resp = new HttpResponse(response.statusCode,
        113 response.headers, body.join('').replace(/\0/g, ''));
        114 callback(null, resp);
        115 });
        116 });
        117
        118 request.on('error', function(e) {
        119 if (e.code === 'ECONNRESET') {
        120 setTimeout(function() {
        121 sendRequest(options, callback, opt_data);
        122 }, 15);
        123 } else {
        124 var message = e.message;
        125 if (e.code) {
        126 message = e.code + ' ' + message;
        127 }
        128 callback(new Error(message));
        129 }
        130 });
        131
        132 if (opt_data) {
        133 request.write(opt_data);
        134 }
        135
        136 request.end();
        137};
        138
        139
        140// PUBLIC API
        141
        142/** @type {webdriver.http.Executor.} */
        143exports.Executor = base.require('webdriver.http.Executor');
        144
        145/** @type {webdriver.http.Request.} */
        146exports.Request = base.require('webdriver.http.Request');
        147
        148/** @type {webdriver.http.Response.} */
        149exports.Response = base.require('webdriver.http.Response');
        150
        151exports.HttpClient = HttpClient;
        \ No newline at end of file +index.js

        http/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines the {@code webdriver.http.Client} for use with
        20 * NodeJS.
        21 */
        22
        23var http = require('http'),
        24 url = require('url');
        25
        26var base = require('../_base'),
        27 HttpResponse = base.require('webdriver.http.Response');
        28
        29
        30/**
        31 * A {@link webdriver.http.Client} implementation using Node's built-in http
        32 * module.
        33 * @param {string} serverUrl URL for the WebDriver server to send commands to.
        34 * @param {http.Agent=} opt_agent The agent to use for each request.
        35 * Defaults to {@code http.globalAgent}.
        36 * @param {string=} opt_proxy The proxy to use for the connection to the server.
        37 * Default is to use no proxy.
        38 * @constructor
        39 * @implements {webdriver.http.Client}
        40 */
        41var HttpClient = function(serverUrl, opt_agent, opt_proxy) {
        42 var parsedUrl = url.parse(serverUrl);
        43 if (!parsedUrl.hostname) {
        44 throw new Error('Invalid server URL: ' + serverUrl);
        45 }
        46
        47 /** @private {http.Agent} */
        48 this.agent_ = opt_agent;
        49
        50 /** @private {string} */
        51 this.proxy_ = opt_proxy;
        52
        53 /**
        54 * Base options for each request.
        55 * @private {!Object}
        56 */
        57 this.options_ = {
        58 host: parsedUrl.hostname,
        59 path: parsedUrl.pathname,
        60 port: parsedUrl.port
        61 };
        62};
        63
        64
        65/** @override */
        66HttpClient.prototype.send = function(httpRequest, callback) {
        67 var data;
        68 httpRequest.headers['Content-Length'] = 0;
        69 if (httpRequest.method == 'POST' || httpRequest.method == 'PUT') {
        70 data = JSON.stringify(httpRequest.data);
        71 httpRequest.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
        72 httpRequest.headers['Content-Type'] = 'application/json;charset=UTF-8';
        73 }
        74
        75 var path = this.options_.path;
        76 if (path[path.length - 1] === '/' && httpRequest.path[0] === '/') {
        77 path += httpRequest.path.substring(1);
        78 } else {
        79 path += httpRequest.path;
        80 }
        81
        82 var options = {
        83 method: httpRequest.method,
        84 host: this.options_.host,
        85 port: this.options_.port,
        86 path: path,
        87 headers: httpRequest.headers
        88 };
        89
        90 if (this.agent_) {
        91 options.agent = this.agent_;
        92 }
        93
        94 sendRequest(options, callback, data, this.proxy_);
        95};
        96
        97
        98/**
        99 * Sends a single HTTP request.
        100 * @param {!Object} options The request options.
        101 * @param {function(Error, !webdriver.http.Response=)} callback The function to
        102 * invoke with the server's response.
        103 * @param {string=} opt_data The data to send with the request.
        104 * @param {string=} opt_proxy The proxy server to use for the request.
        105 */
        106var sendRequest = function(options, callback, opt_data, opt_proxy) {
        107 var host = options.host;
        108 var port = options.port;
        109
        110 if (opt_proxy) {
        111 var proxy = url.parse(opt_proxy);
        112
        113 options.headers['Host'] = options.host;
        114 options.host = proxy.hostname;
        115 options.port = proxy.port;
        116
        117 if (proxy.auth) {
        118 options.headers['Proxy-Authorization'] =
        119 'Basic ' + new Buffer(proxy.auth).toString('base64');
        120 }
        121 }
        122
        123 var request = http.request(options, function(response) {
        124 if (response.statusCode == 302 || response.statusCode == 303) {
        125 try {
        126 var location = url.parse(response.headers['location']);
        127 } catch (ex) {
        128 callback(Error(
        129 'Failed to parse "Location" header for server redirect: ' +
        130 ex.message + '\nResponse was: \n' +
        131 new HttpResponse(response.statusCode, response.headers, '')));
        132 return;
        133 }
        134
        135 if (!location.hostname) {
        136 location.hostname = host;
        137 location.port = port;
        138 }
        139
        140 request.abort();
        141 sendRequest({
        142 method: 'GET',
        143 host: location.hostname,
        144 path: location.pathname + (location.search || ''),
        145 port: location.port,
        146 headers: {
        147 'Accept': 'application/json; charset=utf-8'
        148 }
        149 }, callback, undefined, opt_proxy);
        150 return;
        151 }
        152
        153 var body = [];
        154 response.on('data', body.push.bind(body));
        155 response.on('end', function() {
        156 var resp = new HttpResponse(response.statusCode,
        157 response.headers, body.join('').replace(/\0/g, ''));
        158 callback(null, resp);
        159 });
        160 });
        161
        162 request.on('error', function(e) {
        163 if (e.code === 'ECONNRESET') {
        164 setTimeout(function() {
        165 sendRequest(options, callback, opt_data, opt_proxy);
        166 }, 15);
        167 } else {
        168 var message = e.message;
        169 if (e.code) {
        170 message = e.code + ' ' + message;
        171 }
        172 callback(new Error(message));
        173 }
        174 });
        175
        176 if (opt_data) {
        177 request.write(opt_data);
        178 }
        179
        180 request.end();
        181};
        182
        183
        184// PUBLIC API
        185
        186/** @type {webdriver.http.Executor.} */
        187exports.Executor = base.require('webdriver.http.Executor');
        188
        189/** @type {webdriver.http.Request.} */
        190exports.Request = base.require('webdriver.http.Request');
        191
        192/** @type {webdriver.http.Response.} */
        193exports.Response = base.require('webdriver.http.Response');
        194
        195exports.HttpClient = HttpClient;
        \ No newline at end of file diff --git a/docs/source/http/util.js.src.html b/docs/source/http/util.js.src.html index f4b4915..e0a159c 100644 --- a/docs/source/http/util.js.src.html +++ b/docs/source/http/util.js.src.html @@ -1 +1 @@ -util.js

        http/util.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview Various HTTP utilities.
        18 */
        19
        20var base = require('../_base'),
        21 HttpClient = require('./index').HttpClient,
        22 checkResponse = base.require('bot.response').checkResponse,
        23 Executor = base.require('webdriver.http.Executor'),
        24 HttpRequest = base.require('webdriver.http.Request'),
        25 Command = base.require('webdriver.Command'),
        26 CommandName = base.require('webdriver.CommandName'),
        27 promise = base.require('webdriver.promise');
        28
        29
        30
        31/**
        32 * Queries a WebDriver server for its current status.
        33 * @param {string} url Base URL of the server to query.
        34 * @param {function(Error, *=)} callback The function to call with the
        35 * response.
        36 */
        37function getStatus(url, callback) {
        38 var client = new HttpClient(url);
        39 var executor = new Executor(client);
        40 var command = new Command(CommandName.GET_SERVER_STATUS);
        41 executor.execute(command, function(err, responseObj) {
        42 if (err) return callback(err);
        43 try {
        44 checkResponse(responseObj);
        45 } catch (ex) {
        46 return callback(ex);
        47 }
        48 callback(null, responseObj['value']);
        49 });
        50}
        51
        52
        53// PUBLIC API
        54
        55
        56/**
        57 * Queries a WebDriver server for its current status.
        58 * @param {string} url Base URL of the server to query.
        59 * @return {!webdriver.promise.Promise.<!Object>} A promise that resolves with
        60 * a hash of the server status.
        61 */
        62exports.getStatus = function(url) {
        63 return promise.checkedNodeCall(getStatus.bind(null, url));
        64};
        65
        66
        67/**
        68 * Waits for a WebDriver server to be healthy and accepting requests.
        69 * @param {string} url Base URL of the server to query.
        70 * @param {number} timeout How long to wait for the server.
        71 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        72 * server is ready.
        73 */
        74exports.waitForServer = function(url, timeout) {
        75 var ready = promise.defer(),
        76 start = Date.now(),
        77 checkServerStatus = getStatus.bind(null, url, onResponse);
        78 checkServerStatus();
        79 return ready.promise;
        80
        81 function onResponse(err) {
        82 if (!ready.isPending()) return;
        83 if (!err) return ready.fulfill();
        84
        85 if (Date.now() - start > timeout) {
        86 ready.reject(
        87 Error('Timed out waiting for the WebDriver server at ' + url));
        88 } else {
        89 setTimeout(function() {
        90 if (ready.isPending()) {
        91 checkServerStatus();
        92 }
        93 }, 50);
        94 }
        95 }
        96};
        97
        98
        99/**
        100 * Polls a URL with GET requests until it returns a 2xx response or the
        101 * timeout expires.
        102 * @param {string} url The URL to poll.
        103 * @param {number} timeout How long to wait, in milliseconds.
        104 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        105 * URL responds with 2xx.
        106 */
        107exports.waitForUrl = function(url, timeout) {
        108 var client = new HttpClient(url),
        109 request = new HttpRequest('GET', ''),
        110 testUrl = client.send.bind(client, request, onResponse),
        111 ready = promise.defer(),
        112 start = Date.now();
        113 testUrl();
        114 return ready.promise;
        115
        116 function onResponse(err, response) {
        117 if (!ready.isPending()) return;
        118 if (!err && response.status > 199 && response.status < 300) {
        119 return ready.fulfill();
        120 }
        121
        122 if (Date.now() - start > timeout) {
        123 ready.reject(Error(
        124 'Timed out waiting for the URL to return 2xx: ' + url));
        125 } else {
        126 setTimeout(function() {
        127 if (ready.isPending()) {
        128 testUrl();
        129 }
        130 }, 50);
        131 }
        132 }
        133};
        \ No newline at end of file +util.js

        http/util.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Various HTTP utilities.
        20 */
        21
        22var base = require('../_base'),
        23 HttpClient = require('./index').HttpClient,
        24 checkResponse = base.require('bot.response').checkResponse,
        25 Executor = base.require('webdriver.http.Executor'),
        26 HttpRequest = base.require('webdriver.http.Request'),
        27 Command = base.require('webdriver.Command'),
        28 CommandName = base.require('webdriver.CommandName'),
        29 promise = base.require('webdriver.promise');
        30
        31
        32
        33/**
        34 * Queries a WebDriver server for its current status.
        35 * @param {string} url Base URL of the server to query.
        36 * @param {function(Error, *=)} callback The function to call with the
        37 * response.
        38 */
        39function getStatus(url, callback) {
        40 var client = new HttpClient(url);
        41 var executor = new Executor(client);
        42 var command = new Command(CommandName.GET_SERVER_STATUS);
        43 executor.execute(command, function(err, responseObj) {
        44 if (err) return callback(err);
        45 try {
        46 checkResponse(responseObj);
        47 } catch (ex) {
        48 return callback(ex);
        49 }
        50 callback(null, responseObj['value']);
        51 });
        52}
        53
        54
        55// PUBLIC API
        56
        57
        58/**
        59 * Queries a WebDriver server for its current status.
        60 * @param {string} url Base URL of the server to query.
        61 * @return {!webdriver.promise.Promise.<!Object>} A promise that resolves with
        62 * a hash of the server status.
        63 */
        64exports.getStatus = function(url) {
        65 return promise.checkedNodeCall(getStatus.bind(null, url));
        66};
        67
        68
        69/**
        70 * Waits for a WebDriver server to be healthy and accepting requests.
        71 * @param {string} url Base URL of the server to query.
        72 * @param {number} timeout How long to wait for the server.
        73 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        74 * server is ready.
        75 */
        76exports.waitForServer = function(url, timeout) {
        77 var ready = promise.defer(),
        78 start = Date.now(),
        79 checkServerStatus = getStatus.bind(null, url, onResponse);
        80 checkServerStatus();
        81 return ready.promise;
        82
        83 function onResponse(err) {
        84 if (!ready.isPending()) return;
        85 if (!err) return ready.fulfill();
        86
        87 if (Date.now() - start > timeout) {
        88 ready.reject(
        89 Error('Timed out waiting for the WebDriver server at ' + url));
        90 } else {
        91 setTimeout(function() {
        92 if (ready.isPending()) {
        93 checkServerStatus();
        94 }
        95 }, 50);
        96 }
        97 }
        98};
        99
        100
        101/**
        102 * Polls a URL with GET requests until it returns a 2xx response or the
        103 * timeout expires.
        104 * @param {string} url The URL to poll.
        105 * @param {number} timeout How long to wait, in milliseconds.
        106 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        107 * URL responds with 2xx.
        108 */
        109exports.waitForUrl = function(url, timeout) {
        110 var client = new HttpClient(url),
        111 request = new HttpRequest('GET', ''),
        112 testUrl = client.send.bind(client, request, onResponse),
        113 ready = promise.defer(),
        114 start = Date.now();
        115 testUrl();
        116 return ready.promise;
        117
        118 function onResponse(err, response) {
        119 if (!ready.isPending()) return;
        120 if (!err && response.status > 199 && response.status < 300) {
        121 return ready.fulfill();
        122 }
        123
        124 if (Date.now() - start > timeout) {
        125 ready.reject(Error(
        126 'Timed out waiting for the URL to return 2xx: ' + url));
        127 } else {
        128 setTimeout(function() {
        129 if (ready.isPending()) {
        130 testUrl();
        131 }
        132 }, 50);
        133 }
        134 }
        135};
        \ No newline at end of file diff --git a/docs/source/ie.js.src.html b/docs/source/ie.js.src.html new file mode 100644 index 0000000..9591bb8 --- /dev/null +++ b/docs/source/ie.js.src.html @@ -0,0 +1 @@ +ie.js

        ie.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a {@linkplain Driver WebDriver} client for Microsoft's
        20 * Internet Explorer. Before using the IEDriver, you must download the latest
        21 * [IEDriverServer](http://selenium-release.storage.googleapis.com/index.html)
        22 * and place it on your
        23 * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29). You must also apply
        24 * the system configuration outlined on the Selenium project
        25 * [wiki](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver)
        26 */
        27
        28'use strict';
        29
        30var fs = require('fs'),
        31 util = require('util');
        32
        33var webdriver = require('./index'),
        34 executors = require('./executors'),
        35 io = require('./io'),
        36 portprober = require('./net/portprober'),
        37 remote = require('./remote');
        38
        39
        40/**
        41 * @const
        42 * @final
        43 */
        44var IEDRIVER_EXE = 'IEDriverServer.exe';
        45
        46
        47
        48/**
        49 * IEDriverServer logging levels.
        50 * @enum {string}
        51 */
        52var Level = {
        53 FATAL: 'FATAL',
        54 ERROR: 'ERROR',
        55 WARN: 'WARN',
        56 INFO: 'INFO',
        57 DEBUG: 'DEBUG',
        58 TRACE: 'TRACE'
        59};
        60
        61
        62
        63/**
        64 * Option keys:
        65 * https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#ie-specific
        66 * @enum {string}
        67 */
        68var Key = {
        69 IGNORE_PROTECTED_MODE_SETTINGS: 'ignoreProtectedModeSettings',
        70 IGNORE_ZOOM_SETTING: 'ignoreZoomSetting',
        71 INITIAL_BROWSER_URL: 'initialBrowserUrl',
        72 ENABLE_PERSISTENT_HOVER: 'enablePersistentHover',
        73 ENABLE_ELEMENT_CACHE_CLEANUP: 'enableElementCacheCleanup',
        74 REQUIRE_WINDOW_FOCUS: 'requireWindowFocus',
        75 BROWSER_ATTACH_TIMEOUT: 'browserAttachTimeout',
        76 FORCE_CREATE_PROCESS: 'ie.forceCreateProcessApi',
        77 BROWSER_COMMAND_LINE_SWITCHES: 'ie.browserCommandLineSwitches',
        78 USE_PER_PROCESS_PROXY: 'ie.usePerProcessProxy',
        79 ENSURE_CLEAN_SESSION: 'ie.ensureCleanSession',
        80 LOG_FILE: 'logFile',
        81 LOG_LEVEL: 'logLevel',
        82 HOST: 'host',
        83 EXTRACT_PATH: 'extractPath',
        84 SILENT: 'silent'
        85};
        86
        87
        88/**
        89 * Class for managing IEDriver specific options.
        90 * @constructor
        91 */
        92var Options = function() {
        93 /** @private {!Object<(boolean|number|string)>} */
        94 this.options_ = {};
        95
        96 /** @private {(webdriver.ProxyConfig|null)} */
        97 this.proxy_ = null;
        98};
        99
        100
        101
        102/**
        103 * Extracts the IEDriver specific options from the given capabilities
        104 * object.
        105 * @param {!webdriver.Capabilities} capabilities The capabilities object.
        106 * @return {!Options} The IEDriver options.
        107 */
        108Options.fromCapabilities = function(capabilities) {
        109 var options = new Options();
        110 var map = options.options_;
        111
        112 Object.keys(Key).forEach(function(key) {
        113 key = Key[key];
        114 if (capabilities.has(key)) {
        115 map[key] = capabilities.get(key);
        116 }
        117 });
        118
        119 if (capabilities.has(webdriver.Capability.PROXY)) {
        120 options.setProxy(capabilities.get(webdriver.Capability.PROXY));
        121 }
        122
        123 return options;
        124};
        125
        126
        127/**
        128 * Whether to disable the protected mode settings check when the session is
        129 * created. Disbling this setting may lead to significant instability as the
        130 * browser may become unresponsive/hang. Only "best effort" support is provided
        131 * when using this capability.
        132 *
        133 * For more information, refer to the IEDriver's
        134 * [required system configuration](http://goo.gl/eH0Yi3).
        135 *
        136 * @param {boolean} ignoreSettings Whether to ignore protected mode settings.
        137 * @return {!Options} A self reference.
        138 */
        139Options.prototype.introduceFlakinessByIgnoringProtectedModeSettings =
        140 function(ignoreSettings) {
        141 this.options_[Key.IGNORE_PROTECTED_MODE_SETTINGS] = !!ignoreSettings;
        142 return this;
        143 };
        144
        145
        146/**
        147 * Indicates whether to skip the check that the browser's zoom level is set to
        148 * 100%.
        149 *
        150 * @parm {boolean} ignore Whether to ignore the browser's zoom level settings.
        151 * @return {!Options} A self reference.
        152 */
        153Options.prototype.ignoreZoomSetting = function(ignore) {
        154 this.options_[Key.IGNORE_ZOOM_SETTING] = !!ignore;
        155 return this;
        156};
        157
        158
        159/**
        160 * Sets the initial URL loaded when IE starts. This is intended to be used with
        161 * {@link #ignoreProtectedModeSettings} to allow the user to initialize IE in
        162 * the proper Protected Mode zone. Setting this option may cause browser
        163 * instability or flaky and unresponsive code. Only "best effort" support is
        164 * provided when using this option.
        165 *
        166 * @param {string} url The initial browser URL.
        167 * @return {!Options} A self reference.
        168 */
        169Options.prototype.initialBrowserUrl = function(url) {
        170 this.options_[Key.INITIAL_BROWSER_URL] = url;
        171 return this;
        172};
        173
        174
        175/**
        176 * Configures whether to enable persistent mouse hovering (true by default).
        177 * Persistent hovering is achieved by continuously firing mouse over events at
        178 * the last location the mouse cursor has been moved to.
        179 *
        180 * @param {boolean} enable Whether to enable persistent hovering.
        181 * @return {!Options} A self reference.
        182 */
        183Options.prototype.enablePersistentHover = function(enable) {
        184 this.options_[Key.ENABLE_PERSISTENT_HOVER] = !!enable;
        185 return this;
        186};
        187
        188
        189/**
        190 * Configures whether the driver should attempt to remove obsolete
        191 * {@linkplain webdriver.WebElement WebElements} from its internal cache on
        192 * page navigation (true by default). Disabling this option will cause the
        193 * driver to run with a larger memory footprint.
        194 *
        195 * @param {boolean} enable Whether to enable element reference cleanup.
        196 * @return {!Options} A self reference.
        197 */
        198Options.prototype.enableElementCacheCleanup = function(enable) {
        199 this.options_[Key.ENABLE_ELEMENT_CACHE_CLEANUP] = !!enable;
        200 return this;
        201};
        202
        203
        204/**
        205 * Configures whether to require the IE window to have input focus before
        206 * performing any user interactions (i.e. mouse or keyboard events). This
        207 * option is disabled by default, but delivers much more accurate interaction
        208 * events when enabled.
        209 *
        210 * @param {boolean} require Whether to require window focus.
        211 * @return {!Options} A self reference.
        212 */
        213Options.prototype.requireWindowFocus = function(require) {
        214 this.options_[Key.REQUIRE_WINDOW_FOCUS] = !!require;
        215 return this;
        216};
        217
        218
        219/**
        220 * Configures the timeout, in milliseconds, that the driver will attempt to
        221 * located and attach to a newly opened instance of Internet Explorer. The
        222 * default is zero, which indicates waiting indefinitely.
        223 *
        224 * @param {number} timeout How long to wait for IE.
        225 * @return {!Options} A self reference.
        226 */
        227Options.prototype.browserAttachTimeout = function(timeout) {
        228 this.options_[Key.BROWSER_ATTACH_TIMEOUT] = Math.max(timeout, 0);
        229 return this;
        230};
        231
        232
        233/**
        234 * Configures whether to launch Internet Explorer using the CreateProcess API.
        235 * If this option is not specified, IE is launched using IELaunchURL, if
        236 * available. For IE 8 and above, this option requires the TabProcGrowth
        237 * registry value to be set to 0.
        238 *
        239 * @param {boolean} force Whether to use the CreateProcess API.
        240 * @return {!Options} A self reference.
        241 */
        242Options.prototype.forceCreateProcessApi = function(force) {
        243 this.options_[Key.FORCE_CREATE_PROCESS] = !!force;
        244 return this;
        245};
        246
        247
        248/**
        249 * Specifies command-line switches to use when launching Internet Explorer.
        250 * This is only valid when used with {@link #forceCreateProcessApi}.
        251 *
        252 * @param {...(string|!Array.<string>)} var_args The arguments to add.
        253 * @return {!Options} A self reference.
        254 */
        255Options.prototype.addArguments = function(var_args) {
        256 var args = this.options_[Key.BROWSER_COMMAND_LINE_SWITCHES] || [];
        257 args = args.concat.apply(args, arguments);
        258 this.options_[Key.BROWSER_COMMAND_LINE_SWITCHES] = args;
        259 return this;
        260};
        261
        262
        263/**
        264 * Configures whether proxies should be configured on a per-process basis. If
        265 * not set, setting a {@linkplain #setProxy proxy} will configure the system
        266 * proxy. The default behavior is to use the system proxy.
        267 *
        268 * @param {boolean} enable Whether to enable per-process proxy settings.
        269 * @return {!Options} A self reference.
        270 */
        271Options.prototype.usePerProcessProxy = function(enable) {
        272 this.options_[Key.USE_PER_PROCESS_PROXY] = !!enable;
        273 return this;
        274};
        275
        276
        277/**
        278 * Configures whether to clear the cache, cookies, history, and saved form data
        279 * before starting the browser. _Using this capability will clear session data
        280 * for all running instances of Internet Explorer, including those started
        281 * manually._
        282 *
        283 * @param {boolean} cleanSession Whether to clear all session data on startup.
        284 * @return {!Options} A self reference.
        285 */
        286Options.prototype.ensureCleanSession = function(cleanSession) {
        287 this.options_[Key.ENSURE_CLEAN_SESSION] = !!cleanSession;
        288 return this;
        289};
        290
        291
        292/**
        293 * Sets the path to the log file the driver should log to.
        294 * @param {string} path The log file path.
        295 * @return {!Options} A self reference.
        296 */
        297Options.prototype.setLogFile = function(file) {
        298 this.options_[Key.LOG_FILE] = file;
        299 return this;
        300};
        301
        302
        303/**
        304 * Sets the IEDriverServer's logging {@linkplain Level level}.
        305 * @param {Level} level The logging level.
        306 * @return {!Options} A self reference.
        307 */
        308Options.prototype.setLogLevel = function(level) {
        309 this.options_[Key.LOG_LEVEL] = level;
        310 return this;
        311};
        312
        313
        314/**
        315 * Sets the IP address of the driver's host adapter.
        316 * @param {string} host The IP address to use.
        317 * @return {!Options} A self reference.
        318 */
        319Options.prototype.setHost = function(host) {
        320 this.options_[Key.HOST] = host;
        321 return this;
        322};
        323
        324
        325/**
        326 * Sets the path of the temporary data directory to use.
        327 * @param {string} path The log file path.
        328 * @return {!Options} A self reference.
        329 */
        330Options.prototype.setExtractPath = function(path) {
        331 this.options_[Key.EXTRACT_PATH] = path;
        332 return this;
        333};
        334
        335
        336/**
        337 * Sets whether the driver should start in silent mode.
        338 * @param {boolean} silent Whether to run in silent mode.
        339 * @return {!Options} A self reference.
        340 */
        341Options.prototype.silent = function(silent) {
        342 this.options_[Key.SILENT] = silent;
        343 return this;
        344};
        345
        346
        347/**
        348 * Sets the proxy settings for the new session.
        349 * @param {webdriver.ProxyConfig} proxy The proxy configuration to use.
        350 * @return {!Options} A self reference.
        351 */
        352Options.prototype.setProxy = function(proxy) {
        353 this.proxy_ = proxy;
        354 return this;
        355};
        356
        357
        358/**
        359 * Converts this options instance to a {@link webdriver.Capabilities} object.
        360 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge
        361 * these options into, if any.
        362 * @return {!webdriver.Capabilities} The capabilities.
        363 */
        364Options.prototype.toCapabilities = function(opt_capabilities) {
        365 var capabilities = opt_capabilities || webdriver.Capabilities.ie();
        366 if (this.proxy_) {
        367 capabilities.set(webdriver.Capability.PROXY, this.proxy_);
        368 }
        369 Object.keys(this.options_).forEach(function(key) {
        370 capabilities.set(key, this.options_[key]);
        371 }, this);
        372 return capabilities;
        373};
        374
        375
        376function createServiceFromCapabilities(capabilities) {
        377 if (process.platform !== 'win32') {
        378 throw Error(
        379 'The IEDriver may only be used on Windows, but you appear to be on ' +
        380 process.platform + '. Did you mean to run against a remote ' +
        381 'WebDriver server?');
        382 }
        383
        384 var exe = io.findInPath(IEDRIVER_EXE, true);
        385 if (!fs.existsSync(exe)) {
        386 throw Error('File does not exist: ' + exe);
        387 }
        388
        389 var args = [];
        390 if (capabilities.has(Key.HOST)) {
        391 args.push('--host=' + capabilities.get(Key.HOST));
        392 }
        393 if (capabilities.has(Key.LOG_FILE)) {
        394 args.push('--log-file=' + capabilities.get(Key.LOG_FILE));
        395 }
        396 if (capabilities.has(Key.LOG_LEVEL)) {
        397 args.push('--log-level=' + capabilities.get(Key.LOG_LEVEL));
        398 }
        399 if (capabilities.has(Key.EXTRACT_PATH)) {
        400 args.push('--extract-path=' + capabilities.get(Key.EXTRACT_PATH));
        401 }
        402 if (capabilities.get(Key.SILENT)) {
        403 args.push('--silent');
        404 }
        405
        406 var port = portprober.findFreePort();
        407 return new remote.DriverService(exe, {
        408 loopback: true,
        409 port: port,
        410 args: port.then(function(port) {
        411 return args.concat('--port=' + port);
        412 }),
        413 stdio: 'ignore'
        414 });
        415}
        416
        417
        418/**
        419 * A WebDriver client for Microsoft's Internet Explorer.
        420 *
        421 * @param {(webdriver.Capabilities|Options)=} opt_config The configuration
        422 * options.
        423 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
        424 * {@code null} to use the currently active flow.
        425 * @constructor
        426 * @extends {webdriver.WebDriver}
        427 */
        428var Driver = function(opt_config, opt_flow) {
        429 var capabilities = opt_config instanceof Options ?
        430 opt_config.toCapabilities() :
        431 (opt_config || webdriver.Capabilities.ie());
        432
        433 var service = createServiceFromCapabilities(capabilities);
        434 var executor = executors.createExecutor(service.start());
        435 var driver = webdriver.WebDriver.createSession(
        436 executor, capabilities, opt_flow);
        437
        438 webdriver.WebDriver.call(
        439 this, driver.getSession(), executor, driver.controlFlow());
        440
        441 var boundQuit = this.quit.bind(this);
        442
        443 /** @override */
        444 this.quit = function() {
        445 return boundQuit().thenFinally(service.kill.bind(service));
        446 };
        447};
        448util.inherits(Driver, webdriver.WebDriver);
        449
        450
        451/**
        452 * This function is a no-op as file detectors are not supported by this
        453 * implementation.
        454 * @override
        455 */
        456Driver.prototype.setFileDetector = function() {
        457};
        458
        459
        460// PUBLIC API
        461
        462
        463exports.Driver = Driver;
        464exports.Options = Options;
        465exports.Level = Level;
        \ No newline at end of file diff --git a/docs/source/index.js.src.html b/docs/source/index.js.src.html index b4cc5c6..9c22c9d 100644 --- a/docs/source/index.js.src.html +++ b/docs/source/index.js.src.html @@ -1 +1 @@ -index.js

        index.js

        1// Copyright 2012 Selenium committers
        2// Copyright 2012 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview The main user facing module. Exports WebDriver's primary
        18 * public API and provides convenience assessors to certain sub-modules.
        19 */
        20
        21var base = require('./_base');
        22var builder = require('./builder');
        23var error = require('./error');
        24
        25
        26// NOTE: the remainder of this file is nasty and verbose, but the annotations
        27// are necessary to guide the Closure Compiler's type analysis. Without them,
        28// we would not be able to extract any meaningful API documentation.
        29
        30
        31/** @type {function(new: webdriver.ActionSequence)} */
        32exports.ActionSequence = base.require('webdriver.ActionSequence');
        33
        34
        35/** @type {function(new: builder.Builder)} */
        36exports.Builder = builder.Builder;
        37
        38
        39/** @type {webdriver.By.} */
        40exports.By = base.require('webdriver.By');
        41
        42
        43/** @type {function(new: webdriver.Capabilities)} */
        44exports.Capabilities = base.require('webdriver.Capabilities');
        45
        46
        47/** @type {function(new: webdriver.Command)} */
        48exports.Command = base.require('webdriver.Command');
        49
        50
        51/** @type {function(new: webdriver.EventEmitter)} */
        52exports.EventEmitter = base.require('webdriver.EventEmitter');
        53
        54
        55/** @type {function(new: webdriver.Session)} */
        56exports.Session = base.require('webdriver.Session');
        57
        58
        59/** @type {function(new: webdriver.WebDriver)} */
        60exports.WebDriver = base.require('webdriver.WebDriver');
        61
        62
        63/** @type {function(new: webdriver.WebElement)} */
        64exports.WebElement = base.require('webdriver.WebElement');
        65
        66
        67// Export the remainder of our API through getters to keep things cleaner
        68// when this module is used in a REPL environment.
        69
        70
        71/** @type {webdriver.Browser.} */
        72(exports.__defineGetter__('Browser', function() {
        73 return base.require('webdriver.Browser');
        74}));
        75
        76
        77/** @type {webdriver.Button.} */
        78(exports.__defineGetter__('Button', function() {
        79 return base.require('webdriver.Button');
        80}));
        81
        82
        83/** @type {webdriver.Capability.} */
        84(exports.__defineGetter__('Capability', function() {
        85 return base.require('webdriver.Capability');
        86}));
        87
        88
        89/** @type {webdriver.CommandName.} */
        90(exports.__defineGetter__('CommandName', function() {
        91 return base.require('webdriver.CommandName');
        92}));
        93
        94
        95/** @type {webdriver.Key.} */
        96(exports.__defineGetter__('Key', function() {
        97 return base.require('webdriver.Key');
        98}));
        99
        100
        101/** @type {error.} */
        102(exports.__defineGetter__('error', function() {
        103 return error;
        104}));
        105
        106
        107/** @type {error.} */
        108(exports.__defineGetter__('error', function() {
        109 return error;
        110}));
        111
        112
        113/** @type {webdriver.logging.} */
        114(exports.__defineGetter__('logging', function() {
        115 return base.exportPublicApi('webdriver.logging');
        116}));
        117
        118
        119/** @type {webdriver.promise.} */
        120(exports.__defineGetter__('promise', function() {
        121 return base.exportPublicApi('webdriver.promise');
        122}));
        123
        124
        125/** @type {webdriver.stacktrace.} */
        126(exports.__defineGetter__('stacktrace', function() {
        127 return base.exportPublicApi('webdriver.stacktrace');
        128}));
        \ No newline at end of file +index.js

        index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview The main user facing module. Exports WebDriver's primary
        20 * public API and provides convenience assessors to certain sub-modules.
        21 */
        22
        23var base = require('./_base');
        24var builder = require('./builder');
        25var error = require('./error');
        26
        27
        28// NOTE: the remainder of this file is nasty and verbose, but the annotations
        29// are necessary to guide the Closure Compiler's type analysis. Without them,
        30// we would not be able to extract any meaningful API documentation.
        31
        32
        33/** @type {function(new: webdriver.ActionSequence)} */
        34exports.ActionSequence = base.require('webdriver.ActionSequence');
        35
        36
        37/** @type {function(new: builder.Builder)} */
        38exports.Builder = builder.Builder;
        39
        40
        41/** @type {webdriver.By.} */
        42exports.By = base.require('webdriver.By');
        43
        44
        45/** @type {function(new: webdriver.Capabilities)} */
        46exports.Capabilities = base.require('webdriver.Capabilities');
        47
        48
        49/** @type {function(new: webdriver.Command)} */
        50exports.Command = base.require('webdriver.Command');
        51
        52
        53/** @type {function(new: webdriver.EventEmitter)} */
        54exports.EventEmitter = base.require('webdriver.EventEmitter');
        55
        56
        57/** @type {function(new: webdriver.FileDetector)} */
        58exports.FileDetector = base.require('webdriver.FileDetector');
        59
        60
        61/** @type {function(new: webdriver.Serializable)} */
        62exports.Serializable = base.require('webdriver.Serializable');
        63
        64
        65/** @type {function(new: webdriver.Session)} */
        66exports.Session = base.require('webdriver.Session');
        67
        68
        69/** @type {function(new: webdriver.WebDriver)} */
        70exports.WebDriver = base.require('webdriver.WebDriver');
        71
        72
        73/** @type {function(new: webdriver.WebElement)} */
        74exports.WebElement = base.require('webdriver.WebElement');
        75
        76
        77/** @type {function(new: webdriver.WebElementPromise)} */
        78exports.WebElementPromise = base.require('webdriver.WebElementPromise');
        79
        80
        81// Export the remainder of our API through getters to keep things cleaner
        82// when this module is used in a REPL environment.
        83
        84
        85/** @type {webdriver.Browser.} */
        86(exports.__defineGetter__('Browser', function() {
        87 return base.require('webdriver.Browser');
        88}));
        89
        90
        91/** @type {webdriver.Button.} */
        92(exports.__defineGetter__('Button', function() {
        93 return base.require('webdriver.Button');
        94}));
        95
        96
        97/** @type {webdriver.Capability.} */
        98(exports.__defineGetter__('Capability', function() {
        99 return base.require('webdriver.Capability');
        100}));
        101
        102
        103/** @type {webdriver.CommandName.} */
        104(exports.__defineGetter__('CommandName', function() {
        105 return base.require('webdriver.CommandName');
        106}));
        107
        108
        109/** @type {webdriver.Key.} */
        110(exports.__defineGetter__('Key', function() {
        111 return base.require('webdriver.Key');
        112}));
        113
        114
        115/** @type {error.} */
        116(exports.__defineGetter__('error', function() {
        117 return error;
        118}));
        119
        120
        121/** @type {error.} */
        122(exports.__defineGetter__('error', function() {
        123 return error;
        124}));
        125
        126
        127/** @type {webdriver.logging.} */
        128(exports.__defineGetter__('logging', function() {
        129 return base.exportPublicApi('webdriver.logging');
        130}));
        131
        132
        133/** @type {webdriver.promise.} */
        134(exports.__defineGetter__('promise', function() {
        135 return base.exportPublicApi('webdriver.promise');
        136}));
        137
        138
        139/** @type {webdriver.stacktrace.} */
        140(exports.__defineGetter__('stacktrace', function() {
        141 return base.exportPublicApi('webdriver.stacktrace');
        142}));
        143
        144
        145/** @type {webdriver.until.} */
        146(exports.__defineGetter__('until', function() {
        147 return base.exportPublicApi('webdriver.until');
        148}));
        \ No newline at end of file diff --git a/docs/source/io/exec.js.src.html b/docs/source/io/exec.js.src.html new file mode 100644 index 0000000..9063c7e --- /dev/null +++ b/docs/source/io/exec.js.src.html @@ -0,0 +1 @@ +exec.js

        io/exec.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var childProcess = require('child_process');
        21
        22var promise = require('..').promise;
        23
        24
        25/**
        26 * A hash with configuration options for an executed command.
        27 *
        28 * - `args` - Command line arguments.
        29 * - `env` - Command environment; will inherit from the current process if
        30 * missing.
        31 * - `stdio` - IO configuration for the spawned server process. For more
        32 * information, refer to the documentation of `child_process.spawn`.
        33 *
        34 * @typedef {{
        35 * args: (!Array.<string>|undefined),
        36 * env: (!Object.<string, string>|undefined),
        37 * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
        38 * }}
        39 */
        40var Options;
        41
        42
        43/**
        44 * Describes a command's termination conditions.
        45 * @param {?number} code The exit code, or {@code null} if the command did not
        46 * exit normally.
        47 * @param {?string} signal The signal used to kill the command, or
        48 * {@code null}.
        49 * @constructor
        50 */
        51var Result = function(code, signal) {
        52 /** @type {?number} */
        53 this.code = code;
        54
        55 /** @type {?string} */
        56 this.signal = signal;
        57};
        58
        59
        60/** @override */
        61Result.prototype.toString = function() {
        62 return 'Result(code=' + this.code + ', signal=' + this.signal + ')';
        63};
        64
        65
        66
        67/**
        68 * Represents a command running in a sub-process.
        69 * @param {!promise.Promise.<!Result>} result The command result.
        70 * @constructor
        71 */
        72var Command = function(result, onKill) {
        73 /** @return {boolean} Whether this command is still running. */
        74 this.isRunning = function() {
        75 return result.isPending();
        76 };
        77
        78 /**
        79 * @return {!promise.Promise.<!Result>} A promise for the result of this
        80 * command.
        81 */
        82 this.result = function() {
        83 return result;
        84 };
        85
        86 /**
        87 * Sends a signal to the underlying process.
        88 * @param {string=} opt_signal The signal to send; defaults to
        89 * {@code SIGTERM}.
        90 */
        91 this.kill = function(opt_signal) {
        92 onKill(opt_signal || 'SIGTERM');
        93 };
        94};
        95
        96
        97// PUBLIC API
        98
        99
        100/**
        101 * Spawns a child process. The returned {@link Command} may be used to wait
        102 * for the process result or to send signals to the process.
        103 *
        104 * @param {string} command The executable to spawn.
        105 * @param {Options=} opt_options The command options.
        106 * @return {!Command} The launched command.
        107 */
        108module.exports = function(command, opt_options) {
        109 var options = opt_options || {};
        110
        111 var proc = childProcess.spawn(command, options.args || [], {
        112 env: options.env || process.env,
        113 stdio: options.stdio || 'ignore'
        114 }).once('exit', onExit);
        115
        116 // This process should not wait on the spawned child, however, we do
        117 // want to ensure the child is killed when this process exits.
        118 proc.unref();
        119 process.once('exit', killCommand);
        120
        121 var result = promise.defer();
        122 var cmd = new Command(result.promise, function(signal) {
        123 if (!result.isPending() || !proc) {
        124 return; // No longer running.
        125 }
        126 proc.kill(signal);
        127 });
        128 return cmd;
        129
        130 function onExit(code, signal) {
        131 proc = null;
        132 process.removeListener('exit', killCommand);
        133 result.fulfill(new Result(code, signal));
        134 }
        135
        136 function killCommand() {
        137 process.removeListener('exit', killCommand);
        138 proc && proc.kill('SIGTERM');
        139 }
        140};
        \ No newline at end of file diff --git a/docs/source/io/index.js.src.html b/docs/source/io/index.js.src.html index 0a18b2b..c2a0f4c 100644 --- a/docs/source/io/index.js.src.html +++ b/docs/source/io/index.js.src.html @@ -1 +1 @@ -index.js

        io/index.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16var fs = require('fs'),
        17 path = require('path');
        18
        19
        20var PATH_SEPARATOR = process.platform === 'win32' ? ';' : ':';
        21
        22
        23// PUBLIC API
        24
        25
        26/**
        27 * Searches the {@code PATH} environment variable for the given file.
        28 * @param {string} file The file to locate on the PATH.
        29 * @param {boolean=} opt_checkCwd Whether to always start with the search with
        30 * the current working directory, regardless of whether it is explicitly
        31 * listed on the PATH.
        32 * @return {?string} Path to the located file, or {@code null} if it could
        33 * not be found.
        34 */
        35exports.findInPath = function(file, opt_checkCwd) {
        36 if (opt_checkCwd) {
        37 var tmp = path.join(process.cwd(), file);
        38 if (fs.existsSync(tmp)) {
        39 return tmp;
        40 }
        41 }
        42
        43 var dirs = process.env['PATH'].split(PATH_SEPARATOR);
        44 var found = null;
        45 dirs.forEach(function(dir) {
        46 var tmp = path.join(dir, file);
        47 if (!found && fs.existsSync(tmp)) {
        48 found = tmp;
        49 }
        50 });
        51 return found;
        52};
        \ No newline at end of file +index.js

        io/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18var fs = require('fs'),
        19 path = require('path'),
        20 rimraf = require('rimraf'),
        21 tmp = require('tmp');
        22
        23var promise = require('..').promise;
        24
        25
        26
        27// PUBLIC API
        28
        29
        30
        31/**
        32 * Recursively removes a directory and all of its contents. This is equivalent
        33 * to {@code rm -rf} on a POSIX system.
        34 * @param {string} path Path to the directory to remove.
        35 * @return {!promise.Promise} A promise to be resolved when the operation has
        36 * completed.
        37 */
        38exports.rmDir = function(path) {
        39 return new promise.Promise(function(fulfill, reject) {
        40 var numAttempts = 0;
        41 attemptRm();
        42 function attemptRm() {
        43 numAttempts += 1;
        44 rimraf(path, function(err) {
        45 if (err) {
        46 if (err.code === 'ENOTEMPTY' && numAttempts < 2) {
        47 attemptRm();
        48 return;
        49 }
        50 reject(err);
        51 } else {
        52 fulfill();
        53 }
        54 });
        55 }
        56 });
        57};
        58
        59
        60/**
        61 * Copies one file to another.
        62 * @param {string} src The source file.
        63 * @param {string} dst The destination file.
        64 * @return {!promise.Promise.<string>} A promise for the copied file's path.
        65 */
        66exports.copy = function(src, dst) {
        67 var copied = promise.defer();
        68
        69 var rs = fs.createReadStream(src);
        70 rs.on('error', copied.reject);
        71 rs.on('end', function() {
        72 copied.fulfill(dst);
        73 });
        74
        75 var ws = fs.createWriteStream(dst);
        76 ws.on('error', copied.reject);
        77
        78 rs.pipe(ws);
        79
        80 return copied.promise;
        81};
        82
        83
        84/**
        85 * Recursively copies the contents of one directory to another.
        86 * @param {string} src The source directory to copy.
        87 * @param {string} dst The directory to copy into.
        88 * @param {(RegEx|function(string): boolean)=} opt_exclude An exclusion filter
        89 * as either a regex or predicate function. All files matching this filter
        90 * will not be copied.
        91 * @return {!promise.Promise.<string>} A promise for the destination
        92 * directory's path once all files have been copied.
        93 */
        94exports.copyDir = function(src, dst, opt_exclude) {
        95 var predicate = opt_exclude;
        96 if (opt_exclude && typeof opt_exclude !== 'function') {
        97 predicate = function(p) {
        98 return !opt_exclude.test(p);
        99 };
        100 }
        101
        102 // TODO(jleyba): Make this function completely async.
        103 if (!fs.existsSync(dst)) {
        104 fs.mkdirSync(dst);
        105 }
        106
        107 var files = fs.readdirSync(src);
        108 files = files.map(function(file) {
        109 return path.join(src, file);
        110 });
        111
        112 if (predicate) {
        113 files = files.filter(predicate);
        114 }
        115
        116 var results = [];
        117 files.forEach(function(file) {
        118 var stats = fs.statSync(file);
        119 var target = path.join(dst, path.basename(file));
        120
        121 if (stats.isDirectory()) {
        122 if (!fs.existsSync(target)) {
        123 fs.mkdirSync(target, stats.mode);
        124 }
        125 results.push(exports.copyDir(file, target, predicate));
        126 } else {
        127 results.push(exports.copy(file, target));
        128 }
        129 });
        130
        131 return promise.all(results).then(function() {
        132 return dst;
        133 });
        134};
        135
        136
        137/**
        138 * Tests if a file path exists.
        139 * @param {string} path The path to test.
        140 * @return {!promise.Promise.<boolean>} A promise for whether the file exists.
        141 */
        142exports.exists = function(path) {
        143 var result = promise.defer();
        144 fs.exists(path, result.fulfill);
        145 return result.promise;
        146};
        147
        148
        149/**
        150 * Deletes a name from the filesystem and possibly the file it refers to. Has
        151 * no effect if the file does not exist.
        152 * @param {string} path The path to remove.
        153 * @return {!promise.Promise} A promise for when the file has been removed.
        154 */
        155exports.unlink = function(path) {
        156 return new promise.Promise(function(fulfill, reject) {
        157 fs.exists(path, function(exists) {
        158 if (exists) {
        159 fs.unlink(path, function(err) {
        160 err && reject(err) || fulfill();
        161 });
        162 } else {
        163 fulfill();
        164 }
        165 });
        166 });
        167};
        168
        169
        170/**
        171 * @return {!promise.Promise.<string>} A promise for the path to a temporary
        172 * directory.
        173 * @see https://www.npmjs.org/package/tmp
        174 */
        175exports.tmpDir = function() {
        176 return promise.checkedNodeCall(tmp.dir);
        177};
        178
        179
        180/**
        181 * @param {{postfix: string}=} opt_options Temporary file options.
        182 * @return {!promise.Promise.<string>} A promise for the path to a temporary
        183 * file.
        184 * @see https://www.npmjs.org/package/tmp
        185 */
        186exports.tmpFile = function(opt_options) {
        187 // |tmp.file| checks arguments length to detect options rather than doing a
        188 // truthy check, so we must only pass options if there are some to pass.
        189 return opt_options ?
        190 promise.checkedNodeCall(tmp.file, opt_options) :
        191 promise.checkedNodeCall(tmp.file);
        192};
        193
        194
        195/**
        196 * Searches the {@code PATH} environment variable for the given file.
        197 * @param {string} file The file to locate on the PATH.
        198 * @param {boolean=} opt_checkCwd Whether to always start with the search with
        199 * the current working directory, regardless of whether it is explicitly
        200 * listed on the PATH.
        201 * @return {?string} Path to the located file, or {@code null} if it could
        202 * not be found.
        203 */
        204exports.findInPath = function(file, opt_checkCwd) {
        205 if (opt_checkCwd) {
        206 var tmp = path.join(process.cwd(), file);
        207 if (fs.existsSync(tmp)) {
        208 return tmp;
        209 }
        210 }
        211
        212 var dirs = process.env['PATH'].split(path.delimiter);
        213 var found = null;
        214 dirs.forEach(function(dir) {
        215 var tmp = path.join(dir, file);
        216 if (!found && fs.existsSync(tmp)) {
        217 found = tmp;
        218 }
        219 });
        220 return found;
        221};
        \ No newline at end of file diff --git a/docs/source/lib/atoms/error.js.src.html b/docs/source/lib/atoms/error.js.src.html index b9696c3..201fcac 100644 --- a/docs/source/lib/atoms/error.js.src.html +++ b/docs/source/lib/atoms/error.js.src.html @@ -1 +1 @@ -error.js

        lib/atoms/error.js

        1// Copyright 2010 WebDriver committers
        2// Copyright 2010 Google Inc.
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview Utilities for working with errors as defined by WebDriver's
        18 * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol.
        19 */
        20
        21goog.provide('bot.Error');
        22goog.provide('bot.ErrorCode');
        23
        24
        25/**
        26 * Error codes from the WebDriver wire protocol:
        27 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
        28 *
        29 * @enum {number}
        30 */
        31bot.ErrorCode = {
        32 SUCCESS: 0, // Included for completeness
        33
        34 NO_SUCH_ELEMENT: 7,
        35 NO_SUCH_FRAME: 8,
        36 UNKNOWN_COMMAND: 9,
        37 UNSUPPORTED_OPERATION: 9, // Alias.
        38 STALE_ELEMENT_REFERENCE: 10,
        39 ELEMENT_NOT_VISIBLE: 11,
        40 INVALID_ELEMENT_STATE: 12,
        41 UNKNOWN_ERROR: 13,
        42 ELEMENT_NOT_SELECTABLE: 15,
        43 JAVASCRIPT_ERROR: 17,
        44 XPATH_LOOKUP_ERROR: 19,
        45 TIMEOUT: 21,
        46 NO_SUCH_WINDOW: 23,
        47 INVALID_COOKIE_DOMAIN: 24,
        48 UNABLE_TO_SET_COOKIE: 25,
        49 MODAL_DIALOG_OPENED: 26,
        50 NO_MODAL_DIALOG_OPEN: 27,
        51 SCRIPT_TIMEOUT: 28,
        52 INVALID_ELEMENT_COORDINATES: 29,
        53 IME_NOT_AVAILABLE: 30,
        54 IME_ENGINE_ACTIVATION_FAILED: 31,
        55 INVALID_SELECTOR_ERROR: 32,
        56 SESSION_NOT_CREATED: 33,
        57 MOVE_TARGET_OUT_OF_BOUNDS: 34,
        58 SQL_DATABASE_ERROR: 35,
        59 INVALID_XPATH_SELECTOR: 51,
        60 INVALID_XPATH_SELECTOR_RETURN_TYPE: 52,
        61 // The following error codes are derived straight from HTTP return codes.
        62 METHOD_NOT_ALLOWED: 405
        63};
        64
        65
        66
        67/**
        68 * Error extension that includes error status codes from the WebDriver wire
        69 * protocol:
        70 * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes
        71 *
        72 * @param {!bot.ErrorCode} code The error's status code.
        73 * @param {string=} opt_message Optional error message.
        74 * @constructor
        75 * @extends {Error}
        76 */
        77bot.Error = function(code, opt_message) {
        78
        79 /**
        80 * This error's status code.
        81 * @type {!bot.ErrorCode}
        82 */
        83 this.code = code;
        84
        85 /** @type {string} */
        86 this.state =
        87 bot.Error.CODE_TO_STATE_[code] || bot.Error.State.UNKNOWN_ERROR;
        88
        89 /** @override */
        90 this.message = opt_message || '';
        91
        92 var name = this.state.replace(/((?:^|\s+)[a-z])/g, function(str) {
        93 // IE<9 does not support String#trim(). Also, IE does not include 0xa0
        94 // (the non-breaking-space) in the \s character class, so we have to
        95 // explicitly include it.
        96 return str.toUpperCase().replace(/^[\s\xa0]+/g, '');
        97 });
        98
        99 var l = name.length - 'Error'.length;
        100 if (l < 0 || name.indexOf('Error', l) != l) {
        101 name += 'Error';
        102 }
        103
        104 /** @override */
        105 this.name = name;
        106
        107 // Generate a stacktrace for our custom error; ensure the error has our
        108 // custom name and message so the stack prints correctly in all browsers.
        109 var template = new Error(this.message);
        110 template.name = this.name;
        111
        112 /** @override */
        113 this.stack = template.stack || '';
        114};
        115goog.inherits(bot.Error, Error);
        116
        117
        118/**
        119 * Status strings enumerated in the W3C WebDriver working draft.
        120 * @enum {string}
        121 * @see http://www.w3.org/TR/webdriver/#status-codes
        122 */
        123bot.Error.State = {
        124 ELEMENT_NOT_SELECTABLE: 'element not selectable',
        125 ELEMENT_NOT_VISIBLE: 'element not visible',
        126 IME_ENGINE_ACTIVATION_FAILED: 'ime engine activation failed',
        127 IME_NOT_AVAILABLE: 'ime not available',
        128 INVALID_COOKIE_DOMAIN: 'invalid cookie domain',
        129 INVALID_ELEMENT_COORDINATES: 'invalid element coordinates',
        130 INVALID_ELEMENT_STATE: 'invalid element state',
        131 INVALID_SELECTOR: 'invalid selector',
        132 JAVASCRIPT_ERROR: 'javascript error',
        133 MOVE_TARGET_OUT_OF_BOUNDS: 'move target out of bounds',
        134 NO_SUCH_ALERT: 'no such alert',
        135 NO_SUCH_DOM: 'no such dom',
        136 NO_SUCH_ELEMENT: 'no such element',
        137 NO_SUCH_FRAME: 'no such frame',
        138 NO_SUCH_WINDOW: 'no such window',
        139 SCRIPT_TIMEOUT: 'script timeout',
        140 SESSION_NOT_CREATED: 'session not created',
        141 STALE_ELEMENT_REFERENCE: 'stale element reference',
        142 SUCCESS: 'success',
        143 TIMEOUT: 'timeout',
        144 UNABLE_TO_SET_COOKIE: 'unable to set cookie',
        145 UNEXPECTED_ALERT_OPEN: 'unexpected alert open',
        146 UNKNOWN_COMMAND: 'unknown command',
        147 UNKNOWN_ERROR: 'unknown error',
        148 UNSUPPORTED_OPERATION: 'unsupported operation'
        149};
        150
        151
        152/**
        153 * A map of error codes to state string.
        154 * @private {!Object.<bot.ErrorCode, bot.Error.State>}
        155 */
        156bot.Error.CODE_TO_STATE_ = {};
        157goog.scope(function() {
        158 var map = bot.Error.CODE_TO_STATE_;
        159 var code = bot.ErrorCode;
        160 var state = bot.Error.State;
        161
        162 map[code.ELEMENT_NOT_SELECTABLE] = state.ELEMENT_NOT_SELECTABLE;
        163 map[code.ELEMENT_NOT_VISIBLE] = state.ELEMENT_NOT_VISIBLE;
        164 map[code.IME_ENGINE_ACTIVATION_FAILED] = state.IME_ENGINE_ACTIVATION_FAILED;
        165 map[code.IME_NOT_AVAILABLE] = state.IME_NOT_AVAILABLE;
        166 map[code.INVALID_COOKIE_DOMAIN] = state.INVALID_COOKIE_DOMAIN;
        167 map[code.INVALID_ELEMENT_COORDINATES] = state.INVALID_ELEMENT_COORDINATES;
        168 map[code.INVALID_ELEMENT_STATE] = state.INVALID_ELEMENT_STATE;
        169 map[code.INVALID_SELECTOR_ERROR] = state.INVALID_SELECTOR;
        170 map[code.INVALID_XPATH_SELECTOR] = state.INVALID_SELECTOR;
        171 map[code.INVALID_XPATH_SELECTOR_RETURN_TYPE] = state.INVALID_SELECTOR;
        172 map[code.JAVASCRIPT_ERROR] = state.JAVASCRIPT_ERROR;
        173 map[code.METHOD_NOT_ALLOWED] = state.UNSUPPORTED_OPERATION;
        174 map[code.MOVE_TARGET_OUT_OF_BOUNDS] = state.MOVE_TARGET_OUT_OF_BOUNDS;
        175 map[code.NO_MODAL_DIALOG_OPEN] = state.NO_SUCH_ALERT;
        176 map[code.NO_SUCH_ELEMENT] = state.NO_SUCH_ELEMENT;
        177 map[code.NO_SUCH_FRAME] = state.NO_SUCH_FRAME;
        178 map[code.NO_SUCH_WINDOW] = state.NO_SUCH_WINDOW;
        179 map[code.SCRIPT_TIMEOUT] = state.SCRIPT_TIMEOUT;
        180 map[code.SESSION_NOT_CREATED] = state.SESSION_NOT_CREATED;
        181 map[code.STALE_ELEMENT_REFERENCE] = state.STALE_ELEMENT_REFERENCE;
        182 map[code.SUCCESS] = state.SUCCESS;
        183 map[code.TIMEOUT] = state.TIMEOUT;
        184 map[code.UNABLE_TO_SET_COOKIE] = state.UNABLE_TO_SET_COOKIE;
        185 map[code.MODAL_DIALOG_OPENED] = state.UNEXPECTED_ALERT_OPEN;
        186 map[code.UNKNOWN_ERROR] = state.UNKNOWN_ERROR;
        187 map[code.UNSUPPORTED_OPERATION] = state.UNKNOWN_COMMAND;
        188}); // goog.scope
        189
        190
        191/**
        192 * Flag used for duck-typing when this code is embedded in a Firefox extension.
        193 * This is required since an Error thrown in one component and then reported
        194 * to another will fail instanceof checks in the second component.
        195 * @type {boolean}
        196 */
        197bot.Error.prototype.isAutomationError = true;
        198
        199
        200if (goog.DEBUG) {
        201 /** @return {string} The string representation of this error. */
        202 bot.Error.prototype.toString = function() {
        203 return this.name + ': ' + this.message;
        204 };
        205}
        \ No newline at end of file +error.js

        lib/atoms/error.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Utilities for working with errors as defined by WebDriver's
        20 * wire protocol: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
        21 */
        22
        23goog.provide('bot.Error');
        24goog.provide('bot.ErrorCode');
        25
        26
        27/**
        28 * Error codes from the Selenium WebDriver protocol:
        29 * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes
        30 *
        31 * @enum {number}
        32 */
        33bot.ErrorCode = {
        34 SUCCESS: 0, // Included for completeness
        35
        36 NO_SUCH_ELEMENT: 7,
        37 NO_SUCH_FRAME: 8,
        38 UNKNOWN_COMMAND: 9,
        39 UNSUPPORTED_OPERATION: 9, // Alias.
        40 STALE_ELEMENT_REFERENCE: 10,
        41 ELEMENT_NOT_VISIBLE: 11,
        42 INVALID_ELEMENT_STATE: 12,
        43 UNKNOWN_ERROR: 13,
        44 ELEMENT_NOT_SELECTABLE: 15,
        45 JAVASCRIPT_ERROR: 17,
        46 XPATH_LOOKUP_ERROR: 19,
        47 TIMEOUT: 21,
        48 NO_SUCH_WINDOW: 23,
        49 INVALID_COOKIE_DOMAIN: 24,
        50 UNABLE_TO_SET_COOKIE: 25,
        51 UNEXPECTED_ALERT_OPEN: 26,
        52 NO_SUCH_ALERT: 27,
        53 SCRIPT_TIMEOUT: 28,
        54 INVALID_ELEMENT_COORDINATES: 29,
        55 IME_NOT_AVAILABLE: 30,
        56 IME_ENGINE_ACTIVATION_FAILED: 31,
        57 INVALID_SELECTOR_ERROR: 32,
        58 SESSION_NOT_CREATED: 33,
        59 MOVE_TARGET_OUT_OF_BOUNDS: 34,
        60 SQL_DATABASE_ERROR: 35,
        61 INVALID_XPATH_SELECTOR: 51,
        62 INVALID_XPATH_SELECTOR_RETURN_TYPE: 52,
        63 // The following error codes are derived straight from HTTP return codes.
        64 METHOD_NOT_ALLOWED: 405
        65};
        66
        67
        68/**
        69 * Represents an error returned from a WebDriver command request.
        70 *
        71 * @param {!bot.ErrorCode} code The error's status code.
        72 * @param {string=} opt_message Optional error message.
        73 * @constructor
        74 * @extends {Error}
        75 */
        76bot.Error = function(code, opt_message) {
        77
        78 /**
        79 * This error's status code.
        80 * @type {!bot.ErrorCode}
        81 */
        82 this.code = code;
        83
        84 /** @type {string} */
        85 this.state =
        86 bot.Error.CODE_TO_STATE_[code] || bot.Error.State.UNKNOWN_ERROR;
        87
        88 /** @override */
        89 this.message = opt_message || '';
        90
        91 var name = this.state.replace(/((?:^|\s+)[a-z])/g, function(str) {
        92 // IE<9 does not support String#trim(). Also, IE does not include 0xa0
        93 // (the non-breaking-space) in the \s character class, so we have to
        94 // explicitly include it.
        95 return str.toUpperCase().replace(/^[\s\xa0]+/g, '');
        96 });
        97
        98 var l = name.length - 'Error'.length;
        99 if (l < 0 || name.indexOf('Error', l) != l) {
        100 name += 'Error';
        101 }
        102
        103 /** @override */
        104 this.name = name;
        105
        106 // Generate a stacktrace for our custom error; ensure the error has our
        107 // custom name and message so the stack prints correctly in all browsers.
        108 var template = new Error(this.message);
        109 template.name = this.name;
        110
        111 /** @override */
        112 this.stack = template.stack || '';
        113};
        114goog.inherits(bot.Error, Error);
        115
        116
        117/**
        118 * Status strings enumerated in the W3C WebDriver protocol.
        119 * @enum {string}
        120 * @see https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors
        121 */
        122bot.Error.State = {
        123 ELEMENT_NOT_SELECTABLE: 'element not selectable',
        124 ELEMENT_NOT_VISIBLE: 'element not visible',
        125 INVALID_ARGUMENT: 'invalid argument',
        126 INVALID_COOKIE_DOMAIN: 'invalid cookie domain',
        127 INVALID_ELEMENT_COORDINATES: 'invalid element coordinates',
        128 INVALID_ELEMENT_STATE: 'invalid element state',
        129 INVALID_SELECTOR: 'invalid selector',
        130 INVALID_SESSION_ID: 'invalid session id',
        131 JAVASCRIPT_ERROR: 'javascript error',
        132 MOVE_TARGET_OUT_OF_BOUNDS: 'move target out of bounds',
        133 NO_SUCH_ALERT: 'no such alert',
        134 NO_SUCH_ELEMENT: 'no such element',
        135 NO_SUCH_FRAME: 'no such frame',
        136 NO_SUCH_WINDOW: 'no such window',
        137 SCRIPT_TIMEOUT: 'script timeout',
        138 SESSION_NOT_CREATED: 'session not created',
        139 STALE_ELEMENT_REFERENCE: 'stale element reference',
        140 TIMEOUT: 'timeout',
        141 UNABLE_TO_SET_COOKIE: 'unable to set cookie',
        142 UNEXPECTED_ALERT_OPEN: 'unexpected alert open',
        143 UNKNOWN_COMMAND: 'unknown command',
        144 UNKNOWN_ERROR: 'unknown error',
        145 UNKNOWN_METHOD: 'unknown method',
        146 UNSUPPORTED_OPERATION: 'unsupported operation'
        147};
        148
        149
        150/**
        151 * A map of error codes to state string.
        152 * @private {!Object.<bot.ErrorCode, bot.Error.State>}
        153 */
        154bot.Error.CODE_TO_STATE_ = {};
        155goog.scope(function() {
        156 var map = bot.Error.CODE_TO_STATE_;
        157 var code = bot.ErrorCode;
        158 var state = bot.Error.State;
        159
        160 map[code.ELEMENT_NOT_SELECTABLE] = state.ELEMENT_NOT_SELECTABLE;
        161 map[code.ELEMENT_NOT_VISIBLE] = state.ELEMENT_NOT_VISIBLE;
        162 map[code.IME_ENGINE_ACTIVATION_FAILED] = state.UNKNOWN_ERROR;
        163 map[code.IME_NOT_AVAILABLE] = state.UNKNOWN_ERROR;
        164 map[code.INVALID_COOKIE_DOMAIN] = state.INVALID_COOKIE_DOMAIN;
        165 map[code.INVALID_ELEMENT_COORDINATES] = state.INVALID_ELEMENT_COORDINATES;
        166 map[code.INVALID_ELEMENT_STATE] = state.INVALID_ELEMENT_STATE;
        167 map[code.INVALID_SELECTOR_ERROR] = state.INVALID_SELECTOR;
        168 map[code.INVALID_XPATH_SELECTOR] = state.INVALID_SELECTOR;
        169 map[code.INVALID_XPATH_SELECTOR_RETURN_TYPE] = state.INVALID_SELECTOR;
        170 map[code.JAVASCRIPT_ERROR] = state.JAVASCRIPT_ERROR;
        171 map[code.METHOD_NOT_ALLOWED] = state.UNSUPPORTED_OPERATION;
        172 map[code.MOVE_TARGET_OUT_OF_BOUNDS] = state.MOVE_TARGET_OUT_OF_BOUNDS;
        173 map[code.NO_SUCH_ALERT] = state.NO_SUCH_ALERT;
        174 map[code.NO_SUCH_ELEMENT] = state.NO_SUCH_ELEMENT;
        175 map[code.NO_SUCH_FRAME] = state.NO_SUCH_FRAME;
        176 map[code.NO_SUCH_WINDOW] = state.NO_SUCH_WINDOW;
        177 map[code.SCRIPT_TIMEOUT] = state.SCRIPT_TIMEOUT;
        178 map[code.SESSION_NOT_CREATED] = state.SESSION_NOT_CREATED;
        179 map[code.STALE_ELEMENT_REFERENCE] = state.STALE_ELEMENT_REFERENCE;
        180 map[code.TIMEOUT] = state.TIMEOUT;
        181 map[code.UNABLE_TO_SET_COOKIE] = state.UNABLE_TO_SET_COOKIE;
        182 map[code.UNEXPECTED_ALERT_OPEN] = state.UNEXPECTED_ALERT_OPEN
        183 map[code.UNKNOWN_ERROR] = state.UNKNOWN_ERROR;
        184 map[code.UNSUPPORTED_OPERATION] = state.UNKNOWN_COMMAND;
        185}); // goog.scope
        186
        187
        188/**
        189 * Flag used for duck-typing when this code is embedded in a Firefox extension.
        190 * This is required since an Error thrown in one component and then reported
        191 * to another will fail instanceof checks in the second component.
        192 * @type {boolean}
        193 */
        194bot.Error.prototype.isAutomationError = true;
        195
        196
        197if (goog.DEBUG) {
        198 /** @return {string} The string representation of this error. */
        199 bot.Error.prototype.toString = function() {
        200 return this.name + ': ' + this.message;
        201 };
        202}
        \ No newline at end of file diff --git a/docs/source/lib/atoms/json.js.src.html b/docs/source/lib/atoms/json.js.src.html index a585c2f..7f38fbe 100644 --- a/docs/source/lib/atoms/json.js.src.html +++ b/docs/source/lib/atoms/json.js.src.html @@ -1 +1 @@ -json.js

        lib/atoms/json.js

        1// Copyright 2012 WebDriver committers
        2// Copyright 2012 Google Inc.
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview Provides JSON utilities that uses native JSON parsing where
        18 * possible (a feature not currently offered by Closure).
        19 */
        20
        21goog.provide('bot.json');
        22
        23goog.require('bot.userAgent');
        24goog.require('goog.json');
        25goog.require('goog.userAgent');
        26
        27
        28/**
        29 * @define {boolean} NATIVE_JSON indicates whether the code should rely on the
        30 * native {@code JSON} functions, if available.
        31 *
        32 * <p>The JSON functions can be defined by external libraries like Prototype
        33 * and setting this flag to false forces the use of Closure's goog.json
        34 * implementation.
        35 *
        36 * <p>If your JavaScript can be loaded by a third_party site and you are wary
        37 * about relying on the native functions, specify
        38 * "--define bot.json.NATIVE_JSON=false" to the Closure compiler.
        39 */
        40bot.json.NATIVE_JSON = true;
        41
        42
        43/**
        44 * Whether the current browser supports the native JSON interface.
        45 * @const
        46 * @see http://caniuse.com/#search=JSON
        47 * @private {boolean}
        48 */
        49bot.json.SUPPORTS_NATIVE_JSON_ =
        50 // List WebKit and Opera first since every supported version of these
        51 // browsers supports native JSON (and we can compile away large chunks of
        52 // code for individual fragments by setting the appropriate compiler flags).
        53 goog.userAgent.WEBKIT || goog.userAgent.OPERA ||
        54 (goog.userAgent.GECKO && bot.userAgent.isEngineVersion(3.5)) ||
        55 (goog.userAgent.IE && bot.userAgent.isEngineVersion(8));
        56
        57
        58/**
        59 * Converts a JSON object to its string representation.
        60 * @param {*} jsonObj The input object.
        61 * @param {?(function(string, *): *)=} opt_replacer A replacer function called
        62 * for each (key, value) pair that determines how the value should be
        63 * serialized. By default, this just returns the value and allows default
        64 * serialization to kick in.
        65 * @return {string} A JSON string representation of the input object.
        66 */
        67bot.json.stringify = bot.json.NATIVE_JSON && bot.json.SUPPORTS_NATIVE_JSON_ ?
        68 JSON.stringify : goog.json.serialize;
        69
        70
        71/**
        72 * Parses a JSON string and returns the result.
        73 * @param {string} jsonStr The string to parse.
        74 * @return {*} The JSON object.
        75 * @throws {Error} If the input string is an invalid JSON string.
        76 */
        77bot.json.parse = bot.json.NATIVE_JSON && bot.json.SUPPORTS_NATIVE_JSON_ ?
        78 JSON.parse : goog.json.parse;
        \ No newline at end of file +json.js

        lib/atoms/json.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Provides JSON utilities that uses native JSON parsing where
        20 * possible (a feature not currently offered by Closure).
        21 */
        22
        23goog.provide('bot.json');
        24
        25goog.require('bot.userAgent');
        26goog.require('goog.json');
        27goog.require('goog.userAgent');
        28
        29
        30/**
        31 * @define {boolean} NATIVE_JSON indicates whether the code should rely on the
        32 * native {@code JSON} functions, if available.
        33 *
        34 * <p>The JSON functions can be defined by external libraries like Prototype
        35 * and setting this flag to false forces the use of Closure's goog.json
        36 * implementation.
        37 *
        38 * <p>If your JavaScript can be loaded by a third_party site and you are wary
        39 * about relying on the native functions, specify
        40 * "--define bot.json.NATIVE_JSON=false" to the Closure compiler.
        41 */
        42bot.json.NATIVE_JSON = true;
        43
        44
        45/**
        46 * Whether the current browser supports the native JSON interface.
        47 * @const
        48 * @see http://caniuse.com/#search=JSON
        49 * @private {boolean}
        50 */
        51bot.json.SUPPORTS_NATIVE_JSON_ =
        52 // List WebKit first since every supported version supports
        53 // native JSON (and we can compile away large chunks of code for
        54 // individual fragments by setting the appropriate compiler flags).
        55 goog.userAgent.WEBKIT ||
        56 (goog.userAgent.GECKO && bot.userAgent.isEngineVersion(3.5)) ||
        57 (goog.userAgent.IE && bot.userAgent.isEngineVersion(8));
        58
        59
        60/**
        61 * Converts a JSON object to its string representation.
        62 * @param {*} jsonObj The input object.
        63 * @param {?(function(string, *): *)=} opt_replacer A replacer function called
        64 * for each (key, value) pair that determines how the value should be
        65 * serialized. By default, this just returns the value and allows default
        66 * serialization to kick in.
        67 * @return {string} A JSON string representation of the input object.
        68 */
        69bot.json.stringify = bot.json.NATIVE_JSON && bot.json.SUPPORTS_NATIVE_JSON_ ?
        70 JSON.stringify : goog.json.serialize;
        71
        72
        73/**
        74 * Parses a JSON string and returns the result.
        75 * @param {string} jsonStr The string to parse.
        76 * @return {*} The JSON object.
        77 * @throws {Error} If the input string is an invalid JSON string.
        78 */
        79bot.json.parse = bot.json.NATIVE_JSON && bot.json.SUPPORTS_NATIVE_JSON_ ?
        80 JSON.parse : goog.json.parse;
        \ No newline at end of file diff --git a/docs/source/lib/atoms/response.js.src.html b/docs/source/lib/atoms/response.js.src.html index 4a2bb7c..700e670 100644 --- a/docs/source/lib/atoms/response.js.src.html +++ b/docs/source/lib/atoms/response.js.src.html @@ -1 +1 @@ -response.js

        lib/atoms/response.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for working with WebDriver response objects.
        17 * @see: http://code.google.com/p/selenium/wiki/JsonWireProtocol#Responses
        18 */
        19
        20goog.provide('bot.response');
        21goog.provide('bot.response.ResponseObject');
        22
        23goog.require('bot.Error');
        24goog.require('bot.ErrorCode');
        25
        26
        27/**
        28 * Type definition for a response object, as defined by the JSON wire protocol.
        29 * @typedef {{status: bot.ErrorCode, value: (*|{message: string})}}
        30 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Responses
        31 */
        32bot.response.ResponseObject;
        33
        34
        35/**
        36 * @param {*} value The value to test.
        37 * @return {boolean} Whether the given value is a response object.
        38 */
        39bot.response.isResponseObject = function(value) {
        40 return goog.isObject(value) && goog.isNumber(value['status']);
        41};
        42
        43
        44/**
        45 * Creates a new success response object with the provided value.
        46 * @param {*} value The response value.
        47 * @return {!bot.response.ResponseObject} The new response object.
        48 */
        49bot.response.createResponse = function(value) {
        50 if (bot.response.isResponseObject(value)) {
        51 return /** @type {!bot.response.ResponseObject} */ (value);
        52 }
        53 return {
        54 'status': bot.ErrorCode.SUCCESS,
        55 'value': value
        56 };
        57};
        58
        59
        60/**
        61 * Converts an error value into its JSON representation as defined by the
        62 * WebDriver wire protocol.
        63 * @param {(bot.Error|Error|*)} error The error value to convert.
        64 * @return {!bot.response.ResponseObject} The new response object.
        65 */
        66bot.response.createErrorResponse = function(error) {
        67 if (bot.response.isResponseObject(error)) {
        68 return /** @type {!bot.response.ResponseObject} */ (error);
        69 }
        70
        71 var statusCode = error && goog.isNumber(error.code) ? error.code :
        72 bot.ErrorCode.UNKNOWN_ERROR;
        73 return {
        74 'status': /** @type {bot.ErrorCode} */ (statusCode),
        75 'value': {
        76 'message': (error && error.message || error) + ''
        77 }
        78 };
        79};
        80
        81
        82/**
        83 * Checks that a response object does not specify an error as defined by the
        84 * WebDriver wire protocol. If the response object defines an error, it will
        85 * be thrown. Otherwise, the response will be returned as is.
        86 * @param {!bot.response.ResponseObject} responseObj The response object to
        87 * check.
        88 * @return {!bot.response.ResponseObject} The checked response object.
        89 * @throws {bot.Error} If the response describes an error.
        90 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Failed_Commands
        91 */
        92bot.response.checkResponse = function(responseObj) {
        93 var status = responseObj['status'];
        94 if (status == bot.ErrorCode.SUCCESS) {
        95 return responseObj;
        96 }
        97
        98 // If status is not defined, assume an unknown error.
        99 status = status || bot.ErrorCode.UNKNOWN_ERROR;
        100
        101 var value = responseObj['value'];
        102 if (!value || !goog.isObject(value)) {
        103 throw new bot.Error(status, value + '');
        104 }
        105
        106 throw new bot.Error(status, value['message'] + '');
        107};
        \ No newline at end of file +response.js

        lib/atoms/response.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Utilities for working with WebDriver response objects.
        20 * @see: hhttps://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#responses
        21 */
        22
        23goog.provide('bot.response');
        24goog.provide('bot.response.ResponseObject');
        25
        26goog.require('bot.Error');
        27goog.require('bot.ErrorCode');
        28
        29
        30/**
        31 * Type definition for a response object, as defined by the JSON wire protocol.
        32 * @typedef {{status: bot.ErrorCode, value: (*|{message: string})}}
        33 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#responses
        34 */
        35bot.response.ResponseObject;
        36
        37
        38/**
        39 * @param {*} value The value to test.
        40 * @return {boolean} Whether the given value is a response object.
        41 */
        42bot.response.isResponseObject = function(value) {
        43 return goog.isObject(value) && goog.isNumber(value['status']);
        44};
        45
        46
        47/**
        48 * Creates a new success response object with the provided value.
        49 * @param {*} value The response value.
        50 * @return {!bot.response.ResponseObject} The new response object.
        51 */
        52bot.response.createResponse = function(value) {
        53 if (bot.response.isResponseObject(value)) {
        54 return /** @type {!bot.response.ResponseObject} */ (value);
        55 }
        56 return {
        57 'status': bot.ErrorCode.SUCCESS,
        58 'value': value
        59 };
        60};
        61
        62
        63/**
        64 * Converts an error value into its JSON representation as defined by the
        65 * WebDriver wire protocol.
        66 * @param {(bot.Error|Error|*)} error The error value to convert.
        67 * @return {!bot.response.ResponseObject} The new response object.
        68 */
        69bot.response.createErrorResponse = function(error) {
        70 if (bot.response.isResponseObject(error)) {
        71 return /** @type {!bot.response.ResponseObject} */ (error);
        72 }
        73
        74 var statusCode = error && goog.isNumber(error.code) ? error.code :
        75 bot.ErrorCode.UNKNOWN_ERROR;
        76 return {
        77 'status': /** @type {bot.ErrorCode} */ (statusCode),
        78 'value': {
        79 'message': (error && error.message || error) + ''
        80 }
        81 };
        82};
        83
        84
        85/**
        86 * Checks that a response object does not specify an error as defined by the
        87 * WebDriver wire protocol. If the response object defines an error, it will
        88 * be thrown. Otherwise, the response will be returned as is.
        89 * @param {!bot.response.ResponseObject} responseObj The response object to
        90 * check.
        91 * @return {!bot.response.ResponseObject} The checked response object.
        92 * @throws {bot.Error} If the response describes an error.
        93 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#failed-commands
        94 */
        95bot.response.checkResponse = function(responseObj) {
        96 var status = responseObj['status'];
        97 if (status == bot.ErrorCode.SUCCESS) {
        98 return responseObj;
        99 }
        100
        101 // If status is not defined, assume an unknown error.
        102 status = status || bot.ErrorCode.UNKNOWN_ERROR;
        103
        104 var value = responseObj['value'];
        105 if (!value || !goog.isObject(value)) {
        106 throw new bot.Error(status, value + '');
        107 }
        108
        109 throw new bot.Error(status, value['message'] + '');
        110};
        \ No newline at end of file diff --git a/docs/source/lib/atoms/userAgent.js.src.html b/docs/source/lib/atoms/userAgent.js.src.html index 9eed02c..ae1f199 100644 --- a/docs/source/lib/atoms/userAgent.js.src.html +++ b/docs/source/lib/atoms/userAgent.js.src.html @@ -1 +1 @@ -userAgent.js

        lib/atoms/userAgent.js

        1// Copyright 2011 WebDriver committers
        2// Copyright 2011 Google Inc.
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview Similar to goog.userAgent.isVersion, but with support for
        18 * getting the version information when running in a firefox extension.
        19 */
        20goog.provide('bot.userAgent');
        21
        22goog.require('goog.string');
        23goog.require('goog.userAgent');
        24goog.require('goog.userAgent.product');
        25goog.require('goog.userAgent.product.isVersion');
        26
        27
        28/**
        29 * Whether the rendering engine version of the current browser is equal to or
        30 * greater than the given version. This implementation differs from
        31 * goog.userAgent.isVersion in the following ways:
        32 * <ol>
        33 * <li>in a Firefox extension, tests the engine version through the XUL version
        34 * comparator service, because no window.navigator object is available
        35 * <li>in IE, compares the given version to the current documentMode
        36 * </ol>
        37 *
        38 * @param {string|number} version The version number to check.
        39 * @return {boolean} Whether the browser engine version is the same or higher
        40 * than the given version.
        41 */
        42bot.userAgent.isEngineVersion = function(version) {
        43 if (bot.userAgent.FIREFOX_EXTENSION) {
        44 return bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_(version);
        45 } else if (goog.userAgent.IE) {
        46 return goog.string.compareVersions(
        47 /** @type {number} */ (goog.userAgent.DOCUMENT_MODE), version) >= 0;
        48 } else {
        49 return goog.userAgent.isVersionOrHigher(version);
        50 }
        51};
        52
        53
        54/**
        55 * Whether the product version of the current browser is equal to or greater
        56 * than the given version. This implementation differs from
        57 * goog.userAgent.product.isVersion in the following ways:
        58 * <ol>
        59 * <li>in a Firefox extension, tests the product version through the XUL version
        60 * comparator service, because no window.navigator object is available
        61 * <li>on Android, always compares to the version to the OS version
        62 * </ol>
        63 *
        64 * @param {string|number} version The version number to check.
        65 * @return {boolean} Whether the browser product version is the same or higher
        66 * than the given version.
        67 */
        68bot.userAgent.isProductVersion = function(version) {
        69 if (bot.userAgent.FIREFOX_EXTENSION) {
        70 return bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_(version);
        71 } else if (goog.userAgent.product.ANDROID) {
        72 return goog.string.compareVersions(
        73 bot.userAgent.ANDROID_VERSION_, version) >= 0;
        74 } else {
        75 return goog.userAgent.product.isVersion(version);
        76 }
        77};
        78
        79
        80/**
        81 * When we are in a Firefox extension, this is a function that accepts a version
        82 * and returns whether the version of Gecko we are on is the same or higher
        83 * than the given version. When we are not in a Firefox extension, this is null.
        84 * @private {(undefined|function((string|number)): boolean)}
        85 */
        86bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_;
        87
        88
        89/**
        90 * When we are in a Firefox extension, this is a function that accepts a version
        91 * and returns whether the version of Firefox we are on is the same or higher
        92 * than the given version. When we are not in a Firefox extension, this is null.
        93 * @private {(undefined|function((string|number)): boolean)}
        94 */
        95bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_;
        96
        97
        98/**
        99 * Whether we are in a Firefox extension.
        100 *
        101 * @const
        102 * @type {boolean}
        103 */
        104bot.userAgent.FIREFOX_EXTENSION = (function() {
        105 // False if this browser is not a Gecko browser.
        106 if (!goog.userAgent.GECKO) {
        107 return false;
        108 }
        109
        110 // False if this code isn't running in an extension.
        111 var Components = goog.global.Components;
        112 if (!Components) {
        113 return false;
        114 }
        115 try {
        116 if (!Components['classes']) {
        117 return false;
        118 }
        119 } catch (e) {
        120 return false;
        121 }
        122
        123 // Populate the version checker functions.
        124 var cc = Components['classes'];
        125 var ci = Components['interfaces'];
        126 var versionComparator = cc['@mozilla.org/xpcom/version-comparator;1'][
        127 'getService'](ci['nsIVersionComparator']);
        128 var appInfo = cc['@mozilla.org/xre/app-info;1']['getService'](
        129 ci['nsIXULAppInfo']);
        130 var geckoVersion = appInfo['platformVersion'];
        131 var firefoxVersion = appInfo['version'];
        132
        133 bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_ = function(version) {
        134 return versionComparator.compare(geckoVersion, '' + version) >= 0;
        135 };
        136 bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_ = function(version) {
        137 return versionComparator.compare(firefoxVersion, '' + version) >= 0;
        138 };
        139
        140 return true;
        141})();
        142
        143
        144/**
        145 * Whether we are on IOS.
        146 *
        147 * @const
        148 * @type {boolean}
        149 */
        150bot.userAgent.IOS = goog.userAgent.product.IPAD ||
        151 goog.userAgent.product.IPHONE;
        152
        153
        154/**
        155 * Whether we are on a mobile browser.
        156 *
        157 * @const
        158 * @type {boolean}
        159 */
        160bot.userAgent.MOBILE = bot.userAgent.IOS || goog.userAgent.product.ANDROID;
        161
        162
        163/**
        164 * Android Operating System Version.
        165 * @private {string}
        166 * @const
        167 */
        168bot.userAgent.ANDROID_VERSION_ = (function() {
        169 if (goog.userAgent.product.ANDROID) {
        170 var userAgentString = goog.userAgent.getUserAgentString();
        171 var match = /Android\s+([0-9\.]+)/.exec(userAgentString);
        172 return match ? match[1] : '0';
        173 } else {
        174 return '0';
        175 }
        176})();
        177
        178
        179/**
        180 * Whether the current document is IE in a documentMode older than 8.
        181 * @type {boolean}
        182 * @const
        183 */
        184bot.userAgent.IE_DOC_PRE8 = goog.userAgent.IE &&
        185 !goog.userAgent.isDocumentModeOrHigher(8);
        186
        187
        188/**
        189 * Whether the current document is IE in IE9 (or newer) standards mode.
        190 * @type {boolean}
        191 * @const
        192 */
        193bot.userAgent.IE_DOC_9 = goog.userAgent.isDocumentModeOrHigher(9);
        194
        195
        196/**
        197 * Whether the current document is IE in a documentMode older than 9.
        198 * @type {boolean}
        199 * @const
        200 */
        201bot.userAgent.IE_DOC_PRE9 = goog.userAgent.IE &&
        202 !goog.userAgent.isDocumentModeOrHigher(9);
        203
        204
        205/**
        206 * Whether the current document is IE in IE10 (or newer) standards mode.
        207 * @type {boolean}
        208 * @const
        209 */
        210bot.userAgent.IE_DOC_10 = goog.userAgent.isDocumentModeOrHigher(10);
        211
        212
        213/**
        214 * Whether the current document is IE in a documentMode older than 10.
        215 * @type {boolean}
        216 * @const
        217 */
        218bot.userAgent.IE_DOC_PRE10 = goog.userAgent.IE &&
        219 !goog.userAgent.isDocumentModeOrHigher(10);
        220
        221
        222/**
        223 * Whether the current browser is Android pre-gingerbread.
        224 * @type {boolean}
        225 * @const
        226 */
        227bot.userAgent.ANDROID_PRE_GINGERBREAD = goog.userAgent.product.ANDROID &&
        228 !bot.userAgent.isProductVersion(2.3);
        229
        230
        231/**
        232 * Whether the current browser is Android pre-icecreamsandwich
        233 * @type {boolean}
        234 * @const
        235 */
        236bot.userAgent.ANDROID_PRE_ICECREAMSANDWICH = goog.userAgent.product.ANDROID &&
        237 !bot.userAgent.isProductVersion(4);
        238
        239
        240/**
        241 * Whether the current browser is Safari 6.
        242 * @type {boolean}
        243 * @const
        244 */
        245bot.userAgent.SAFARI_6 = goog.userAgent.product.SAFARI &&
        246 bot.userAgent.isProductVersion(6);
        247
        248
        249/**
        250 * Whether the current browser is Windows Phone.
        251 * @type {boolean}
        252 * @const
        253 */
        254bot.userAgent.WINDOWS_PHONE = goog.userAgent.IE &&
        255 goog.userAgent.getUserAgentString().indexOf('IEMobile') != -1;
        \ No newline at end of file +userAgent.js

        lib/atoms/userAgent.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Similar to goog.userAgent.isVersion, but with support for
        20 * getting the version information when running in a firefox extension.
        21 */
        22goog.provide('bot.userAgent');
        23
        24goog.require('goog.string');
        25goog.require('goog.userAgent');
        26goog.require('goog.userAgent.product');
        27goog.require('goog.userAgent.product.isVersion');
        28
        29
        30/**
        31 * Whether the rendering engine version of the current browser is equal to or
        32 * greater than the given version. This implementation differs from
        33 * goog.userAgent.isVersion in the following ways:
        34 * <ol>
        35 * <li>in a Firefox extension, tests the engine version through the XUL version
        36 * comparator service, because no window.navigator object is available
        37 * <li>in IE, compares the given version to the current documentMode
        38 * </ol>
        39 *
        40 * @param {string|number} version The version number to check.
        41 * @return {boolean} Whether the browser engine version is the same or higher
        42 * than the given version.
        43 */
        44bot.userAgent.isEngineVersion = function(version) {
        45 if (bot.userAgent.FIREFOX_EXTENSION) {
        46 return bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_(version);
        47 } else if (goog.userAgent.IE) {
        48 return goog.string.compareVersions(
        49 /** @type {number} */ (goog.userAgent.DOCUMENT_MODE), version) >= 0;
        50 } else {
        51 return goog.userAgent.isVersionOrHigher(version);
        52 }
        53};
        54
        55
        56/**
        57 * Whether the product version of the current browser is equal to or greater
        58 * than the given version. This implementation differs from
        59 * goog.userAgent.product.isVersion in the following ways:
        60 * <ol>
        61 * <li>in a Firefox extension, tests the product version through the XUL version
        62 * comparator service, because no window.navigator object is available
        63 * <li>on Android, always compares to the version to the OS version
        64 * </ol>
        65 *
        66 * @param {string|number} version The version number to check.
        67 * @return {boolean} Whether the browser product version is the same or higher
        68 * than the given version.
        69 */
        70bot.userAgent.isProductVersion = function(version) {
        71 if (bot.userAgent.FIREFOX_EXTENSION) {
        72 return bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_(version);
        73 } else if (goog.userAgent.product.ANDROID) {
        74 return goog.string.compareVersions(
        75 bot.userAgent.ANDROID_VERSION_, version) >= 0;
        76 } else {
        77 return goog.userAgent.product.isVersion(version);
        78 }
        79};
        80
        81
        82/**
        83 * When we are in a Firefox extension, this is a function that accepts a version
        84 * and returns whether the version of Gecko we are on is the same or higher
        85 * than the given version. When we are not in a Firefox extension, this is null.
        86 * @private {(undefined|function((string|number)): boolean)}
        87 */
        88bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_;
        89
        90
        91/**
        92 * When we are in a Firefox extension, this is a function that accepts a version
        93 * and returns whether the version of Firefox we are on is the same or higher
        94 * than the given version. When we are not in a Firefox extension, this is null.
        95 * @private {(undefined|function((string|number)): boolean)}
        96 */
        97bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_;
        98
        99
        100/**
        101 * Whether we are in a Firefox extension.
        102 *
        103 * @const
        104 * @type {boolean}
        105 */
        106bot.userAgent.FIREFOX_EXTENSION = (function() {
        107 // False if this browser is not a Gecko browser.
        108 if (!goog.userAgent.GECKO) {
        109 return false;
        110 }
        111
        112 // False if this code isn't running in an extension.
        113 var Components = goog.global.Components;
        114 if (!Components) {
        115 return false;
        116 }
        117 try {
        118 if (!Components['classes']) {
        119 return false;
        120 }
        121 } catch (e) {
        122 return false;
        123 }
        124
        125 // Populate the version checker functions.
        126 var cc = Components['classes'];
        127 var ci = Components['interfaces'];
        128 var versionComparator = cc['@mozilla.org/xpcom/version-comparator;1'][
        129 'getService'](ci['nsIVersionComparator']);
        130 var appInfo = cc['@mozilla.org/xre/app-info;1']['getService'](
        131 ci['nsIXULAppInfo']);
        132 var geckoVersion = appInfo['platformVersion'];
        133 var firefoxVersion = appInfo['version'];
        134
        135 bot.userAgent.FIREFOX_EXTENSION_IS_ENGINE_VERSION_ = function(version) {
        136 return versionComparator.compare(geckoVersion, '' + version) >= 0;
        137 };
        138 bot.userAgent.FIREFOX_EXTENSION_IS_PRODUCT_VERSION_ = function(version) {
        139 return versionComparator.compare(firefoxVersion, '' + version) >= 0;
        140 };
        141
        142 return true;
        143})();
        144
        145
        146/**
        147 * Whether we are on IOS.
        148 *
        149 * @const
        150 * @type {boolean}
        151 */
        152bot.userAgent.IOS = goog.userAgent.product.IPAD ||
        153 goog.userAgent.product.IPHONE;
        154
        155
        156/**
        157 * Whether we are on a mobile browser.
        158 *
        159 * @const
        160 * @type {boolean}
        161 */
        162bot.userAgent.MOBILE = bot.userAgent.IOS || goog.userAgent.product.ANDROID;
        163
        164
        165/**
        166 * Android Operating System Version.
        167 * @private {string}
        168 * @const
        169 */
        170bot.userAgent.ANDROID_VERSION_ = (function() {
        171 if (goog.userAgent.product.ANDROID) {
        172 var userAgentString = goog.userAgent.getUserAgentString();
        173 var match = /Android\s+([0-9\.]+)/.exec(userAgentString);
        174 return match ? match[1] : '0';
        175 } else {
        176 return '0';
        177 }
        178})();
        179
        180
        181/**
        182 * Whether the current document is IE in a documentMode older than 8.
        183 * @type {boolean}
        184 * @const
        185 */
        186bot.userAgent.IE_DOC_PRE8 = goog.userAgent.IE &&
        187 !goog.userAgent.isDocumentModeOrHigher(8);
        188
        189
        190/**
        191 * Whether the current document is IE in IE9 (or newer) standards mode.
        192 * @type {boolean}
        193 * @const
        194 */
        195bot.userAgent.IE_DOC_9 = goog.userAgent.isDocumentModeOrHigher(9);
        196
        197
        198/**
        199 * Whether the current document is IE in a documentMode older than 9.
        200 * @type {boolean}
        201 * @const
        202 */
        203bot.userAgent.IE_DOC_PRE9 = goog.userAgent.IE &&
        204 !goog.userAgent.isDocumentModeOrHigher(9);
        205
        206
        207/**
        208 * Whether the current document is IE in IE10 (or newer) standards mode.
        209 * @type {boolean}
        210 * @const
        211 */
        212bot.userAgent.IE_DOC_10 = goog.userAgent.isDocumentModeOrHigher(10);
        213
        214
        215/**
        216 * Whether the current document is IE in a documentMode older than 10.
        217 * @type {boolean}
        218 * @const
        219 */
        220bot.userAgent.IE_DOC_PRE10 = goog.userAgent.IE &&
        221 !goog.userAgent.isDocumentModeOrHigher(10);
        222
        223
        224/**
        225 * Whether the current browser is Android pre-gingerbread.
        226 * @type {boolean}
        227 * @const
        228 */
        229bot.userAgent.ANDROID_PRE_GINGERBREAD = goog.userAgent.product.ANDROID &&
        230 !bot.userAgent.isProductVersion(2.3);
        231
        232
        233/**
        234 * Whether the current browser is Android pre-icecreamsandwich
        235 * @type {boolean}
        236 * @const
        237 */
        238bot.userAgent.ANDROID_PRE_ICECREAMSANDWICH = goog.userAgent.product.ANDROID &&
        239 !bot.userAgent.isProductVersion(4);
        240
        241
        242/**
        243 * Whether the current browser is Safari 6.
        244 * @type {boolean}
        245 * @const
        246 */
        247bot.userAgent.SAFARI_6 = goog.userAgent.product.SAFARI &&
        248 bot.userAgent.isProductVersion(6);
        249
        250
        251/**
        252 * Whether the current browser is Windows Phone.
        253 * @type {boolean}
        254 * @const
        255 */
        256bot.userAgent.WINDOWS_PHONE = goog.userAgent.IE &&
        257 goog.userAgent.getUserAgentString().indexOf('IEMobile') != -1;
        \ No newline at end of file diff --git a/docs/source/lib/goog/array/array.js.src.html b/docs/source/lib/goog/array/array.js.src.html index aab8e53..5b88d26 100644 --- a/docs/source/lib/goog/array/array.js.src.html +++ b/docs/source/lib/goog/array/array.js.src.html @@ -1 +1 @@ -array.js

        lib/goog/array/array.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for manipulating arrays.
        17 *
        18 */
        19
        20
        21goog.provide('goog.array');
        22goog.provide('goog.array.ArrayLike');
        23
        24goog.require('goog.asserts');
        25
        26
        27/**
        28 * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
        29 * rely on Array.prototype functions, if available.
        30 *
        31 * The Array.prototype functions can be defined by external libraries like
        32 * Prototype and setting this flag to false forces closure to use its own
        33 * goog.array implementation.
        34 *
        35 * If your javascript can be loaded by a third party site and you are wary about
        36 * relying on the prototype functions, specify
        37 * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
        38 *
        39 * Setting goog.TRUSTED_SITE to false will automatically set
        40 * NATIVE_ARRAY_PROTOTYPES to false.
        41 */
        42goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
        43
        44
        45/**
        46 * @typedef {Array|NodeList|Arguments|{length: number}}
        47 */
        48goog.array.ArrayLike;
        49
        50
        51/**
        52 * Returns the last element in an array without removing it.
        53 * @param {goog.array.ArrayLike} array The array.
        54 * @return {*} Last item in array.
        55 */
        56goog.array.peek = function(array) {
        57 return array[array.length - 1];
        58};
        59
        60
        61/**
        62 * Reference to the original {@code Array.prototype}.
        63 * @private
        64 */
        65goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
        66
        67
        68// NOTE(arv): Since most of the array functions are generic it allows you to
        69// pass an array-like object. Strings have a length and are considered array-
        70// like. However, the 'in' operator does not work on strings so we cannot just
        71// use the array path even if the browser supports indexing into strings. We
        72// therefore end up splitting the string.
        73
        74
        75/**
        76 * Returns the index of the first element of an array with a specified
        77 * value, or -1 if the element is not present in the array.
        78 *
        79 * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
        80 *
        81 * @param {goog.array.ArrayLike} arr The array to be searched.
        82 * @param {*} obj The object for which we are searching.
        83 * @param {number=} opt_fromIndex The index at which to start the search. If
        84 * omitted the search starts at index 0.
        85 * @return {number} The index of the first matching array element.
        86 */
        87goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        88 goog.array.ARRAY_PROTOTYPE_.indexOf ?
        89 function(arr, obj, opt_fromIndex) {
        90 goog.asserts.assert(arr.length != null);
        91
        92 return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
        93 } :
        94 function(arr, obj, opt_fromIndex) {
        95 var fromIndex = opt_fromIndex == null ?
        96 0 : (opt_fromIndex < 0 ?
        97 Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
        98
        99 if (goog.isString(arr)) {
        100 // Array.prototype.indexOf uses === so only strings should be found.
        101 if (!goog.isString(obj) || obj.length != 1) {
        102 return -1;
        103 }
        104 return arr.indexOf(obj, fromIndex);
        105 }
        106
        107 for (var i = fromIndex; i < arr.length; i++) {
        108 if (i in arr && arr[i] === obj)
        109 return i;
        110 }
        111 return -1;
        112 };
        113
        114
        115/**
        116 * Returns the index of the last element of an array with a specified value, or
        117 * -1 if the element is not present in the array.
        118 *
        119 * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
        120 *
        121 * @param {goog.array.ArrayLike} arr The array to be searched.
        122 * @param {*} obj The object for which we are searching.
        123 * @param {?number=} opt_fromIndex The index at which to start the search. If
        124 * omitted the search starts at the end of the array.
        125 * @return {number} The index of the last matching array element.
        126 */
        127goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        128 goog.array.ARRAY_PROTOTYPE_.lastIndexOf ?
        129 function(arr, obj, opt_fromIndex) {
        130 goog.asserts.assert(arr.length != null);
        131
        132 // Firefox treats undefined and null as 0 in the fromIndex argument which
        133 // leads it to always return -1
        134 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
        135 return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
        136 } :
        137 function(arr, obj, opt_fromIndex) {
        138 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
        139
        140 if (fromIndex < 0) {
        141 fromIndex = Math.max(0, arr.length + fromIndex);
        142 }
        143
        144 if (goog.isString(arr)) {
        145 // Array.prototype.lastIndexOf uses === so only strings should be found.
        146 if (!goog.isString(obj) || obj.length != 1) {
        147 return -1;
        148 }
        149 return arr.lastIndexOf(obj, fromIndex);
        150 }
        151
        152 for (var i = fromIndex; i >= 0; i--) {
        153 if (i in arr && arr[i] === obj)
        154 return i;
        155 }
        156 return -1;
        157 };
        158
        159
        160/**
        161 * Calls a function for each element in an array. Skips holes in the array.
        162 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
        163 *
        164 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
        165 * which to iterate.
        166 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
        167 * element. This function takes 3 arguments (the element, the index and the
        168 * array). The return value is ignored.
        169 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
        170 * @template T,S
        171 */
        172goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
        173 goog.array.ARRAY_PROTOTYPE_.forEach ?
        174 function(arr, f, opt_obj) {
        175 goog.asserts.assert(arr.length != null);
        176
        177 goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
        178 } :
        179 function(arr, f, opt_obj) {
        180 var l = arr.length; // must be fixed during loop... see docs
        181 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        182 for (var i = 0; i < l; i++) {
        183 if (i in arr2) {
        184 f.call(opt_obj, arr2[i], i, arr);
        185 }
        186 }
        187 };
        188
        189
        190/**
        191 * Calls a function for each element in an array, starting from the last
        192 * element rather than the first.
        193 *
        194 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        195 * like object over which to iterate.
        196 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
        197 * element. This function
        198 * takes 3 arguments (the element, the index and the array). The return
        199 * value is ignored.
        200 * @param {S=} opt_obj The object to be used as the value of 'this'
        201 * within f.
        202 * @template T,S
        203 */
        204goog.array.forEachRight = function(arr, f, opt_obj) {
        205 var l = arr.length; // must be fixed during loop... see docs
        206 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        207 for (var i = l - 1; i >= 0; --i) {
        208 if (i in arr2) {
        209 f.call(opt_obj, arr2[i], i, arr);
        210 }
        211 }
        212};
        213
        214
        215/**
        216 * Calls a function for each element in an array, and if the function returns
        217 * true adds the element to a new array.
        218 *
        219 * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
        220 *
        221 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        222 * like object over which to iterate.
        223 * @param {?function(this:S, T, number, ?):boolean} f The function to call for
        224 * every element. This function
        225 * takes 3 arguments (the element, the index and the array) and must
        226 * return a Boolean. If the return value is true the element is added to the
        227 * result array. If it is false the element is not included.
        228 * @param {S=} opt_obj The object to be used as the value of 'this'
        229 * within f.
        230 * @return {!Array} a new array in which only elements that passed the test are
        231 * present.
        232 * @template T,S
        233 */
        234goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
        235 goog.array.ARRAY_PROTOTYPE_.filter ?
        236 function(arr, f, opt_obj) {
        237 goog.asserts.assert(arr.length != null);
        238
        239 return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
        240 } :
        241 function(arr, f, opt_obj) {
        242 var l = arr.length; // must be fixed during loop... see docs
        243 var res = [];
        244 var resLength = 0;
        245 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        246 for (var i = 0; i < l; i++) {
        247 if (i in arr2) {
        248 var val = arr2[i]; // in case f mutates arr2
        249 if (f.call(opt_obj, val, i, arr)) {
        250 res[resLength++] = val;
        251 }
        252 }
        253 }
        254 return res;
        255 };
        256
        257
        258/**
        259 * Calls a function for each element in an array and inserts the result into a
        260 * new array.
        261 *
        262 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
        263 *
        264 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        265 * like object over which to iterate.
        266 * @param {?function(this:S, T, number, ?):?} f The function to call for every
        267 * element. This function
        268 * takes 3 arguments (the element, the index and the array) and should
        269 * return something. The result will be inserted into a new array.
        270 * @param {S=} opt_obj The object to be used as the value of 'this'
        271 * within f.
        272 * @return {!Array} a new array with the results from f.
        273 * @template T,S
        274 */
        275goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
        276 goog.array.ARRAY_PROTOTYPE_.map ?
        277 function(arr, f, opt_obj) {
        278 goog.asserts.assert(arr.length != null);
        279
        280 return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
        281 } :
        282 function(arr, f, opt_obj) {
        283 var l = arr.length; // must be fixed during loop... see docs
        284 var res = new Array(l);
        285 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        286 for (var i = 0; i < l; i++) {
        287 if (i in arr2) {
        288 res[i] = f.call(opt_obj, arr2[i], i, arr);
        289 }
        290 }
        291 return res;
        292 };
        293
        294
        295/**
        296 * Passes every element of an array into a function and accumulates the result.
        297 *
        298 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
        299 *
        300 * For example:
        301 * var a = [1, 2, 3, 4];
        302 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
        303 * returns 10
        304 *
        305 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        306 * like object over which to iterate.
        307 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
        308 * every element. This function
        309 * takes 4 arguments (the function's previous result or the initial value,
        310 * the value of the current array element, the current array index, and the
        311 * array itself)
        312 * function(previousValue, currentValue, index, array).
        313 * @param {?} val The initial value to pass into the function on the first call.
        314 * @param {S=} opt_obj The object to be used as the value of 'this'
        315 * within f.
        316 * @return {R} Result of evaluating f repeatedly across the values of the array.
        317 * @template T,S,R
        318 */
        319goog.array.reduce = function(arr, f, val, opt_obj) {
        320 if (arr.reduce) {
        321 if (opt_obj) {
        322 return arr.reduce(goog.bind(f, opt_obj), val);
        323 } else {
        324 return arr.reduce(f, val);
        325 }
        326 }
        327 var rval = val;
        328 goog.array.forEach(arr, function(val, index) {
        329 rval = f.call(opt_obj, rval, val, index, arr);
        330 });
        331 return rval;
        332};
        333
        334
        335/**
        336 * Passes every element of an array into a function and accumulates the result,
        337 * starting from the last element and working towards the first.
        338 *
        339 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
        340 *
        341 * For example:
        342 * var a = ['a', 'b', 'c'];
        343 * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
        344 * returns 'cba'
        345 *
        346 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        347 * like object over which to iterate.
        348 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
        349 * every element. This function
        350 * takes 4 arguments (the function's previous result or the initial value,
        351 * the value of the current array element, the current array index, and the
        352 * array itself)
        353 * function(previousValue, currentValue, index, array).
        354 * @param {?} val The initial value to pass into the function on the first call.
        355 * @param {S=} opt_obj The object to be used as the value of 'this'
        356 * within f.
        357 * @return {R} Object returned as a result of evaluating f repeatedly across the
        358 * values of the array.
        359 * @template T,S,R
        360 */
        361goog.array.reduceRight = function(arr, f, val, opt_obj) {
        362 if (arr.reduceRight) {
        363 if (opt_obj) {
        364 return arr.reduceRight(goog.bind(f, opt_obj), val);
        365 } else {
        366 return arr.reduceRight(f, val);
        367 }
        368 }
        369 var rval = val;
        370 goog.array.forEachRight(arr, function(val, index) {
        371 rval = f.call(opt_obj, rval, val, index, arr);
        372 });
        373 return rval;
        374};
        375
        376
        377/**
        378 * Calls f for each element of an array. If any call returns true, some()
        379 * returns true (without checking the remaining elements). If all calls
        380 * return false, some() returns false.
        381 *
        382 * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
        383 *
        384 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        385 * like object over which to iterate.
        386 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        387 * for every element. This function takes 3 arguments (the element, the
        388 * index and the array) and should return a boolean.
        389 * @param {S=} opt_obj The object to be used as the value of 'this'
        390 * within f.
        391 * @return {boolean} true if any element passes the test.
        392 * @template T,S
        393 */
        394goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
        395 goog.array.ARRAY_PROTOTYPE_.some ?
        396 function(arr, f, opt_obj) {
        397 goog.asserts.assert(arr.length != null);
        398
        399 return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
        400 } :
        401 function(arr, f, opt_obj) {
        402 var l = arr.length; // must be fixed during loop... see docs
        403 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        404 for (var i = 0; i < l; i++) {
        405 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        406 return true;
        407 }
        408 }
        409 return false;
        410 };
        411
        412
        413/**
        414 * Call f for each element of an array. If all calls return true, every()
        415 * returns true. If any call returns false, every() returns false and
        416 * does not continue to check the remaining elements.
        417 *
        418 * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
        419 *
        420 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        421 * like object over which to iterate.
        422 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        423 * for every element. This function takes 3 arguments (the element, the
        424 * index and the array) and should return a boolean.
        425 * @param {S=} opt_obj The object to be used as the value of 'this'
        426 * within f.
        427 * @return {boolean} false if any element fails the test.
        428 * @template T,S
        429 */
        430goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
        431 goog.array.ARRAY_PROTOTYPE_.every ?
        432 function(arr, f, opt_obj) {
        433 goog.asserts.assert(arr.length != null);
        434
        435 return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
        436 } :
        437 function(arr, f, opt_obj) {
        438 var l = arr.length; // must be fixed during loop... see docs
        439 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        440 for (var i = 0; i < l; i++) {
        441 if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
        442 return false;
        443 }
        444 }
        445 return true;
        446 };
        447
        448
        449/**
        450 * Counts the array elements that fulfill the predicate, i.e. for which the
        451 * callback function returns true. Skips holes in the array.
        452 *
        453 * @param {!(Array.<T>|goog.array.ArrayLike)} arr Array or array like object
        454 * over which to iterate.
        455 * @param {function(this: S, T, number, ?): boolean} f The function to call for
        456 * every element. Takes 3 arguments (the element, the index and the array).
        457 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
        458 * @return {number} The number of the matching elements.
        459 * @template T,S
        460 */
        461goog.array.count = function(arr, f, opt_obj) {
        462 var count = 0;
        463 goog.array.forEach(arr, function(element, index, arr) {
        464 if (f.call(opt_obj, element, index, arr)) {
        465 ++count;
        466 }
        467 }, opt_obj);
        468 return count;
        469};
        470
        471
        472/**
        473 * Search an array for the first element that satisfies a given condition and
        474 * return that element.
        475 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        476 * like object over which to iterate.
        477 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        478 * for every element. This function takes 3 arguments (the element, the
        479 * index and the array) and should return a boolean.
        480 * @param {S=} opt_obj An optional "this" context for the function.
        481 * @return {T} The first array element that passes the test, or null if no
        482 * element is found.
        483 * @template T,S
        484 */
        485goog.array.find = function(arr, f, opt_obj) {
        486 var i = goog.array.findIndex(arr, f, opt_obj);
        487 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
        488};
        489
        490
        491/**
        492 * Search an array for the first element that satisfies a given condition and
        493 * return its index.
        494 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        495 * like object over which to iterate.
        496 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        497 * every element. This function
        498 * takes 3 arguments (the element, the index and the array) and should
        499 * return a boolean.
        500 * @param {S=} opt_obj An optional "this" context for the function.
        501 * @return {number} The index of the first array element that passes the test,
        502 * or -1 if no element is found.
        503 * @template T,S
        504 */
        505goog.array.findIndex = function(arr, f, opt_obj) {
        506 var l = arr.length; // must be fixed during loop... see docs
        507 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        508 for (var i = 0; i < l; i++) {
        509 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        510 return i;
        511 }
        512 }
        513 return -1;
        514};
        515
        516
        517/**
        518 * Search an array (in reverse order) for the last element that satisfies a
        519 * given condition and return that element.
        520 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        521 * like object over which to iterate.
        522 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        523 * for every element. This function
        524 * takes 3 arguments (the element, the index and the array) and should
        525 * return a boolean.
        526 * @param {S=} opt_obj An optional "this" context for the function.
        527 * @return {T} The last array element that passes the test, or null if no
        528 * element is found.
        529 * @template T,S
        530 */
        531goog.array.findRight = function(arr, f, opt_obj) {
        532 var i = goog.array.findIndexRight(arr, f, opt_obj);
        533 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
        534};
        535
        536
        537/**
        538 * Search an array (in reverse order) for the last element that satisfies a
        539 * given condition and return its index.
        540 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        541 * like object over which to iterate.
        542 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        543 * for every element. This function
        544 * takes 3 arguments (the element, the index and the array) and should
        545 * return a boolean.
        546 * @param {Object=} opt_obj An optional "this" context for the function.
        547 * @return {number} The index of the last array element that passes the test,
        548 * or -1 if no element is found.
        549 * @template T,S
        550 */
        551goog.array.findIndexRight = function(arr, f, opt_obj) {
        552 var l = arr.length; // must be fixed during loop... see docs
        553 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        554 for (var i = l - 1; i >= 0; i--) {
        555 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        556 return i;
        557 }
        558 }
        559 return -1;
        560};
        561
        562
        563/**
        564 * Whether the array contains the given object.
        565 * @param {goog.array.ArrayLike} arr The array to test for the presence of the
        566 * element.
        567 * @param {*} obj The object for which to test.
        568 * @return {boolean} true if obj is present.
        569 */
        570goog.array.contains = function(arr, obj) {
        571 return goog.array.indexOf(arr, obj) >= 0;
        572};
        573
        574
        575/**
        576 * Whether the array is empty.
        577 * @param {goog.array.ArrayLike} arr The array to test.
        578 * @return {boolean} true if empty.
        579 */
        580goog.array.isEmpty = function(arr) {
        581 return arr.length == 0;
        582};
        583
        584
        585/**
        586 * Clears the array.
        587 * @param {goog.array.ArrayLike} arr Array or array like object to clear.
        588 */
        589goog.array.clear = function(arr) {
        590 // For non real arrays we don't have the magic length so we delete the
        591 // indices.
        592 if (!goog.isArray(arr)) {
        593 for (var i = arr.length - 1; i >= 0; i--) {
        594 delete arr[i];
        595 }
        596 }
        597 arr.length = 0;
        598};
        599
        600
        601/**
        602 * Pushes an item into an array, if it's not already in the array.
        603 * @param {Array.<T>} arr Array into which to insert the item.
        604 * @param {T} obj Value to add.
        605 * @template T
        606 */
        607goog.array.insert = function(arr, obj) {
        608 if (!goog.array.contains(arr, obj)) {
        609 arr.push(obj);
        610 }
        611};
        612
        613
        614/**
        615 * Inserts an object at the given index of the array.
        616 * @param {goog.array.ArrayLike} arr The array to modify.
        617 * @param {*} obj The object to insert.
        618 * @param {number=} opt_i The index at which to insert the object. If omitted,
        619 * treated as 0. A negative index is counted from the end of the array.
        620 */
        621goog.array.insertAt = function(arr, obj, opt_i) {
        622 goog.array.splice(arr, opt_i, 0, obj);
        623};
        624
        625
        626/**
        627 * Inserts at the given index of the array, all elements of another array.
        628 * @param {goog.array.ArrayLike} arr The array to modify.
        629 * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
        630 * @param {number=} opt_i The index at which to insert the object. If omitted,
        631 * treated as 0. A negative index is counted from the end of the array.
        632 */
        633goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
        634 goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
        635};
        636
        637
        638/**
        639 * Inserts an object into an array before a specified object.
        640 * @param {Array.<T>} arr The array to modify.
        641 * @param {T} obj The object to insert.
        642 * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
        643 * is omitted or not found, obj is inserted at the end of the array.
        644 * @template T
        645 */
        646goog.array.insertBefore = function(arr, obj, opt_obj2) {
        647 var i;
        648 if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
        649 arr.push(obj);
        650 } else {
        651 goog.array.insertAt(arr, obj, i);
        652 }
        653};
        654
        655
        656/**
        657 * Removes the first occurrence of a particular value from an array.
        658 * @param {goog.array.ArrayLike} arr Array from which to remove value.
        659 * @param {*} obj Object to remove.
        660 * @return {boolean} True if an element was removed.
        661 */
        662goog.array.remove = function(arr, obj) {
        663 var i = goog.array.indexOf(arr, obj);
        664 var rv;
        665 if ((rv = i >= 0)) {
        666 goog.array.removeAt(arr, i);
        667 }
        668 return rv;
        669};
        670
        671
        672/**
        673 * Removes from an array the element at index i
        674 * @param {goog.array.ArrayLike} arr Array or array like object from which to
        675 * remove value.
        676 * @param {number} i The index to remove.
        677 * @return {boolean} True if an element was removed.
        678 */
        679goog.array.removeAt = function(arr, i) {
        680 goog.asserts.assert(arr.length != null);
        681
        682 // use generic form of splice
        683 // splice returns the removed items and if successful the length of that
        684 // will be 1
        685 return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
        686};
        687
        688
        689/**
        690 * Removes the first value that satisfies the given condition.
        691 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array
        692 * like object over which to iterate.
        693 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        694 * for every element. This function
        695 * takes 3 arguments (the element, the index and the array) and should
        696 * return a boolean.
        697 * @param {S=} opt_obj An optional "this" context for the function.
        698 * @return {boolean} True if an element was removed.
        699 * @template T,S
        700 */
        701goog.array.removeIf = function(arr, f, opt_obj) {
        702 var i = goog.array.findIndex(arr, f, opt_obj);
        703 if (i >= 0) {
        704 goog.array.removeAt(arr, i);
        705 return true;
        706 }
        707 return false;
        708};
        709
        710
        711/**
        712 * Returns a new array that is the result of joining the arguments. If arrays
        713 * are passed then their items are added, however, if non-arrays are passed they
        714 * will be added to the return array as is.
        715 *
        716 * Note that ArrayLike objects will be added as is, rather than having their
        717 * items added.
        718 *
        719 * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
        720 * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
        721 * goog.array.concat([1, 2], null) -> [1, 2, null]
        722 *
        723 * There is bug in all current versions of IE (6, 7 and 8) where arrays created
        724 * in an iframe become corrupted soon (not immediately) after the iframe is
        725 * destroyed. This is common if loading data via goog.net.IframeIo, for example.
        726 * This corruption only affects the concat method which will start throwing
        727 * Catastrophic Errors (#-2147418113).
        728 *
        729 * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
        730 *
        731 * Internally goog.array should use this, so that all methods will continue to
        732 * work on these broken array objects.
        733 *
        734 * @param {...*} var_args Items to concatenate. Arrays will have each item
        735 * added, while primitives and objects will be added as is.
        736 * @return {!Array} The new resultant array.
        737 */
        738goog.array.concat = function(var_args) {
        739 return goog.array.ARRAY_PROTOTYPE_.concat.apply(
        740 goog.array.ARRAY_PROTOTYPE_, arguments);
        741};
        742
        743
        744/**
        745 * Converts an object to an array.
        746 * @param {goog.array.ArrayLike} object The object to convert to an array.
        747 * @return {!Array} The object converted into an array. If object has a
        748 * length property, every property indexed with a non-negative number
        749 * less than length will be included in the result. If object does not
        750 * have a length property, an empty array will be returned.
        751 */
        752goog.array.toArray = function(object) {
        753 var length = object.length;
        754
        755 // If length is not a number the following it false. This case is kept for
        756 // backwards compatibility since there are callers that pass objects that are
        757 // not array like.
        758 if (length > 0) {
        759 var rv = new Array(length);
        760 for (var i = 0; i < length; i++) {
        761 rv[i] = object[i];
        762 }
        763 return rv;
        764 }
        765 return [];
        766};
        767
        768
        769/**
        770 * Does a shallow copy of an array.
        771 * @param {goog.array.ArrayLike} arr Array or array-like object to clone.
        772 * @return {!Array} Clone of the input array.
        773 */
        774goog.array.clone = goog.array.toArray;
        775
        776
        777/**
        778 * Extends an array with another array, element, or "array like" object.
        779 * This function operates 'in-place', it does not create a new Array.
        780 *
        781 * Example:
        782 * var a = [];
        783 * goog.array.extend(a, [0, 1]);
        784 * a; // [0, 1]
        785 * goog.array.extend(a, 2);
        786 * a; // [0, 1, 2]
        787 *
        788 * @param {Array} arr1 The array to modify.
        789 * @param {...*} var_args The elements or arrays of elements to add to arr1.
        790 */
        791goog.array.extend = function(arr1, var_args) {
        792 for (var i = 1; i < arguments.length; i++) {
        793 var arr2 = arguments[i];
        794 // If we have an Array or an Arguments object we can just call push
        795 // directly.
        796 var isArrayLike;
        797 if (goog.isArray(arr2) ||
        798 // Detect Arguments. ES5 says that the [[Class]] of an Arguments object
        799 // is "Arguments" but only V8 and JSC/Safari gets this right. We instead
        800 // detect Arguments by checking for array like and presence of "callee".
        801 (isArrayLike = goog.isArrayLike(arr2)) &&
        802 // The getter for callee throws an exception in strict mode
        803 // according to section 10.6 in ES5 so check for presence instead.
        804 Object.prototype.hasOwnProperty.call(arr2, 'callee')) {
        805 arr1.push.apply(arr1, arr2);
        806 } else if (isArrayLike) {
        807 // Otherwise loop over arr2 to prevent copying the object.
        808 var len1 = arr1.length;
        809 var len2 = arr2.length;
        810 for (var j = 0; j < len2; j++) {
        811 arr1[len1 + j] = arr2[j];
        812 }
        813 } else {
        814 arr1.push(arr2);
        815 }
        816 }
        817};
        818
        819
        820/**
        821 * Adds or removes elements from an array. This is a generic version of Array
        822 * splice. This means that it might work on other objects similar to arrays,
        823 * such as the arguments object.
        824 *
        825 * @param {goog.array.ArrayLike} arr The array to modify.
        826 * @param {number|undefined} index The index at which to start changing the
        827 * array. If not defined, treated as 0.
        828 * @param {number} howMany How many elements to remove (0 means no removal. A
        829 * value below 0 is treated as zero and so is any other non number. Numbers
        830 * are floored).
        831 * @param {...*} var_args Optional, additional elements to insert into the
        832 * array.
        833 * @return {!Array} the removed elements.
        834 */
        835goog.array.splice = function(arr, index, howMany, var_args) {
        836 goog.asserts.assert(arr.length != null);
        837
        838 return goog.array.ARRAY_PROTOTYPE_.splice.apply(
        839 arr, goog.array.slice(arguments, 1));
        840};
        841
        842
        843/**
        844 * Returns a new array from a segment of an array. This is a generic version of
        845 * Array slice. This means that it might work on other objects similar to
        846 * arrays, such as the arguments object.
        847 *
        848 * @param {Array.<T>|goog.array.ArrayLike} arr The array from
        849 * which to copy a segment.
        850 * @param {number} start The index of the first element to copy.
        851 * @param {number=} opt_end The index after the last element to copy.
        852 * @return {!Array.<T>} A new array containing the specified segment of the
        853 * original array.
        854 * @template T
        855 */
        856goog.array.slice = function(arr, start, opt_end) {
        857 goog.asserts.assert(arr.length != null);
        858
        859 // passing 1 arg to slice is not the same as passing 2 where the second is
        860 // null or undefined (in that case the second argument is treated as 0).
        861 // we could use slice on the arguments object and then use apply instead of
        862 // testing the length
        863 if (arguments.length <= 2) {
        864 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
        865 } else {
        866 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
        867 }
        868};
        869
        870
        871/**
        872 * Removes all duplicates from an array (retaining only the first
        873 * occurrence of each array element). This function modifies the
        874 * array in place and doesn't change the order of the non-duplicate items.
        875 *
        876 * For objects, duplicates are identified as having the same unique ID as
        877 * defined by {@link goog.getUid}.
        878 *
        879 * Runtime: N,
        880 * Worstcase space: 2N (no dupes)
        881 *
        882 * @param {goog.array.ArrayLike} arr The array from which to remove duplicates.
        883 * @param {Array=} opt_rv An optional array in which to return the results,
        884 * instead of performing the removal inplace. If specified, the original
        885 * array will remain unchanged.
        886 */
        887goog.array.removeDuplicates = function(arr, opt_rv) {
        888 var returnArray = opt_rv || arr;
        889
        890 var seen = {}, cursorInsert = 0, cursorRead = 0;
        891 while (cursorRead < arr.length) {
        892 var current = arr[cursorRead++];
        893
        894 // Prefix each type with a single character representing the type to
        895 // prevent conflicting keys (e.g. true and 'true').
        896 var key = goog.isObject(current) ?
        897 'o' + goog.getUid(current) :
        898 (typeof current).charAt(0) + current;
        899
        900 if (!Object.prototype.hasOwnProperty.call(seen, key)) {
        901 seen[key] = true;
        902 returnArray[cursorInsert++] = current;
        903 }
        904 }
        905 returnArray.length = cursorInsert;
        906};
        907
        908
        909/**
        910 * Searches the specified array for the specified target using the binary
        911 * search algorithm. If no opt_compareFn is specified, elements are compared
        912 * using <code>goog.array.defaultCompare</code>, which compares the elements
        913 * using the built in < and > operators. This will produce the expected
        914 * behavior for homogeneous arrays of String(s) and Number(s). The array
        915 * specified <b>must</b> be sorted in ascending order (as defined by the
        916 * comparison function). If the array is not sorted, results are undefined.
        917 * If the array contains multiple instances of the specified target value, any
        918 * of these instances may be found.
        919 *
        920 * Runtime: O(log n)
        921 *
        922 * @param {goog.array.ArrayLike} arr The array to be searched.
        923 * @param {*} target The sought value.
        924 * @param {Function=} opt_compareFn Optional comparison function by which the
        925 * array is ordered. Should take 2 arguments to compare, and return a
        926 * negative number, zero, or a positive number depending on whether the
        927 * first argument is less than, equal to, or greater than the second.
        928 * @return {number} Lowest index of the target value if found, otherwise
        929 * (-(insertion point) - 1). The insertion point is where the value should
        930 * be inserted into arr to preserve the sorted property. Return value >= 0
        931 * iff target is found.
        932 */
        933goog.array.binarySearch = function(arr, target, opt_compareFn) {
        934 return goog.array.binarySearch_(arr,
        935 opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
        936 target);
        937};
        938
        939
        940/**
        941 * Selects an index in the specified array using the binary search algorithm.
        942 * The evaluator receives an element and determines whether the desired index
        943 * is before, at, or after it. The evaluator must be consistent (formally,
        944 * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
        945 * must be monotonically non-increasing).
        946 *
        947 * Runtime: O(log n)
        948 *
        949 * @param {goog.array.ArrayLike} arr The array to be searched.
        950 * @param {Function} evaluator Evaluator function that receives 3 arguments
        951 * (the element, the index and the array). Should return a negative number,
        952 * zero, or a positive number depending on whether the desired index is
        953 * before, at, or after the element passed to it.
        954 * @param {Object=} opt_obj The object to be used as the value of 'this'
        955 * within evaluator.
        956 * @return {number} Index of the leftmost element matched by the evaluator, if
        957 * such exists; otherwise (-(insertion point) - 1). The insertion point is
        958 * the index of the first element for which the evaluator returns negative,
        959 * or arr.length if no such element exists. The return value is non-negative
        960 * iff a match is found.
        961 */
        962goog.array.binarySelect = function(arr, evaluator, opt_obj) {
        963 return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
        964 undefined /* opt_target */, opt_obj);
        965};
        966
        967
        968/**
        969 * Implementation of a binary search algorithm which knows how to use both
        970 * comparison functions and evaluators. If an evaluator is provided, will call
        971 * the evaluator with the given optional data object, conforming to the
        972 * interface defined in binarySelect. Otherwise, if a comparison function is
        973 * provided, will call the comparison function against the given data object.
        974 *
        975 * This implementation purposefully does not use goog.bind or goog.partial for
        976 * performance reasons.
        977 *
        978 * Runtime: O(log n)
        979 *
        980 * @param {goog.array.ArrayLike} arr The array to be searched.
        981 * @param {Function} compareFn Either an evaluator or a comparison function,
        982 * as defined by binarySearch and binarySelect above.
        983 * @param {boolean} isEvaluator Whether the function is an evaluator or a
        984 * comparison function.
        985 * @param {*=} opt_target If the function is a comparison function, then this is
        986 * the target to binary search for.
        987 * @param {Object=} opt_selfObj If the function is an evaluator, this is an
        988 * optional this object for the evaluator.
        989 * @return {number} Lowest index of the target value if found, otherwise
        990 * (-(insertion point) - 1). The insertion point is where the value should
        991 * be inserted into arr to preserve the sorted property. Return value >= 0
        992 * iff target is found.
        993 * @private
        994 */
        995goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
        996 opt_selfObj) {
        997 var left = 0; // inclusive
        998 var right = arr.length; // exclusive
        999 var found;
        1000 while (left < right) {
        1001 var middle = (left + right) >> 1;
        1002 var compareResult;
        1003 if (isEvaluator) {
        1004 compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
        1005 } else {
        1006 compareResult = compareFn(opt_target, arr[middle]);
        1007 }
        1008 if (compareResult > 0) {
        1009 left = middle + 1;
        1010 } else {
        1011 right = middle;
        1012 // We are looking for the lowest index so we can't return immediately.
        1013 found = !compareResult;
        1014 }
        1015 }
        1016 // left is the index if found, or the insertion point otherwise.
        1017 // ~left is a shorthand for -left - 1.
        1018 return found ? left : ~left;
        1019};
        1020
        1021
        1022/**
        1023 * Sorts the specified array into ascending order. If no opt_compareFn is
        1024 * specified, elements are compared using
        1025 * <code>goog.array.defaultCompare</code>, which compares the elements using
        1026 * the built in < and > operators. This will produce the expected behavior
        1027 * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
        1028 * but will give unpredictable results for heterogenous lists of strings and
        1029 * numbers with different numbers of digits.
        1030 *
        1031 * This sort is not guaranteed to be stable.
        1032 *
        1033 * Runtime: Same as <code>Array.prototype.sort</code>
        1034 *
        1035 * @param {Array.<T>} arr The array to be sorted.
        1036 * @param {?function(T,T):number=} opt_compareFn Optional comparison
        1037 * function by which the
        1038 * array is to be ordered. Should take 2 arguments to compare, and return a
        1039 * negative number, zero, or a positive number depending on whether the
        1040 * first argument is less than, equal to, or greater than the second.
        1041 * @template T
        1042 */
        1043goog.array.sort = function(arr, opt_compareFn) {
        1044 // TODO(arv): Update type annotation since null is not accepted.
        1045 goog.asserts.assert(arr.length != null);
        1046
        1047 goog.array.ARRAY_PROTOTYPE_.sort.call(
        1048 arr, opt_compareFn || goog.array.defaultCompare);
        1049};
        1050
        1051
        1052/**
        1053 * Sorts the specified array into ascending order in a stable way. If no
        1054 * opt_compareFn is specified, elements are compared using
        1055 * <code>goog.array.defaultCompare</code>, which compares the elements using
        1056 * the built in < and > operators. This will produce the expected behavior
        1057 * for homogeneous arrays of String(s) and Number(s).
        1058 *
        1059 * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
        1060 * O(n) overhead of copying the array twice.
        1061 *
        1062 * @param {Array.<T>} arr The array to be sorted.
        1063 * @param {?function(T, T): number=} opt_compareFn Optional comparison function
        1064 * by which the array is to be ordered. Should take 2 arguments to compare,
        1065 * and return a negative number, zero, or a positive number depending on
        1066 * whether the first argument is less than, equal to, or greater than the
        1067 * second.
        1068 * @template T
        1069 */
        1070goog.array.stableSort = function(arr, opt_compareFn) {
        1071 for (var i = 0; i < arr.length; i++) {
        1072 arr[i] = {index: i, value: arr[i]};
        1073 }
        1074 var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
        1075 function stableCompareFn(obj1, obj2) {
        1076 return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
        1077 };
        1078 goog.array.sort(arr, stableCompareFn);
        1079 for (var i = 0; i < arr.length; i++) {
        1080 arr[i] = arr[i].value;
        1081 }
        1082};
        1083
        1084
        1085/**
        1086 * Sorts an array of objects by the specified object key and compare
        1087 * function. If no compare function is provided, the key values are
        1088 * compared in ascending order using <code>goog.array.defaultCompare</code>.
        1089 * This won't work for keys that get renamed by the compiler. So use
        1090 * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
        1091 * @param {Array.<Object>} arr An array of objects to sort.
        1092 * @param {string} key The object key to sort by.
        1093 * @param {Function=} opt_compareFn The function to use to compare key
        1094 * values.
        1095 */
        1096goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
        1097 var compare = opt_compareFn || goog.array.defaultCompare;
        1098 goog.array.sort(arr, function(a, b) {
        1099 return compare(a[key], b[key]);
        1100 });
        1101};
        1102
        1103
        1104/**
        1105 * Tells if the array is sorted.
        1106 * @param {!Array.<T>} arr The array.
        1107 * @param {?function(T,T):number=} opt_compareFn Function to compare the
        1108 * array elements.
        1109 * Should take 2 arguments to compare, and return a negative number, zero,
        1110 * or a positive number depending on whether the first argument is less
        1111 * than, equal to, or greater than the second.
        1112 * @param {boolean=} opt_strict If true no equal elements are allowed.
        1113 * @return {boolean} Whether the array is sorted.
        1114 * @template T
        1115 */
        1116goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
        1117 var compare = opt_compareFn || goog.array.defaultCompare;
        1118 for (var i = 1; i < arr.length; i++) {
        1119 var compareResult = compare(arr[i - 1], arr[i]);
        1120 if (compareResult > 0 || compareResult == 0 && opt_strict) {
        1121 return false;
        1122 }
        1123 }
        1124 return true;
        1125};
        1126
        1127
        1128/**
        1129 * Compares two arrays for equality. Two arrays are considered equal if they
        1130 * have the same length and their corresponding elements are equal according to
        1131 * the comparison function.
        1132 *
        1133 * @param {goog.array.ArrayLike} arr1 The first array to compare.
        1134 * @param {goog.array.ArrayLike} arr2 The second array to compare.
        1135 * @param {Function=} opt_equalsFn Optional comparison function.
        1136 * Should take 2 arguments to compare, and return true if the arguments
        1137 * are equal. Defaults to {@link goog.array.defaultCompareEquality} which
        1138 * compares the elements using the built-in '===' operator.
        1139 * @return {boolean} Whether the two arrays are equal.
        1140 */
        1141goog.array.equals = function(arr1, arr2, opt_equalsFn) {
        1142 if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
        1143 arr1.length != arr2.length) {
        1144 return false;
        1145 }
        1146 var l = arr1.length;
        1147 var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
        1148 for (var i = 0; i < l; i++) {
        1149 if (!equalsFn(arr1[i], arr2[i])) {
        1150 return false;
        1151 }
        1152 }
        1153 return true;
        1154};
        1155
        1156
        1157/**
        1158 * @deprecated Use {@link goog.array.equals}.
        1159 * @param {goog.array.ArrayLike} arr1 See {@link goog.array.equals}.
        1160 * @param {goog.array.ArrayLike} arr2 See {@link goog.array.equals}.
        1161 * @param {Function=} opt_equalsFn See {@link goog.array.equals}.
        1162 * @return {boolean} See {@link goog.array.equals}.
        1163 */
        1164goog.array.compare = function(arr1, arr2, opt_equalsFn) {
        1165 return goog.array.equals(arr1, arr2, opt_equalsFn);
        1166};
        1167
        1168
        1169/**
        1170 * 3-way array compare function.
        1171 * @param {!goog.array.ArrayLike} arr1 The first array to compare.
        1172 * @param {!goog.array.ArrayLike} arr2 The second array to compare.
        1173 * @param {?function(?, ?): number=} opt_compareFn Optional comparison function
        1174 * by which the array is to be ordered. Should take 2 arguments to compare,
        1175 * and return a negative number, zero, or a positive number depending on
        1176 * whether the first argument is less than, equal to, or greater than the
        1177 * second.
        1178 * @return {number} Negative number, zero, or a positive number depending on
        1179 * whether the first argument is less than, equal to, or greater than the
        1180 * second.
        1181 */
        1182goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
        1183 var compare = opt_compareFn || goog.array.defaultCompare;
        1184 var l = Math.min(arr1.length, arr2.length);
        1185 for (var i = 0; i < l; i++) {
        1186 var result = compare(arr1[i], arr2[i]);
        1187 if (result != 0) {
        1188 return result;
        1189 }
        1190 }
        1191 return goog.array.defaultCompare(arr1.length, arr2.length);
        1192};
        1193
        1194
        1195/**
        1196 * Compares its two arguments for order, using the built in < and >
        1197 * operators.
        1198 * @param {*} a The first object to be compared.
        1199 * @param {*} b The second object to be compared.
        1200 * @return {number} A negative number, zero, or a positive number as the first
        1201 * argument is less than, equal to, or greater than the second.
        1202 */
        1203goog.array.defaultCompare = function(a, b) {
        1204 return a > b ? 1 : a < b ? -1 : 0;
        1205};
        1206
        1207
        1208/**
        1209 * Compares its two arguments for equality, using the built in === operator.
        1210 * @param {*} a The first object to compare.
        1211 * @param {*} b The second object to compare.
        1212 * @return {boolean} True if the two arguments are equal, false otherwise.
        1213 */
        1214goog.array.defaultCompareEquality = function(a, b) {
        1215 return a === b;
        1216};
        1217
        1218
        1219/**
        1220 * Inserts a value into a sorted array. The array is not modified if the
        1221 * value is already present.
        1222 * @param {Array.<T>} array The array to modify.
        1223 * @param {T} value The object to insert.
        1224 * @param {?function(T,T):number=} opt_compareFn Optional comparison function by
        1225 * which the
        1226 * array is ordered. Should take 2 arguments to compare, and return a
        1227 * negative number, zero, or a positive number depending on whether the
        1228 * first argument is less than, equal to, or greater than the second.
        1229 * @return {boolean} True if an element was inserted.
        1230 * @template T
        1231 */
        1232goog.array.binaryInsert = function(array, value, opt_compareFn) {
        1233 var index = goog.array.binarySearch(array, value, opt_compareFn);
        1234 if (index < 0) {
        1235 goog.array.insertAt(array, value, -(index + 1));
        1236 return true;
        1237 }
        1238 return false;
        1239};
        1240
        1241
        1242/**
        1243 * Removes a value from a sorted array.
        1244 * @param {Array} array The array to modify.
        1245 * @param {*} value The object to remove.
        1246 * @param {Function=} opt_compareFn Optional comparison function by which the
        1247 * array is ordered. Should take 2 arguments to compare, and return a
        1248 * negative number, zero, or a positive number depending on whether the
        1249 * first argument is less than, equal to, or greater than the second.
        1250 * @return {boolean} True if an element was removed.
        1251 */
        1252goog.array.binaryRemove = function(array, value, opt_compareFn) {
        1253 var index = goog.array.binarySearch(array, value, opt_compareFn);
        1254 return (index >= 0) ? goog.array.removeAt(array, index) : false;
        1255};
        1256
        1257
        1258/**
        1259 * Splits an array into disjoint buckets according to a splitting function.
        1260 * @param {Array.<T>} array The array.
        1261 * @param {function(this:S, T,number,Array.<T>):?} sorter Function to call for
        1262 * every element. This takes 3 arguments (the element, the index and the
        1263 * array) and must return a valid object key (a string, number, etc), or
        1264 * undefined, if that object should not be placed in a bucket.
        1265 * @param {S=} opt_obj The object to be used as the value of 'this' within
        1266 * sorter.
        1267 * @return {!Object} An object, with keys being all of the unique return values
        1268 * of sorter, and values being arrays containing the items for
        1269 * which the splitter returned that key.
        1270 * @template T,S
        1271 */
        1272goog.array.bucket = function(array, sorter, opt_obj) {
        1273 var buckets = {};
        1274
        1275 for (var i = 0; i < array.length; i++) {
        1276 var value = array[i];
        1277 var key = sorter.call(opt_obj, value, i, array);
        1278 if (goog.isDef(key)) {
        1279 // Push the value to the right bucket, creating it if necessary.
        1280 var bucket = buckets[key] || (buckets[key] = []);
        1281 bucket.push(value);
        1282 }
        1283 }
        1284
        1285 return buckets;
        1286};
        1287
        1288
        1289/**
        1290 * Creates a new object built from the provided array and the key-generation
        1291 * function.
        1292 * @param {Array.<T>|goog.array.ArrayLike} arr Array or array like object over
        1293 * which to iterate whose elements will be the values in the new object.
        1294 * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
        1295 * call for every element. This function takes 3 arguments (the element, the
        1296 * index and the array) and should return a string that will be used as the
        1297 * key for the element in the new object. If the function returns the same
        1298 * key for more than one element, the value for that key is
        1299 * implementation-defined.
        1300 * @param {S=} opt_obj The object to be used as the value of 'this'
        1301 * within keyFunc.
        1302 * @return {!Object.<T>} The new object.
        1303 * @template T,S
        1304 */
        1305goog.array.toObject = function(arr, keyFunc, opt_obj) {
        1306 var ret = {};
        1307 goog.array.forEach(arr, function(element, index) {
        1308 ret[keyFunc.call(opt_obj, element, index, arr)] = element;
        1309 });
        1310 return ret;
        1311};
        1312
        1313
        1314/**
        1315 * Creates a range of numbers in an arithmetic progression.
        1316 *
        1317 * Range takes 1, 2, or 3 arguments:
        1318 * <pre>
        1319 * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
        1320 * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
        1321 * range(-2, -5, -1) produces [-2, -3, -4]
        1322 * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
        1323 * </pre>
        1324 *
        1325 * @param {number} startOrEnd The starting value of the range if an end argument
        1326 * is provided. Otherwise, the start value is 0, and this is the end value.
        1327 * @param {number=} opt_end The optional end value of the range.
        1328 * @param {number=} opt_step The step size between range values. Defaults to 1
        1329 * if opt_step is undefined or 0.
        1330 * @return {!Array.<number>} An array of numbers for the requested range. May be
        1331 * an empty array if adding the step would not converge toward the end
        1332 * value.
        1333 */
        1334goog.array.range = function(startOrEnd, opt_end, opt_step) {
        1335 var array = [];
        1336 var start = 0;
        1337 var end = startOrEnd;
        1338 var step = opt_step || 1;
        1339 if (opt_end !== undefined) {
        1340 start = startOrEnd;
        1341 end = opt_end;
        1342 }
        1343
        1344 if (step * (end - start) < 0) {
        1345 // Sign mismatch: start + step will never reach the end value.
        1346 return [];
        1347 }
        1348
        1349 if (step > 0) {
        1350 for (var i = start; i < end; i += step) {
        1351 array.push(i);
        1352 }
        1353 } else {
        1354 for (var i = start; i > end; i += step) {
        1355 array.push(i);
        1356 }
        1357 }
        1358 return array;
        1359};
        1360
        1361
        1362/**
        1363 * Returns an array consisting of the given value repeated N times.
        1364 *
        1365 * @param {*} value The value to repeat.
        1366 * @param {number} n The repeat count.
        1367 * @return {!Array} An array with the repeated value.
        1368 */
        1369goog.array.repeat = function(value, n) {
        1370 var array = [];
        1371 for (var i = 0; i < n; i++) {
        1372 array[i] = value;
        1373 }
        1374 return array;
        1375};
        1376
        1377
        1378/**
        1379 * Returns an array consisting of every argument with all arrays
        1380 * expanded in-place recursively.
        1381 *
        1382 * @param {...*} var_args The values to flatten.
        1383 * @return {!Array} An array containing the flattened values.
        1384 */
        1385goog.array.flatten = function(var_args) {
        1386 var result = [];
        1387 for (var i = 0; i < arguments.length; i++) {
        1388 var element = arguments[i];
        1389 if (goog.isArray(element)) {
        1390 result.push.apply(result, goog.array.flatten.apply(null, element));
        1391 } else {
        1392 result.push(element);
        1393 }
        1394 }
        1395 return result;
        1396};
        1397
        1398
        1399/**
        1400 * Rotates an array in-place. After calling this method, the element at
        1401 * index i will be the element previously at index (i - n) %
        1402 * array.length, for all values of i between 0 and array.length - 1,
        1403 * inclusive.
        1404 *
        1405 * For example, suppose list comprises [t, a, n, k, s]. After invoking
        1406 * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
        1407 *
        1408 * @param {!Array.<T>} array The array to rotate.
        1409 * @param {number} n The amount to rotate.
        1410 * @return {!Array.<T>} The array.
        1411 * @template T
        1412 */
        1413goog.array.rotate = function(array, n) {
        1414 goog.asserts.assert(array.length != null);
        1415
        1416 if (array.length) {
        1417 n %= array.length;
        1418 if (n > 0) {
        1419 goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
        1420 } else if (n < 0) {
        1421 goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
        1422 }
        1423 }
        1424 return array;
        1425};
        1426
        1427
        1428/**
        1429 * Creates a new array for which the element at position i is an array of the
        1430 * ith element of the provided arrays. The returned array will only be as long
        1431 * as the shortest array provided; additional values are ignored. For example,
        1432 * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
        1433 *
        1434 * This is similar to the zip() function in Python. See {@link
        1435 * http://docs.python.org/library/functions.html#zip}
        1436 *
        1437 * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
        1438 * @return {!Array.<!Array>} A new array of arrays created from provided arrays.
        1439 */
        1440goog.array.zip = function(var_args) {
        1441 if (!arguments.length) {
        1442 return [];
        1443 }
        1444 var result = [];
        1445 for (var i = 0; true; i++) {
        1446 var value = [];
        1447 for (var j = 0; j < arguments.length; j++) {
        1448 var arr = arguments[j];
        1449 // If i is larger than the array length, this is the shortest array.
        1450 if (i >= arr.length) {
        1451 return result;
        1452 }
        1453 value.push(arr[i]);
        1454 }
        1455 result.push(value);
        1456 }
        1457};
        1458
        1459
        1460/**
        1461 * Shuffles the values in the specified array using the Fisher-Yates in-place
        1462 * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
        1463 * and so resets the state of that random number generator. Similarly, may reset
        1464 * the state of the any other specified random number generator.
        1465 *
        1466 * Runtime: O(n)
        1467 *
        1468 * @param {!Array} arr The array to be shuffled.
        1469 * @param {function():number=} opt_randFn Optional random function to use for
        1470 * shuffling.
        1471 * Takes no arguments, and returns a random number on the interval [0, 1).
        1472 * Defaults to Math.random() using JavaScript's built-in Math library.
        1473 */
        1474goog.array.shuffle = function(arr, opt_randFn) {
        1475 var randFn = opt_randFn || Math.random;
        1476
        1477 for (var i = arr.length - 1; i > 0; i--) {
        1478 // Choose a random array index in [0, i] (inclusive with i).
        1479 var j = Math.floor(randFn() * (i + 1));
        1480
        1481 var tmp = arr[i];
        1482 arr[i] = arr[j];
        1483 arr[j] = tmp;
        1484 }
        1485};
        \ No newline at end of file +array.js

        lib/goog/array/array.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for manipulating arrays.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 */
        20
        21
        22goog.provide('goog.array');
        23goog.provide('goog.array.ArrayLike');
        24
        25goog.require('goog.asserts');
        26
        27
        28/**
        29 * @define {boolean} NATIVE_ARRAY_PROTOTYPES indicates whether the code should
        30 * rely on Array.prototype functions, if available.
        31 *
        32 * The Array.prototype functions can be defined by external libraries like
        33 * Prototype and setting this flag to false forces closure to use its own
        34 * goog.array implementation.
        35 *
        36 * If your javascript can be loaded by a third party site and you are wary about
        37 * relying on the prototype functions, specify
        38 * "--define goog.NATIVE_ARRAY_PROTOTYPES=false" to the JSCompiler.
        39 *
        40 * Setting goog.TRUSTED_SITE to false will automatically set
        41 * NATIVE_ARRAY_PROTOTYPES to false.
        42 */
        43goog.define('goog.NATIVE_ARRAY_PROTOTYPES', goog.TRUSTED_SITE);
        44
        45
        46/**
        47 * @define {boolean} If true, JSCompiler will use the native implementation of
        48 * array functions where appropriate (e.g., {@code Array#filter}) and remove the
        49 * unused pure JS implementation.
        50 */
        51goog.define('goog.array.ASSUME_NATIVE_FUNCTIONS', false);
        52
        53
        54/**
        55 * @typedef {Array|NodeList|Arguments|{length: number}}
        56 */
        57goog.array.ArrayLike;
        58
        59
        60/**
        61 * Returns the last element in an array without removing it.
        62 * Same as goog.array.last.
        63 * @param {Array<T>|goog.array.ArrayLike} array The array.
        64 * @return {T} Last item in array.
        65 * @template T
        66 */
        67goog.array.peek = function(array) {
        68 return array[array.length - 1];
        69};
        70
        71
        72/**
        73 * Returns the last element in an array without removing it.
        74 * Same as goog.array.peek.
        75 * @param {Array<T>|goog.array.ArrayLike} array The array.
        76 * @return {T} Last item in array.
        77 * @template T
        78 */
        79goog.array.last = goog.array.peek;
        80
        81
        82/**
        83 * Reference to the original {@code Array.prototype}.
        84 * @private
        85 */
        86goog.array.ARRAY_PROTOTYPE_ = Array.prototype;
        87
        88
        89// NOTE(arv): Since most of the array functions are generic it allows you to
        90// pass an array-like object. Strings have a length and are considered array-
        91// like. However, the 'in' operator does not work on strings so we cannot just
        92// use the array path even if the browser supports indexing into strings. We
        93// therefore end up splitting the string.
        94
        95
        96/**
        97 * Returns the index of the first element of an array with a specified value, or
        98 * -1 if the element is not present in the array.
        99 *
        100 * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof}
        101 *
        102 * @param {Array<T>|goog.array.ArrayLike} arr The array to be searched.
        103 * @param {T} obj The object for which we are searching.
        104 * @param {number=} opt_fromIndex The index at which to start the search. If
        105 * omitted the search starts at index 0.
        106 * @return {number} The index of the first matching array element.
        107 * @template T
        108 */
        109goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        110 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        111 goog.array.ARRAY_PROTOTYPE_.indexOf) ?
        112 function(arr, obj, opt_fromIndex) {
        113 goog.asserts.assert(arr.length != null);
        114
        115 return goog.array.ARRAY_PROTOTYPE_.indexOf.call(arr, obj, opt_fromIndex);
        116 } :
        117 function(arr, obj, opt_fromIndex) {
        118 var fromIndex = opt_fromIndex == null ?
        119 0 : (opt_fromIndex < 0 ?
        120 Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
        121
        122 if (goog.isString(arr)) {
        123 // Array.prototype.indexOf uses === so only strings should be found.
        124 if (!goog.isString(obj) || obj.length != 1) {
        125 return -1;
        126 }
        127 return arr.indexOf(obj, fromIndex);
        128 }
        129
        130 for (var i = fromIndex; i < arr.length; i++) {
        131 if (i in arr && arr[i] === obj)
        132 return i;
        133 }
        134 return -1;
        135 };
        136
        137
        138/**
        139 * Returns the index of the last element of an array with a specified value, or
        140 * -1 if the element is not present in the array.
        141 *
        142 * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof}
        143 *
        144 * @param {!Array<T>|!goog.array.ArrayLike} arr The array to be searched.
        145 * @param {T} obj The object for which we are searching.
        146 * @param {?number=} opt_fromIndex The index at which to start the search. If
        147 * omitted the search starts at the end of the array.
        148 * @return {number} The index of the last matching array element.
        149 * @template T
        150 */
        151goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES &&
        152 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        153 goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ?
        154 function(arr, obj, opt_fromIndex) {
        155 goog.asserts.assert(arr.length != null);
        156
        157 // Firefox treats undefined and null as 0 in the fromIndex argument which
        158 // leads it to always return -1
        159 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
        160 return goog.array.ARRAY_PROTOTYPE_.lastIndexOf.call(arr, obj, fromIndex);
        161 } :
        162 function(arr, obj, opt_fromIndex) {
        163 var fromIndex = opt_fromIndex == null ? arr.length - 1 : opt_fromIndex;
        164
        165 if (fromIndex < 0) {
        166 fromIndex = Math.max(0, arr.length + fromIndex);
        167 }
        168
        169 if (goog.isString(arr)) {
        170 // Array.prototype.lastIndexOf uses === so only strings should be found.
        171 if (!goog.isString(obj) || obj.length != 1) {
        172 return -1;
        173 }
        174 return arr.lastIndexOf(obj, fromIndex);
        175 }
        176
        177 for (var i = fromIndex; i >= 0; i--) {
        178 if (i in arr && arr[i] === obj)
        179 return i;
        180 }
        181 return -1;
        182 };
        183
        184
        185/**
        186 * Calls a function for each element in an array. Skips holes in the array.
        187 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
        188 *
        189 * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
        190 * which to iterate.
        191 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
        192 * element. This function takes 3 arguments (the element, the index and the
        193 * array). The return value is ignored.
        194 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
        195 * @template T,S
        196 */
        197goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES &&
        198 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        199 goog.array.ARRAY_PROTOTYPE_.forEach) ?
        200 function(arr, f, opt_obj) {
        201 goog.asserts.assert(arr.length != null);
        202
        203 goog.array.ARRAY_PROTOTYPE_.forEach.call(arr, f, opt_obj);
        204 } :
        205 function(arr, f, opt_obj) {
        206 var l = arr.length; // must be fixed during loop... see docs
        207 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        208 for (var i = 0; i < l; i++) {
        209 if (i in arr2) {
        210 f.call(opt_obj, arr2[i], i, arr);
        211 }
        212 }
        213 };
        214
        215
        216/**
        217 * Calls a function for each element in an array, starting from the last
        218 * element rather than the first.
        219 *
        220 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        221 * like object over which to iterate.
        222 * @param {?function(this: S, T, number, ?): ?} f The function to call for every
        223 * element. This function
        224 * takes 3 arguments (the element, the index and the array). The return
        225 * value is ignored.
        226 * @param {S=} opt_obj The object to be used as the value of 'this'
        227 * within f.
        228 * @template T,S
        229 */
        230goog.array.forEachRight = function(arr, f, opt_obj) {
        231 var l = arr.length; // must be fixed during loop... see docs
        232 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        233 for (var i = l - 1; i >= 0; --i) {
        234 if (i in arr2) {
        235 f.call(opt_obj, arr2[i], i, arr);
        236 }
        237 }
        238};
        239
        240
        241/**
        242 * Calls a function for each element in an array, and if the function returns
        243 * true adds the element to a new array.
        244 *
        245 * See {@link http://tinyurl.com/developer-mozilla-org-array-filter}
        246 *
        247 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        248 * like object over which to iterate.
        249 * @param {?function(this:S, T, number, ?):boolean} f The function to call for
        250 * every element. This function
        251 * takes 3 arguments (the element, the index and the array) and must
        252 * return a Boolean. If the return value is true the element is added to the
        253 * result array. If it is false the element is not included.
        254 * @param {S=} opt_obj The object to be used as the value of 'this'
        255 * within f.
        256 * @return {!Array<T>} a new array in which only elements that passed the test
        257 * are present.
        258 * @template T,S
        259 */
        260goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES &&
        261 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        262 goog.array.ARRAY_PROTOTYPE_.filter) ?
        263 function(arr, f, opt_obj) {
        264 goog.asserts.assert(arr.length != null);
        265
        266 return goog.array.ARRAY_PROTOTYPE_.filter.call(arr, f, opt_obj);
        267 } :
        268 function(arr, f, opt_obj) {
        269 var l = arr.length; // must be fixed during loop... see docs
        270 var res = [];
        271 var resLength = 0;
        272 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        273 for (var i = 0; i < l; i++) {
        274 if (i in arr2) {
        275 var val = arr2[i]; // in case f mutates arr2
        276 if (f.call(opt_obj, val, i, arr)) {
        277 res[resLength++] = val;
        278 }
        279 }
        280 }
        281 return res;
        282 };
        283
        284
        285/**
        286 * Calls a function for each element in an array and inserts the result into a
        287 * new array.
        288 *
        289 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
        290 *
        291 * @param {Array<VALUE>|goog.array.ArrayLike} arr Array or array like object
        292 * over which to iterate.
        293 * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call
        294 * for every element. This function takes 3 arguments (the element,
        295 * the index and the array) and should return something. The result will be
        296 * inserted into a new array.
        297 * @param {THIS=} opt_obj The object to be used as the value of 'this' within f.
        298 * @return {!Array<RESULT>} a new array with the results from f.
        299 * @template THIS, VALUE, RESULT
        300 */
        301goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES &&
        302 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        303 goog.array.ARRAY_PROTOTYPE_.map) ?
        304 function(arr, f, opt_obj) {
        305 goog.asserts.assert(arr.length != null);
        306
        307 return goog.array.ARRAY_PROTOTYPE_.map.call(arr, f, opt_obj);
        308 } :
        309 function(arr, f, opt_obj) {
        310 var l = arr.length; // must be fixed during loop... see docs
        311 var res = new Array(l);
        312 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        313 for (var i = 0; i < l; i++) {
        314 if (i in arr2) {
        315 res[i] = f.call(opt_obj, arr2[i], i, arr);
        316 }
        317 }
        318 return res;
        319 };
        320
        321
        322/**
        323 * Passes every element of an array into a function and accumulates the result.
        324 *
        325 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
        326 *
        327 * For example:
        328 * var a = [1, 2, 3, 4];
        329 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
        330 * returns 10
        331 *
        332 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        333 * like object over which to iterate.
        334 * @param {function(this:S, R, T, number, ?) : R} f The function to call for
        335 * every element. This function
        336 * takes 4 arguments (the function's previous result or the initial value,
        337 * the value of the current array element, the current array index, and the
        338 * array itself)
        339 * function(previousValue, currentValue, index, array).
        340 * @param {?} val The initial value to pass into the function on the first call.
        341 * @param {S=} opt_obj The object to be used as the value of 'this'
        342 * within f.
        343 * @return {R} Result of evaluating f repeatedly across the values of the array.
        344 * @template T,S,R
        345 */
        346goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES &&
        347 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        348 goog.array.ARRAY_PROTOTYPE_.reduce) ?
        349 function(arr, f, val, opt_obj) {
        350 goog.asserts.assert(arr.length != null);
        351 if (opt_obj) {
        352 f = goog.bind(f, opt_obj);
        353 }
        354 return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val);
        355 } :
        356 function(arr, f, val, opt_obj) {
        357 var rval = val;
        358 goog.array.forEach(arr, function(val, index) {
        359 rval = f.call(opt_obj, rval, val, index, arr);
        360 });
        361 return rval;
        362 };
        363
        364
        365/**
        366 * Passes every element of an array into a function and accumulates the result,
        367 * starting from the last element and working towards the first.
        368 *
        369 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduceright}
        370 *
        371 * For example:
        372 * var a = ['a', 'b', 'c'];
        373 * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, '');
        374 * returns 'cba'
        375 *
        376 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        377 * like object over which to iterate.
        378 * @param {?function(this:S, R, T, number, ?) : R} f The function to call for
        379 * every element. This function
        380 * takes 4 arguments (the function's previous result or the initial value,
        381 * the value of the current array element, the current array index, and the
        382 * array itself)
        383 * function(previousValue, currentValue, index, array).
        384 * @param {?} val The initial value to pass into the function on the first call.
        385 * @param {S=} opt_obj The object to be used as the value of 'this'
        386 * within f.
        387 * @return {R} Object returned as a result of evaluating f repeatedly across the
        388 * values of the array.
        389 * @template T,S,R
        390 */
        391goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES &&
        392 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        393 goog.array.ARRAY_PROTOTYPE_.reduceRight) ?
        394 function(arr, f, val, opt_obj) {
        395 goog.asserts.assert(arr.length != null);
        396 if (opt_obj) {
        397 f = goog.bind(f, opt_obj);
        398 }
        399 return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val);
        400 } :
        401 function(arr, f, val, opt_obj) {
        402 var rval = val;
        403 goog.array.forEachRight(arr, function(val, index) {
        404 rval = f.call(opt_obj, rval, val, index, arr);
        405 });
        406 return rval;
        407 };
        408
        409
        410/**
        411 * Calls f for each element of an array. If any call returns true, some()
        412 * returns true (without checking the remaining elements). If all calls
        413 * return false, some() returns false.
        414 *
        415 * See {@link http://tinyurl.com/developer-mozilla-org-array-some}
        416 *
        417 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        418 * like object over which to iterate.
        419 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        420 * for every element. This function takes 3 arguments (the element, the
        421 * index and the array) and should return a boolean.
        422 * @param {S=} opt_obj The object to be used as the value of 'this'
        423 * within f.
        424 * @return {boolean} true if any element passes the test.
        425 * @template T,S
        426 */
        427goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES &&
        428 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        429 goog.array.ARRAY_PROTOTYPE_.some) ?
        430 function(arr, f, opt_obj) {
        431 goog.asserts.assert(arr.length != null);
        432
        433 return goog.array.ARRAY_PROTOTYPE_.some.call(arr, f, opt_obj);
        434 } :
        435 function(arr, f, opt_obj) {
        436 var l = arr.length; // must be fixed during loop... see docs
        437 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        438 for (var i = 0; i < l; i++) {
        439 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        440 return true;
        441 }
        442 }
        443 return false;
        444 };
        445
        446
        447/**
        448 * Call f for each element of an array. If all calls return true, every()
        449 * returns true. If any call returns false, every() returns false and
        450 * does not continue to check the remaining elements.
        451 *
        452 * See {@link http://tinyurl.com/developer-mozilla-org-array-every}
        453 *
        454 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        455 * like object over which to iterate.
        456 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        457 * for every element. This function takes 3 arguments (the element, the
        458 * index and the array) and should return a boolean.
        459 * @param {S=} opt_obj The object to be used as the value of 'this'
        460 * within f.
        461 * @return {boolean} false if any element fails the test.
        462 * @template T,S
        463 */
        464goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES &&
        465 (goog.array.ASSUME_NATIVE_FUNCTIONS ||
        466 goog.array.ARRAY_PROTOTYPE_.every) ?
        467 function(arr, f, opt_obj) {
        468 goog.asserts.assert(arr.length != null);
        469
        470 return goog.array.ARRAY_PROTOTYPE_.every.call(arr, f, opt_obj);
        471 } :
        472 function(arr, f, opt_obj) {
        473 var l = arr.length; // must be fixed during loop... see docs
        474 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        475 for (var i = 0; i < l; i++) {
        476 if (i in arr2 && !f.call(opt_obj, arr2[i], i, arr)) {
        477 return false;
        478 }
        479 }
        480 return true;
        481 };
        482
        483
        484/**
        485 * Counts the array elements that fulfill the predicate, i.e. for which the
        486 * callback function returns true. Skips holes in the array.
        487 *
        488 * @param {!(Array<T>|goog.array.ArrayLike)} arr Array or array like object
        489 * over which to iterate.
        490 * @param {function(this: S, T, number, ?): boolean} f The function to call for
        491 * every element. Takes 3 arguments (the element, the index and the array).
        492 * @param {S=} opt_obj The object to be used as the value of 'this' within f.
        493 * @return {number} The number of the matching elements.
        494 * @template T,S
        495 */
        496goog.array.count = function(arr, f, opt_obj) {
        497 var count = 0;
        498 goog.array.forEach(arr, function(element, index, arr) {
        499 if (f.call(opt_obj, element, index, arr)) {
        500 ++count;
        501 }
        502 }, opt_obj);
        503 return count;
        504};
        505
        506
        507/**
        508 * Search an array for the first element that satisfies a given condition and
        509 * return that element.
        510 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        511 * like object over which to iterate.
        512 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        513 * for every element. This function takes 3 arguments (the element, the
        514 * index and the array) and should return a boolean.
        515 * @param {S=} opt_obj An optional "this" context for the function.
        516 * @return {T|null} The first array element that passes the test, or null if no
        517 * element is found.
        518 * @template T,S
        519 */
        520goog.array.find = function(arr, f, opt_obj) {
        521 var i = goog.array.findIndex(arr, f, opt_obj);
        522 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
        523};
        524
        525
        526/**
        527 * Search an array for the first element that satisfies a given condition and
        528 * return its index.
        529 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        530 * like object over which to iterate.
        531 * @param {?function(this:S, T, number, ?) : boolean} f The function to call for
        532 * every element. This function
        533 * takes 3 arguments (the element, the index and the array) and should
        534 * return a boolean.
        535 * @param {S=} opt_obj An optional "this" context for the function.
        536 * @return {number} The index of the first array element that passes the test,
        537 * or -1 if no element is found.
        538 * @template T,S
        539 */
        540goog.array.findIndex = function(arr, f, opt_obj) {
        541 var l = arr.length; // must be fixed during loop... see docs
        542 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        543 for (var i = 0; i < l; i++) {
        544 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        545 return i;
        546 }
        547 }
        548 return -1;
        549};
        550
        551
        552/**
        553 * Search an array (in reverse order) for the last element that satisfies a
        554 * given condition and return that element.
        555 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        556 * like object over which to iterate.
        557 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        558 * for every element. This function
        559 * takes 3 arguments (the element, the index and the array) and should
        560 * return a boolean.
        561 * @param {S=} opt_obj An optional "this" context for the function.
        562 * @return {T|null} The last array element that passes the test, or null if no
        563 * element is found.
        564 * @template T,S
        565 */
        566goog.array.findRight = function(arr, f, opt_obj) {
        567 var i = goog.array.findIndexRight(arr, f, opt_obj);
        568 return i < 0 ? null : goog.isString(arr) ? arr.charAt(i) : arr[i];
        569};
        570
        571
        572/**
        573 * Search an array (in reverse order) for the last element that satisfies a
        574 * given condition and return its index.
        575 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        576 * like object over which to iterate.
        577 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        578 * for every element. This function
        579 * takes 3 arguments (the element, the index and the array) and should
        580 * return a boolean.
        581 * @param {S=} opt_obj An optional "this" context for the function.
        582 * @return {number} The index of the last array element that passes the test,
        583 * or -1 if no element is found.
        584 * @template T,S
        585 */
        586goog.array.findIndexRight = function(arr, f, opt_obj) {
        587 var l = arr.length; // must be fixed during loop... see docs
        588 var arr2 = goog.isString(arr) ? arr.split('') : arr;
        589 for (var i = l - 1; i >= 0; i--) {
        590 if (i in arr2 && f.call(opt_obj, arr2[i], i, arr)) {
        591 return i;
        592 }
        593 }
        594 return -1;
        595};
        596
        597
        598/**
        599 * Whether the array contains the given object.
        600 * @param {goog.array.ArrayLike} arr The array to test for the presence of the
        601 * element.
        602 * @param {*} obj The object for which to test.
        603 * @return {boolean} true if obj is present.
        604 */
        605goog.array.contains = function(arr, obj) {
        606 return goog.array.indexOf(arr, obj) >= 0;
        607};
        608
        609
        610/**
        611 * Whether the array is empty.
        612 * @param {goog.array.ArrayLike} arr The array to test.
        613 * @return {boolean} true if empty.
        614 */
        615goog.array.isEmpty = function(arr) {
        616 return arr.length == 0;
        617};
        618
        619
        620/**
        621 * Clears the array.
        622 * @param {goog.array.ArrayLike} arr Array or array like object to clear.
        623 */
        624goog.array.clear = function(arr) {
        625 // For non real arrays we don't have the magic length so we delete the
        626 // indices.
        627 if (!goog.isArray(arr)) {
        628 for (var i = arr.length - 1; i >= 0; i--) {
        629 delete arr[i];
        630 }
        631 }
        632 arr.length = 0;
        633};
        634
        635
        636/**
        637 * Pushes an item into an array, if it's not already in the array.
        638 * @param {Array<T>} arr Array into which to insert the item.
        639 * @param {T} obj Value to add.
        640 * @template T
        641 */
        642goog.array.insert = function(arr, obj) {
        643 if (!goog.array.contains(arr, obj)) {
        644 arr.push(obj);
        645 }
        646};
        647
        648
        649/**
        650 * Inserts an object at the given index of the array.
        651 * @param {goog.array.ArrayLike} arr The array to modify.
        652 * @param {*} obj The object to insert.
        653 * @param {number=} opt_i The index at which to insert the object. If omitted,
        654 * treated as 0. A negative index is counted from the end of the array.
        655 */
        656goog.array.insertAt = function(arr, obj, opt_i) {
        657 goog.array.splice(arr, opt_i, 0, obj);
        658};
        659
        660
        661/**
        662 * Inserts at the given index of the array, all elements of another array.
        663 * @param {goog.array.ArrayLike} arr The array to modify.
        664 * @param {goog.array.ArrayLike} elementsToAdd The array of elements to add.
        665 * @param {number=} opt_i The index at which to insert the object. If omitted,
        666 * treated as 0. A negative index is counted from the end of the array.
        667 */
        668goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) {
        669 goog.partial(goog.array.splice, arr, opt_i, 0).apply(null, elementsToAdd);
        670};
        671
        672
        673/**
        674 * Inserts an object into an array before a specified object.
        675 * @param {Array<T>} arr The array to modify.
        676 * @param {T} obj The object to insert.
        677 * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2
        678 * is omitted or not found, obj is inserted at the end of the array.
        679 * @template T
        680 */
        681goog.array.insertBefore = function(arr, obj, opt_obj2) {
        682 var i;
        683 if (arguments.length == 2 || (i = goog.array.indexOf(arr, opt_obj2)) < 0) {
        684 arr.push(obj);
        685 } else {
        686 goog.array.insertAt(arr, obj, i);
        687 }
        688};
        689
        690
        691/**
        692 * Removes the first occurrence of a particular value from an array.
        693 * @param {Array<T>|goog.array.ArrayLike} arr Array from which to remove
        694 * value.
        695 * @param {T} obj Object to remove.
        696 * @return {boolean} True if an element was removed.
        697 * @template T
        698 */
        699goog.array.remove = function(arr, obj) {
        700 var i = goog.array.indexOf(arr, obj);
        701 var rv;
        702 if ((rv = i >= 0)) {
        703 goog.array.removeAt(arr, i);
        704 }
        705 return rv;
        706};
        707
        708
        709/**
        710 * Removes from an array the element at index i
        711 * @param {goog.array.ArrayLike} arr Array or array like object from which to
        712 * remove value.
        713 * @param {number} i The index to remove.
        714 * @return {boolean} True if an element was removed.
        715 */
        716goog.array.removeAt = function(arr, i) {
        717 goog.asserts.assert(arr.length != null);
        718
        719 // use generic form of splice
        720 // splice returns the removed items and if successful the length of that
        721 // will be 1
        722 return goog.array.ARRAY_PROTOTYPE_.splice.call(arr, i, 1).length == 1;
        723};
        724
        725
        726/**
        727 * Removes the first value that satisfies the given condition.
        728 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        729 * like object over which to iterate.
        730 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        731 * for every element. This function
        732 * takes 3 arguments (the element, the index and the array) and should
        733 * return a boolean.
        734 * @param {S=} opt_obj An optional "this" context for the function.
        735 * @return {boolean} True if an element was removed.
        736 * @template T,S
        737 */
        738goog.array.removeIf = function(arr, f, opt_obj) {
        739 var i = goog.array.findIndex(arr, f, opt_obj);
        740 if (i >= 0) {
        741 goog.array.removeAt(arr, i);
        742 return true;
        743 }
        744 return false;
        745};
        746
        747
        748/**
        749 * Removes all values that satisfy the given condition.
        750 * @param {Array<T>|goog.array.ArrayLike} arr Array or array
        751 * like object over which to iterate.
        752 * @param {?function(this:S, T, number, ?) : boolean} f The function to call
        753 * for every element. This function
        754 * takes 3 arguments (the element, the index and the array) and should
        755 * return a boolean.
        756 * @param {S=} opt_obj An optional "this" context for the function.
        757 * @return {number} The number of items removed
        758 * @template T,S
        759 */
        760goog.array.removeAllIf = function(arr, f, opt_obj) {
        761 var removedCount = 0;
        762 goog.array.forEachRight(arr, function(val, index) {
        763 if (f.call(opt_obj, val, index, arr)) {
        764 if (goog.array.removeAt(arr, index)) {
        765 removedCount++;
        766 }
        767 }
        768 });
        769 return removedCount;
        770};
        771
        772
        773/**
        774 * Returns a new array that is the result of joining the arguments. If arrays
        775 * are passed then their items are added, however, if non-arrays are passed they
        776 * will be added to the return array as is.
        777 *
        778 * Note that ArrayLike objects will be added as is, rather than having their
        779 * items added.
        780 *
        781 * goog.array.concat([1, 2], [3, 4]) -> [1, 2, 3, 4]
        782 * goog.array.concat(0, [1, 2]) -> [0, 1, 2]
        783 * goog.array.concat([1, 2], null) -> [1, 2, null]
        784 *
        785 * There is bug in all current versions of IE (6, 7 and 8) where arrays created
        786 * in an iframe become corrupted soon (not immediately) after the iframe is
        787 * destroyed. This is common if loading data via goog.net.IframeIo, for example.
        788 * This corruption only affects the concat method which will start throwing
        789 * Catastrophic Errors (#-2147418113).
        790 *
        791 * See http://endoflow.com/scratch/corrupted-arrays.html for a test case.
        792 *
        793 * Internally goog.array should use this, so that all methods will continue to
        794 * work on these broken array objects.
        795 *
        796 * @param {...*} var_args Items to concatenate. Arrays will have each item
        797 * added, while primitives and objects will be added as is.
        798 * @return {!Array<?>} The new resultant array.
        799 */
        800goog.array.concat = function(var_args) {
        801 return goog.array.ARRAY_PROTOTYPE_.concat.apply(
        802 goog.array.ARRAY_PROTOTYPE_, arguments);
        803};
        804
        805
        806/**
        807 * Returns a new array that contains the contents of all the arrays passed.
        808 * @param {...!Array<T>} var_args
        809 * @return {!Array<T>}
        810 * @template T
        811 */
        812goog.array.join = function(var_args) {
        813 return goog.array.ARRAY_PROTOTYPE_.concat.apply(
        814 goog.array.ARRAY_PROTOTYPE_, arguments);
        815};
        816
        817
        818/**
        819 * Converts an object to an array.
        820 * @param {Array<T>|goog.array.ArrayLike} object The object to convert to an
        821 * array.
        822 * @return {!Array<T>} The object converted into an array. If object has a
        823 * length property, every property indexed with a non-negative number
        824 * less than length will be included in the result. If object does not
        825 * have a length property, an empty array will be returned.
        826 * @template T
        827 */
        828goog.array.toArray = function(object) {
        829 var length = object.length;
        830
        831 // If length is not a number the following it false. This case is kept for
        832 // backwards compatibility since there are callers that pass objects that are
        833 // not array like.
        834 if (length > 0) {
        835 var rv = new Array(length);
        836 for (var i = 0; i < length; i++) {
        837 rv[i] = object[i];
        838 }
        839 return rv;
        840 }
        841 return [];
        842};
        843
        844
        845/**
        846 * Does a shallow copy of an array.
        847 * @param {Array<T>|goog.array.ArrayLike} arr Array or array-like object to
        848 * clone.
        849 * @return {!Array<T>} Clone of the input array.
        850 * @template T
        851 */
        852goog.array.clone = goog.array.toArray;
        853
        854
        855/**
        856 * Extends an array with another array, element, or "array like" object.
        857 * This function operates 'in-place', it does not create a new Array.
        858 *
        859 * Example:
        860 * var a = [];
        861 * goog.array.extend(a, [0, 1]);
        862 * a; // [0, 1]
        863 * goog.array.extend(a, 2);
        864 * a; // [0, 1, 2]
        865 *
        866 * @param {Array<VALUE>} arr1 The array to modify.
        867 * @param {...(Array<VALUE>|VALUE)} var_args The elements or arrays of elements
        868 * to add to arr1.
        869 * @template VALUE
        870 */
        871goog.array.extend = function(arr1, var_args) {
        872 for (var i = 1; i < arguments.length; i++) {
        873 var arr2 = arguments[i];
        874 if (goog.isArrayLike(arr2)) {
        875 var len1 = arr1.length || 0;
        876 var len2 = arr2.length || 0;
        877 arr1.length = len1 + len2;
        878 for (var j = 0; j < len2; j++) {
        879 arr1[len1 + j] = arr2[j];
        880 }
        881 } else {
        882 arr1.push(arr2);
        883 }
        884 }
        885};
        886
        887
        888/**
        889 * Adds or removes elements from an array. This is a generic version of Array
        890 * splice. This means that it might work on other objects similar to arrays,
        891 * such as the arguments object.
        892 *
        893 * @param {Array<T>|goog.array.ArrayLike} arr The array to modify.
        894 * @param {number|undefined} index The index at which to start changing the
        895 * array. If not defined, treated as 0.
        896 * @param {number} howMany How many elements to remove (0 means no removal. A
        897 * value below 0 is treated as zero and so is any other non number. Numbers
        898 * are floored).
        899 * @param {...T} var_args Optional, additional elements to insert into the
        900 * array.
        901 * @return {!Array<T>} the removed elements.
        902 * @template T
        903 */
        904goog.array.splice = function(arr, index, howMany, var_args) {
        905 goog.asserts.assert(arr.length != null);
        906
        907 return goog.array.ARRAY_PROTOTYPE_.splice.apply(
        908 arr, goog.array.slice(arguments, 1));
        909};
        910
        911
        912/**
        913 * Returns a new array from a segment of an array. This is a generic version of
        914 * Array slice. This means that it might work on other objects similar to
        915 * arrays, such as the arguments object.
        916 *
        917 * @param {Array<T>|goog.array.ArrayLike} arr The array from
        918 * which to copy a segment.
        919 * @param {number} start The index of the first element to copy.
        920 * @param {number=} opt_end The index after the last element to copy.
        921 * @return {!Array<T>} A new array containing the specified segment of the
        922 * original array.
        923 * @template T
        924 */
        925goog.array.slice = function(arr, start, opt_end) {
        926 goog.asserts.assert(arr.length != null);
        927
        928 // passing 1 arg to slice is not the same as passing 2 where the second is
        929 // null or undefined (in that case the second argument is treated as 0).
        930 // we could use slice on the arguments object and then use apply instead of
        931 // testing the length
        932 if (arguments.length <= 2) {
        933 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start);
        934 } else {
        935 return goog.array.ARRAY_PROTOTYPE_.slice.call(arr, start, opt_end);
        936 }
        937};
        938
        939
        940/**
        941 * Removes all duplicates from an array (retaining only the first
        942 * occurrence of each array element). This function modifies the
        943 * array in place and doesn't change the order of the non-duplicate items.
        944 *
        945 * For objects, duplicates are identified as having the same unique ID as
        946 * defined by {@link goog.getUid}.
        947 *
        948 * Alternatively you can specify a custom hash function that returns a unique
        949 * value for each item in the array it should consider unique.
        950 *
        951 * Runtime: N,
        952 * Worstcase space: 2N (no dupes)
        953 *
        954 * @param {Array<T>|goog.array.ArrayLike} arr The array from which to remove
        955 * duplicates.
        956 * @param {Array=} opt_rv An optional array in which to return the results,
        957 * instead of performing the removal inplace. If specified, the original
        958 * array will remain unchanged.
        959 * @param {function(T):string=} opt_hashFn An optional function to use to
        960 * apply to every item in the array. This function should return a unique
        961 * value for each item in the array it should consider unique.
        962 * @template T
        963 */
        964goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) {
        965 var returnArray = opt_rv || arr;
        966 var defaultHashFn = function(item) {
        967 // Prefix each type with a single character representing the type to
        968 // prevent conflicting keys (e.g. true and 'true').
        969 return goog.isObject(current) ? 'o' + goog.getUid(current) :
        970 (typeof current).charAt(0) + current;
        971 };
        972 var hashFn = opt_hashFn || defaultHashFn;
        973
        974 var seen = {}, cursorInsert = 0, cursorRead = 0;
        975 while (cursorRead < arr.length) {
        976 var current = arr[cursorRead++];
        977 var key = hashFn(current);
        978 if (!Object.prototype.hasOwnProperty.call(seen, key)) {
        979 seen[key] = true;
        980 returnArray[cursorInsert++] = current;
        981 }
        982 }
        983 returnArray.length = cursorInsert;
        984};
        985
        986
        987/**
        988 * Searches the specified array for the specified target using the binary
        989 * search algorithm. If no opt_compareFn is specified, elements are compared
        990 * using <code>goog.array.defaultCompare</code>, which compares the elements
        991 * using the built in < and > operators. This will produce the expected
        992 * behavior for homogeneous arrays of String(s) and Number(s). The array
        993 * specified <b>must</b> be sorted in ascending order (as defined by the
        994 * comparison function). If the array is not sorted, results are undefined.
        995 * If the array contains multiple instances of the specified target value, any
        996 * of these instances may be found.
        997 *
        998 * Runtime: O(log n)
        999 *
        1000 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
        1001 * @param {TARGET} target The sought value.
        1002 * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison
        1003 * function by which the array is ordered. Should take 2 arguments to
        1004 * compare, and return a negative number, zero, or a positive number
        1005 * depending on whether the first argument is less than, equal to, or
        1006 * greater than the second.
        1007 * @return {number} Lowest index of the target value if found, otherwise
        1008 * (-(insertion point) - 1). The insertion point is where the value should
        1009 * be inserted into arr to preserve the sorted property. Return value >= 0
        1010 * iff target is found.
        1011 * @template TARGET, VALUE
        1012 */
        1013goog.array.binarySearch = function(arr, target, opt_compareFn) {
        1014 return goog.array.binarySearch_(arr,
        1015 opt_compareFn || goog.array.defaultCompare, false /* isEvaluator */,
        1016 target);
        1017};
        1018
        1019
        1020/**
        1021 * Selects an index in the specified array using the binary search algorithm.
        1022 * The evaluator receives an element and determines whether the desired index
        1023 * is before, at, or after it. The evaluator must be consistent (formally,
        1024 * goog.array.map(goog.array.map(arr, evaluator, opt_obj), goog.math.sign)
        1025 * must be monotonically non-increasing).
        1026 *
        1027 * Runtime: O(log n)
        1028 *
        1029 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
        1030 * @param {function(this:THIS, VALUE, number, ?): number} evaluator
        1031 * Evaluator function that receives 3 arguments (the element, the index and
        1032 * the array). Should return a negative number, zero, or a positive number
        1033 * depending on whether the desired index is before, at, or after the
        1034 * element passed to it.
        1035 * @param {THIS=} opt_obj The object to be used as the value of 'this'
        1036 * within evaluator.
        1037 * @return {number} Index of the leftmost element matched by the evaluator, if
        1038 * such exists; otherwise (-(insertion point) - 1). The insertion point is
        1039 * the index of the first element for which the evaluator returns negative,
        1040 * or arr.length if no such element exists. The return value is non-negative
        1041 * iff a match is found.
        1042 * @template THIS, VALUE
        1043 */
        1044goog.array.binarySelect = function(arr, evaluator, opt_obj) {
        1045 return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */,
        1046 undefined /* opt_target */, opt_obj);
        1047};
        1048
        1049
        1050/**
        1051 * Implementation of a binary search algorithm which knows how to use both
        1052 * comparison functions and evaluators. If an evaluator is provided, will call
        1053 * the evaluator with the given optional data object, conforming to the
        1054 * interface defined in binarySelect. Otherwise, if a comparison function is
        1055 * provided, will call the comparison function against the given data object.
        1056 *
        1057 * This implementation purposefully does not use goog.bind or goog.partial for
        1058 * performance reasons.
        1059 *
        1060 * Runtime: O(log n)
        1061 *
        1062 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to be searched.
        1063 * @param {function(TARGET, VALUE): number|
        1064 * function(this:THIS, VALUE, number, ?): number} compareFn Either an
        1065 * evaluator or a comparison function, as defined by binarySearch
        1066 * and binarySelect above.
        1067 * @param {boolean} isEvaluator Whether the function is an evaluator or a
        1068 * comparison function.
        1069 * @param {TARGET=} opt_target If the function is a comparison function, then
        1070 * this is the target to binary search for.
        1071 * @param {THIS=} opt_selfObj If the function is an evaluator, this is an
        1072 * optional this object for the evaluator.
        1073 * @return {number} Lowest index of the target value if found, otherwise
        1074 * (-(insertion point) - 1). The insertion point is where the value should
        1075 * be inserted into arr to preserve the sorted property. Return value >= 0
        1076 * iff target is found.
        1077 * @template THIS, VALUE, TARGET
        1078 * @private
        1079 */
        1080goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target,
        1081 opt_selfObj) {
        1082 var left = 0; // inclusive
        1083 var right = arr.length; // exclusive
        1084 var found;
        1085 while (left < right) {
        1086 var middle = (left + right) >> 1;
        1087 var compareResult;
        1088 if (isEvaluator) {
        1089 compareResult = compareFn.call(opt_selfObj, arr[middle], middle, arr);
        1090 } else {
        1091 compareResult = compareFn(opt_target, arr[middle]);
        1092 }
        1093 if (compareResult > 0) {
        1094 left = middle + 1;
        1095 } else {
        1096 right = middle;
        1097 // We are looking for the lowest index so we can't return immediately.
        1098 found = !compareResult;
        1099 }
        1100 }
        1101 // left is the index if found, or the insertion point otherwise.
        1102 // ~left is a shorthand for -left - 1.
        1103 return found ? left : ~left;
        1104};
        1105
        1106
        1107/**
        1108 * Sorts the specified array into ascending order. If no opt_compareFn is
        1109 * specified, elements are compared using
        1110 * <code>goog.array.defaultCompare</code>, which compares the elements using
        1111 * the built in < and > operators. This will produce the expected behavior
        1112 * for homogeneous arrays of String(s) and Number(s), unlike the native sort,
        1113 * but will give unpredictable results for heterogenous lists of strings and
        1114 * numbers with different numbers of digits.
        1115 *
        1116 * This sort is not guaranteed to be stable.
        1117 *
        1118 * Runtime: Same as <code>Array.prototype.sort</code>
        1119 *
        1120 * @param {Array<T>} arr The array to be sorted.
        1121 * @param {?function(T,T):number=} opt_compareFn Optional comparison
        1122 * function by which the
        1123 * array is to be ordered. Should take 2 arguments to compare, and return a
        1124 * negative number, zero, or a positive number depending on whether the
        1125 * first argument is less than, equal to, or greater than the second.
        1126 * @template T
        1127 */
        1128goog.array.sort = function(arr, opt_compareFn) {
        1129 // TODO(arv): Update type annotation since null is not accepted.
        1130 arr.sort(opt_compareFn || goog.array.defaultCompare);
        1131};
        1132
        1133
        1134/**
        1135 * Sorts the specified array into ascending order in a stable way. If no
        1136 * opt_compareFn is specified, elements are compared using
        1137 * <code>goog.array.defaultCompare</code>, which compares the elements using
        1138 * the built in < and > operators. This will produce the expected behavior
        1139 * for homogeneous arrays of String(s) and Number(s).
        1140 *
        1141 * Runtime: Same as <code>Array.prototype.sort</code>, plus an additional
        1142 * O(n) overhead of copying the array twice.
        1143 *
        1144 * @param {Array<T>} arr The array to be sorted.
        1145 * @param {?function(T, T): number=} opt_compareFn Optional comparison function
        1146 * by which the array is to be ordered. Should take 2 arguments to compare,
        1147 * and return a negative number, zero, or a positive number depending on
        1148 * whether the first argument is less than, equal to, or greater than the
        1149 * second.
        1150 * @template T
        1151 */
        1152goog.array.stableSort = function(arr, opt_compareFn) {
        1153 for (var i = 0; i < arr.length; i++) {
        1154 arr[i] = {index: i, value: arr[i]};
        1155 }
        1156 var valueCompareFn = opt_compareFn || goog.array.defaultCompare;
        1157 function stableCompareFn(obj1, obj2) {
        1158 return valueCompareFn(obj1.value, obj2.value) || obj1.index - obj2.index;
        1159 };
        1160 goog.array.sort(arr, stableCompareFn);
        1161 for (var i = 0; i < arr.length; i++) {
        1162 arr[i] = arr[i].value;
        1163 }
        1164};
        1165
        1166
        1167/**
        1168 * Sort the specified array into ascending order based on item keys
        1169 * returned by the specified key function.
        1170 * If no opt_compareFn is specified, the keys are compared in ascending order
        1171 * using <code>goog.array.defaultCompare</code>.
        1172 *
        1173 * Runtime: O(S(f(n)), where S is runtime of <code>goog.array.sort</code>
        1174 * and f(n) is runtime of the key function.
        1175 *
        1176 * @param {Array<T>} arr The array to be sorted.
        1177 * @param {function(T): K} keyFn Function taking array element and returning
        1178 * a key used for sorting this element.
        1179 * @param {?function(K, K): number=} opt_compareFn Optional comparison function
        1180 * by which the keys are to be ordered. Should take 2 arguments to compare,
        1181 * and return a negative number, zero, or a positive number depending on
        1182 * whether the first argument is less than, equal to, or greater than the
        1183 * second.
        1184 * @template T
        1185 * @template K
        1186 */
        1187goog.array.sortByKey = function(arr, keyFn, opt_compareFn) {
        1188 var keyCompareFn = opt_compareFn || goog.array.defaultCompare;
        1189 goog.array.sort(arr, function(a, b) {
        1190 return keyCompareFn(keyFn(a), keyFn(b));
        1191 });
        1192};
        1193
        1194
        1195/**
        1196 * Sorts an array of objects by the specified object key and compare
        1197 * function. If no compare function is provided, the key values are
        1198 * compared in ascending order using <code>goog.array.defaultCompare</code>.
        1199 * This won't work for keys that get renamed by the compiler. So use
        1200 * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}.
        1201 * @param {Array<Object>} arr An array of objects to sort.
        1202 * @param {string} key The object key to sort by.
        1203 * @param {Function=} opt_compareFn The function to use to compare key
        1204 * values.
        1205 */
        1206goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) {
        1207 goog.array.sortByKey(arr,
        1208 function(obj) { return obj[key]; },
        1209 opt_compareFn);
        1210};
        1211
        1212
        1213/**
        1214 * Tells if the array is sorted.
        1215 * @param {!Array<T>} arr The array.
        1216 * @param {?function(T,T):number=} opt_compareFn Function to compare the
        1217 * array elements.
        1218 * Should take 2 arguments to compare, and return a negative number, zero,
        1219 * or a positive number depending on whether the first argument is less
        1220 * than, equal to, or greater than the second.
        1221 * @param {boolean=} opt_strict If true no equal elements are allowed.
        1222 * @return {boolean} Whether the array is sorted.
        1223 * @template T
        1224 */
        1225goog.array.isSorted = function(arr, opt_compareFn, opt_strict) {
        1226 var compare = opt_compareFn || goog.array.defaultCompare;
        1227 for (var i = 1; i < arr.length; i++) {
        1228 var compareResult = compare(arr[i - 1], arr[i]);
        1229 if (compareResult > 0 || compareResult == 0 && opt_strict) {
        1230 return false;
        1231 }
        1232 }
        1233 return true;
        1234};
        1235
        1236
        1237/**
        1238 * Compares two arrays for equality. Two arrays are considered equal if they
        1239 * have the same length and their corresponding elements are equal according to
        1240 * the comparison function.
        1241 *
        1242 * @param {goog.array.ArrayLike} arr1 The first array to compare.
        1243 * @param {goog.array.ArrayLike} arr2 The second array to compare.
        1244 * @param {Function=} opt_equalsFn Optional comparison function.
        1245 * Should take 2 arguments to compare, and return true if the arguments
        1246 * are equal. Defaults to {@link goog.array.defaultCompareEquality} which
        1247 * compares the elements using the built-in '===' operator.
        1248 * @return {boolean} Whether the two arrays are equal.
        1249 */
        1250goog.array.equals = function(arr1, arr2, opt_equalsFn) {
        1251 if (!goog.isArrayLike(arr1) || !goog.isArrayLike(arr2) ||
        1252 arr1.length != arr2.length) {
        1253 return false;
        1254 }
        1255 var l = arr1.length;
        1256 var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
        1257 for (var i = 0; i < l; i++) {
        1258 if (!equalsFn(arr1[i], arr2[i])) {
        1259 return false;
        1260 }
        1261 }
        1262 return true;
        1263};
        1264
        1265
        1266/**
        1267 * 3-way array compare function.
        1268 * @param {!Array<VALUE>|!goog.array.ArrayLike} arr1 The first array to
        1269 * compare.
        1270 * @param {!Array<VALUE>|!goog.array.ArrayLike} arr2 The second array to
        1271 * compare.
        1272 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
        1273 * function by which the array is to be ordered. Should take 2 arguments to
        1274 * compare, and return a negative number, zero, or a positive number
        1275 * depending on whether the first argument is less than, equal to, or
        1276 * greater than the second.
        1277 * @return {number} Negative number, zero, or a positive number depending on
        1278 * whether the first argument is less than, equal to, or greater than the
        1279 * second.
        1280 * @template VALUE
        1281 */
        1282goog.array.compare3 = function(arr1, arr2, opt_compareFn) {
        1283 var compare = opt_compareFn || goog.array.defaultCompare;
        1284 var l = Math.min(arr1.length, arr2.length);
        1285 for (var i = 0; i < l; i++) {
        1286 var result = compare(arr1[i], arr2[i]);
        1287 if (result != 0) {
        1288 return result;
        1289 }
        1290 }
        1291 return goog.array.defaultCompare(arr1.length, arr2.length);
        1292};
        1293
        1294
        1295/**
        1296 * Compares its two arguments for order, using the built in < and >
        1297 * operators.
        1298 * @param {VALUE} a The first object to be compared.
        1299 * @param {VALUE} b The second object to be compared.
        1300 * @return {number} A negative number, zero, or a positive number as the first
        1301 * argument is less than, equal to, or greater than the second,
        1302 * respectively.
        1303 * @template VALUE
        1304 */
        1305goog.array.defaultCompare = function(a, b) {
        1306 return a > b ? 1 : a < b ? -1 : 0;
        1307};
        1308
        1309
        1310/**
        1311 * Compares its two arguments for inverse order, using the built in < and >
        1312 * operators.
        1313 * @param {VALUE} a The first object to be compared.
        1314 * @param {VALUE} b The second object to be compared.
        1315 * @return {number} A negative number, zero, or a positive number as the first
        1316 * argument is greater than, equal to, or less than the second,
        1317 * respectively.
        1318 * @template VALUE
        1319 */
        1320goog.array.inverseDefaultCompare = function(a, b) {
        1321 return -goog.array.defaultCompare(a, b);
        1322};
        1323
        1324
        1325/**
        1326 * Compares its two arguments for equality, using the built in === operator.
        1327 * @param {*} a The first object to compare.
        1328 * @param {*} b The second object to compare.
        1329 * @return {boolean} True if the two arguments are equal, false otherwise.
        1330 */
        1331goog.array.defaultCompareEquality = function(a, b) {
        1332 return a === b;
        1333};
        1334
        1335
        1336/**
        1337 * Inserts a value into a sorted array. The array is not modified if the
        1338 * value is already present.
        1339 * @param {Array<VALUE>|goog.array.ArrayLike} array The array to modify.
        1340 * @param {VALUE} value The object to insert.
        1341 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
        1342 * function by which the array is ordered. Should take 2 arguments to
        1343 * compare, and return a negative number, zero, or a positive number
        1344 * depending on whether the first argument is less than, equal to, or
        1345 * greater than the second.
        1346 * @return {boolean} True if an element was inserted.
        1347 * @template VALUE
        1348 */
        1349goog.array.binaryInsert = function(array, value, opt_compareFn) {
        1350 var index = goog.array.binarySearch(array, value, opt_compareFn);
        1351 if (index < 0) {
        1352 goog.array.insertAt(array, value, -(index + 1));
        1353 return true;
        1354 }
        1355 return false;
        1356};
        1357
        1358
        1359/**
        1360 * Removes a value from a sorted array.
        1361 * @param {!Array<VALUE>|!goog.array.ArrayLike} array The array to modify.
        1362 * @param {VALUE} value The object to remove.
        1363 * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison
        1364 * function by which the array is ordered. Should take 2 arguments to
        1365 * compare, and return a negative number, zero, or a positive number
        1366 * depending on whether the first argument is less than, equal to, or
        1367 * greater than the second.
        1368 * @return {boolean} True if an element was removed.
        1369 * @template VALUE
        1370 */
        1371goog.array.binaryRemove = function(array, value, opt_compareFn) {
        1372 var index = goog.array.binarySearch(array, value, opt_compareFn);
        1373 return (index >= 0) ? goog.array.removeAt(array, index) : false;
        1374};
        1375
        1376
        1377/**
        1378 * Splits an array into disjoint buckets according to a splitting function.
        1379 * @param {Array<T>} array The array.
        1380 * @param {function(this:S, T,number,Array<T>):?} sorter Function to call for
        1381 * every element. This takes 3 arguments (the element, the index and the
        1382 * array) and must return a valid object key (a string, number, etc), or
        1383 * undefined, if that object should not be placed in a bucket.
        1384 * @param {S=} opt_obj The object to be used as the value of 'this' within
        1385 * sorter.
        1386 * @return {!Object} An object, with keys being all of the unique return values
        1387 * of sorter, and values being arrays containing the items for
        1388 * which the splitter returned that key.
        1389 * @template T,S
        1390 */
        1391goog.array.bucket = function(array, sorter, opt_obj) {
        1392 var buckets = {};
        1393
        1394 for (var i = 0; i < array.length; i++) {
        1395 var value = array[i];
        1396 var key = sorter.call(opt_obj, value, i, array);
        1397 if (goog.isDef(key)) {
        1398 // Push the value to the right bucket, creating it if necessary.
        1399 var bucket = buckets[key] || (buckets[key] = []);
        1400 bucket.push(value);
        1401 }
        1402 }
        1403
        1404 return buckets;
        1405};
        1406
        1407
        1408/**
        1409 * Creates a new object built from the provided array and the key-generation
        1410 * function.
        1411 * @param {Array<T>|goog.array.ArrayLike} arr Array or array like object over
        1412 * which to iterate whose elements will be the values in the new object.
        1413 * @param {?function(this:S, T, number, ?) : string} keyFunc The function to
        1414 * call for every element. This function takes 3 arguments (the element, the
        1415 * index and the array) and should return a string that will be used as the
        1416 * key for the element in the new object. If the function returns the same
        1417 * key for more than one element, the value for that key is
        1418 * implementation-defined.
        1419 * @param {S=} opt_obj The object to be used as the value of 'this'
        1420 * within keyFunc.
        1421 * @return {!Object<T>} The new object.
        1422 * @template T,S
        1423 */
        1424goog.array.toObject = function(arr, keyFunc, opt_obj) {
        1425 var ret = {};
        1426 goog.array.forEach(arr, function(element, index) {
        1427 ret[keyFunc.call(opt_obj, element, index, arr)] = element;
        1428 });
        1429 return ret;
        1430};
        1431
        1432
        1433/**
        1434 * Creates a range of numbers in an arithmetic progression.
        1435 *
        1436 * Range takes 1, 2, or 3 arguments:
        1437 * <pre>
        1438 * range(5) is the same as range(0, 5, 1) and produces [0, 1, 2, 3, 4]
        1439 * range(2, 5) is the same as range(2, 5, 1) and produces [2, 3, 4]
        1440 * range(-2, -5, -1) produces [-2, -3, -4]
        1441 * range(-2, -5, 1) produces [], since stepping by 1 wouldn't ever reach -5.
        1442 * </pre>
        1443 *
        1444 * @param {number} startOrEnd The starting value of the range if an end argument
        1445 * is provided. Otherwise, the start value is 0, and this is the end value.
        1446 * @param {number=} opt_end The optional end value of the range.
        1447 * @param {number=} opt_step The step size between range values. Defaults to 1
        1448 * if opt_step is undefined or 0.
        1449 * @return {!Array<number>} An array of numbers for the requested range. May be
        1450 * an empty array if adding the step would not converge toward the end
        1451 * value.
        1452 */
        1453goog.array.range = function(startOrEnd, opt_end, opt_step) {
        1454 var array = [];
        1455 var start = 0;
        1456 var end = startOrEnd;
        1457 var step = opt_step || 1;
        1458 if (opt_end !== undefined) {
        1459 start = startOrEnd;
        1460 end = opt_end;
        1461 }
        1462
        1463 if (step * (end - start) < 0) {
        1464 // Sign mismatch: start + step will never reach the end value.
        1465 return [];
        1466 }
        1467
        1468 if (step > 0) {
        1469 for (var i = start; i < end; i += step) {
        1470 array.push(i);
        1471 }
        1472 } else {
        1473 for (var i = start; i > end; i += step) {
        1474 array.push(i);
        1475 }
        1476 }
        1477 return array;
        1478};
        1479
        1480
        1481/**
        1482 * Returns an array consisting of the given value repeated N times.
        1483 *
        1484 * @param {VALUE} value The value to repeat.
        1485 * @param {number} n The repeat count.
        1486 * @return {!Array<VALUE>} An array with the repeated value.
        1487 * @template VALUE
        1488 */
        1489goog.array.repeat = function(value, n) {
        1490 var array = [];
        1491 for (var i = 0; i < n; i++) {
        1492 array[i] = value;
        1493 }
        1494 return array;
        1495};
        1496
        1497
        1498/**
        1499 * Returns an array consisting of every argument with all arrays
        1500 * expanded in-place recursively.
        1501 *
        1502 * @param {...*} var_args The values to flatten.
        1503 * @return {!Array<?>} An array containing the flattened values.
        1504 */
        1505goog.array.flatten = function(var_args) {
        1506 var CHUNK_SIZE = 8192;
        1507
        1508 var result = [];
        1509 for (var i = 0; i < arguments.length; i++) {
        1510 var element = arguments[i];
        1511 if (goog.isArray(element)) {
        1512 for (var c = 0; c < element.length; c += CHUNK_SIZE) {
        1513 var chunk = goog.array.slice(element, c, c + CHUNK_SIZE);
        1514 var recurseResult = goog.array.flatten.apply(null, chunk);
        1515 for (var r = 0; r < recurseResult.length; r++) {
        1516 result.push(recurseResult[r]);
        1517 }
        1518 }
        1519 } else {
        1520 result.push(element);
        1521 }
        1522 }
        1523 return result;
        1524};
        1525
        1526
        1527/**
        1528 * Rotates an array in-place. After calling this method, the element at
        1529 * index i will be the element previously at index (i - n) %
        1530 * array.length, for all values of i between 0 and array.length - 1,
        1531 * inclusive.
        1532 *
        1533 * For example, suppose list comprises [t, a, n, k, s]. After invoking
        1534 * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k].
        1535 *
        1536 * @param {!Array<T>} array The array to rotate.
        1537 * @param {number} n The amount to rotate.
        1538 * @return {!Array<T>} The array.
        1539 * @template T
        1540 */
        1541goog.array.rotate = function(array, n) {
        1542 goog.asserts.assert(array.length != null);
        1543
        1544 if (array.length) {
        1545 n %= array.length;
        1546 if (n > 0) {
        1547 goog.array.ARRAY_PROTOTYPE_.unshift.apply(array, array.splice(-n, n));
        1548 } else if (n < 0) {
        1549 goog.array.ARRAY_PROTOTYPE_.push.apply(array, array.splice(0, -n));
        1550 }
        1551 }
        1552 return array;
        1553};
        1554
        1555
        1556/**
        1557 * Moves one item of an array to a new position keeping the order of the rest
        1558 * of the items. Example use case: keeping a list of JavaScript objects
        1559 * synchronized with the corresponding list of DOM elements after one of the
        1560 * elements has been dragged to a new position.
        1561 * @param {!(Array|Arguments|{length:number})} arr The array to modify.
        1562 * @param {number} fromIndex Index of the item to move between 0 and
        1563 * {@code arr.length - 1}.
        1564 * @param {number} toIndex Target index between 0 and {@code arr.length - 1}.
        1565 */
        1566goog.array.moveItem = function(arr, fromIndex, toIndex) {
        1567 goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length);
        1568 goog.asserts.assert(toIndex >= 0 && toIndex < arr.length);
        1569 // Remove 1 item at fromIndex.
        1570 var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1);
        1571 // Insert the removed item at toIndex.
        1572 goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]);
        1573 // We don't use goog.array.insertAt and goog.array.removeAt, because they're
        1574 // significantly slower than splice.
        1575};
        1576
        1577
        1578/**
        1579 * Creates a new array for which the element at position i is an array of the
        1580 * ith element of the provided arrays. The returned array will only be as long
        1581 * as the shortest array provided; additional values are ignored. For example,
        1582 * the result of zipping [1, 2] and [3, 4, 5] is [[1,3], [2, 4]].
        1583 *
        1584 * This is similar to the zip() function in Python. See {@link
        1585 * http://docs.python.org/library/functions.html#zip}
        1586 *
        1587 * @param {...!goog.array.ArrayLike} var_args Arrays to be combined.
        1588 * @return {!Array<!Array<?>>} A new array of arrays created from
        1589 * provided arrays.
        1590 */
        1591goog.array.zip = function(var_args) {
        1592 if (!arguments.length) {
        1593 return [];
        1594 }
        1595 var result = [];
        1596 for (var i = 0; true; i++) {
        1597 var value = [];
        1598 for (var j = 0; j < arguments.length; j++) {
        1599 var arr = arguments[j];
        1600 // If i is larger than the array length, this is the shortest array.
        1601 if (i >= arr.length) {
        1602 return result;
        1603 }
        1604 value.push(arr[i]);
        1605 }
        1606 result.push(value);
        1607 }
        1608};
        1609
        1610
        1611/**
        1612 * Shuffles the values in the specified array using the Fisher-Yates in-place
        1613 * shuffle (also known as the Knuth Shuffle). By default, calls Math.random()
        1614 * and so resets the state of that random number generator. Similarly, may reset
        1615 * the state of the any other specified random number generator.
        1616 *
        1617 * Runtime: O(n)
        1618 *
        1619 * @param {!Array<?>} arr The array to be shuffled.
        1620 * @param {function():number=} opt_randFn Optional random function to use for
        1621 * shuffling.
        1622 * Takes no arguments, and returns a random number on the interval [0, 1).
        1623 * Defaults to Math.random() using JavaScript's built-in Math library.
        1624 */
        1625goog.array.shuffle = function(arr, opt_randFn) {
        1626 var randFn = opt_randFn || Math.random;
        1627
        1628 for (var i = arr.length - 1; i > 0; i--) {
        1629 // Choose a random array index in [0, i] (inclusive with i).
        1630 var j = Math.floor(randFn() * (i + 1));
        1631
        1632 var tmp = arr[i];
        1633 arr[i] = arr[j];
        1634 arr[j] = tmp;
        1635 }
        1636};
        1637
        1638
        1639/**
        1640 * Returns a new array of elements from arr, based on the indexes of elements
        1641 * provided by index_arr. For example, the result of index copying
        1642 * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c'].
        1643 *
        1644 * @param {!Array<T>} arr The array to get a indexed copy from.
        1645 * @param {!Array<number>} index_arr An array of indexes to get from arr.
        1646 * @return {!Array<T>} A new array of elements from arr in index_arr order.
        1647 * @template T
        1648 */
        1649goog.array.copyByIndex = function(arr, index_arr) {
        1650 var result = [];
        1651 goog.array.forEach(index_arr, function(index) {
        1652 result.push(arr[index]);
        1653 });
        1654 return result;
        1655};
        \ No newline at end of file diff --git a/docs/source/lib/goog/asserts/asserts.js.src.html b/docs/source/lib/goog/asserts/asserts.js.src.html index a4d719f..29fa28d 100644 --- a/docs/source/lib/goog/asserts/asserts.js.src.html +++ b/docs/source/lib/goog/asserts/asserts.js.src.html @@ -1 +1 @@ -asserts.js

        lib/goog/asserts/asserts.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities to check the preconditions, postconditions and
        17 * invariants runtime.
        18 *
        19 * Methods in this package should be given special treatment by the compiler
        20 * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
        21 * will restrict <code>foo</code> to a truthy value.
        22 *
        23 * The compiler has an option to disable asserts. So code like:
        24 * <code>
        25 * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
        26 * </code>
        27 * will be transformed into:
        28 * <code>
        29 * var x = foo();
        30 * </code>
        31 * The compiler will leave in foo() (because its return value is used),
        32 * but it will remove bar() because it assumes it does not have side-effects.
        33 *
        34 */
        35
        36goog.provide('goog.asserts');
        37goog.provide('goog.asserts.AssertionError');
        38
        39goog.require('goog.debug.Error');
        40goog.require('goog.string');
        41
        42
        43/**
        44 * @define {boolean} Whether to strip out asserts or to leave them in.
        45 */
        46goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
        47
        48
        49
        50/**
        51 * Error object for failed assertions.
        52 * @param {string} messagePattern The pattern that was used to form message.
        53 * @param {!Array.<*>} messageArgs The items to substitute into the pattern.
        54 * @constructor
        55 * @extends {goog.debug.Error}
        56 */
        57goog.asserts.AssertionError = function(messagePattern, messageArgs) {
        58 messageArgs.unshift(messagePattern);
        59 goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
        60 // Remove the messagePattern afterwards to avoid permenantly modifying the
        61 // passed in array.
        62 messageArgs.shift();
        63
        64 /**
        65 * The message pattern used to format the error message. Error handlers can
        66 * use this to uniquely identify the assertion.
        67 * @type {string}
        68 */
        69 this.messagePattern = messagePattern;
        70};
        71goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
        72
        73
        74/** @override */
        75goog.asserts.AssertionError.prototype.name = 'AssertionError';
        76
        77
        78/**
        79 * Throws an exception with the given message and "Assertion failed" prefixed
        80 * onto it.
        81 * @param {string} defaultMessage The message to use if givenMessage is empty.
        82 * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage.
        83 * @param {string|undefined} givenMessage Message supplied by the caller.
        84 * @param {Array.<*>} givenArgs The substitution arguments for givenMessage.
        85 * @throws {goog.asserts.AssertionError} When the value is not a number.
        86 * @private
        87 */
        88goog.asserts.doAssertFailure_ =
        89 function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
        90 var message = 'Assertion failed';
        91 if (givenMessage) {
        92 message += ': ' + givenMessage;
        93 var args = givenArgs;
        94 } else if (defaultMessage) {
        95 message += ': ' + defaultMessage;
        96 args = defaultArgs;
        97 }
        98 // The '' + works around an Opera 10 bug in the unit tests. Without it,
        99 // a stack trace is added to var message above. With this, a stack trace is
        100 // not added until this line (it causes the extra garbage to be added after
        101 // the assertion message instead of in the middle of it).
        102 throw new goog.asserts.AssertionError('' + message, args || []);
        103};
        104
        105
        106/**
        107 * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
        108 * true.
        109 * @param {*} condition The condition to check.
        110 * @param {string=} opt_message Error message in case of failure.
        111 * @param {...*} var_args The items to substitute into the failure message.
        112 * @return {*} The value of the condition.
        113 * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
        114 */
        115goog.asserts.assert = function(condition, opt_message, var_args) {
        116 if (goog.asserts.ENABLE_ASSERTS && !condition) {
        117 goog.asserts.doAssertFailure_('', null, opt_message,
        118 Array.prototype.slice.call(arguments, 2));
        119 }
        120 return condition;
        121};
        122
        123
        124/**
        125 * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
        126 * when we want to add a check in the unreachable area like switch-case
        127 * statement:
        128 *
        129 * <pre>
        130 * switch(type) {
        131 * case FOO: doSomething(); break;
        132 * case BAR: doSomethingElse(); break;
        133 * default: goog.assert.fail('Unrecognized type: ' + type);
        134 * // We have only 2 types - "default:" section is unreachable code.
        135 * }
        136 * </pre>
        137 *
        138 * @param {string=} opt_message Error message in case of failure.
        139 * @param {...*} var_args The items to substitute into the failure message.
        140 * @throws {goog.asserts.AssertionError} Failure.
        141 */
        142goog.asserts.fail = function(opt_message, var_args) {
        143 if (goog.asserts.ENABLE_ASSERTS) {
        144 throw new goog.asserts.AssertionError(
        145 'Failure' + (opt_message ? ': ' + opt_message : ''),
        146 Array.prototype.slice.call(arguments, 1));
        147 }
        148};
        149
        150
        151/**
        152 * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
        153 * @param {*} value The value to check.
        154 * @param {string=} opt_message Error message in case of failure.
        155 * @param {...*} var_args The items to substitute into the failure message.
        156 * @return {number} The value, guaranteed to be a number when asserts enabled.
        157 * @throws {goog.asserts.AssertionError} When the value is not a number.
        158 */
        159goog.asserts.assertNumber = function(value, opt_message, var_args) {
        160 if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
        161 goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
        162 [goog.typeOf(value), value], opt_message,
        163 Array.prototype.slice.call(arguments, 2));
        164 }
        165 return /** @type {number} */ (value);
        166};
        167
        168
        169/**
        170 * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
        171 * @param {*} value The value to check.
        172 * @param {string=} opt_message Error message in case of failure.
        173 * @param {...*} var_args The items to substitute into the failure message.
        174 * @return {string} The value, guaranteed to be a string when asserts enabled.
        175 * @throws {goog.asserts.AssertionError} When the value is not a string.
        176 */
        177goog.asserts.assertString = function(value, opt_message, var_args) {
        178 if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
        179 goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
        180 [goog.typeOf(value), value], opt_message,
        181 Array.prototype.slice.call(arguments, 2));
        182 }
        183 return /** @type {string} */ (value);
        184};
        185
        186
        187/**
        188 * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
        189 * @param {*} value The value to check.
        190 * @param {string=} opt_message Error message in case of failure.
        191 * @param {...*} var_args The items to substitute into the failure message.
        192 * @return {!Function} The value, guaranteed to be a function when asserts
        193 * enabled.
        194 * @throws {goog.asserts.AssertionError} When the value is not a function.
        195 */
        196goog.asserts.assertFunction = function(value, opt_message, var_args) {
        197 if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
        198 goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
        199 [goog.typeOf(value), value], opt_message,
        200 Array.prototype.slice.call(arguments, 2));
        201 }
        202 return /** @type {!Function} */ (value);
        203};
        204
        205
        206/**
        207 * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
        208 * @param {*} value The value to check.
        209 * @param {string=} opt_message Error message in case of failure.
        210 * @param {...*} var_args The items to substitute into the failure message.
        211 * @return {!Object} The value, guaranteed to be a non-null object.
        212 * @throws {goog.asserts.AssertionError} When the value is not an object.
        213 */
        214goog.asserts.assertObject = function(value, opt_message, var_args) {
        215 if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
        216 goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
        217 [goog.typeOf(value), value],
        218 opt_message, Array.prototype.slice.call(arguments, 2));
        219 }
        220 return /** @type {!Object} */ (value);
        221};
        222
        223
        224/**
        225 * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
        226 * @param {*} value The value to check.
        227 * @param {string=} opt_message Error message in case of failure.
        228 * @param {...*} var_args The items to substitute into the failure message.
        229 * @return {!Array} The value, guaranteed to be a non-null array.
        230 * @throws {goog.asserts.AssertionError} When the value is not an array.
        231 */
        232goog.asserts.assertArray = function(value, opt_message, var_args) {
        233 if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
        234 goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
        235 [goog.typeOf(value), value], opt_message,
        236 Array.prototype.slice.call(arguments, 2));
        237 }
        238 return /** @type {!Array} */ (value);
        239};
        240
        241
        242/**
        243 * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
        244 * @param {*} value The value to check.
        245 * @param {string=} opt_message Error message in case of failure.
        246 * @param {...*} var_args The items to substitute into the failure message.
        247 * @return {boolean} The value, guaranteed to be a boolean when asserts are
        248 * enabled.
        249 * @throws {goog.asserts.AssertionError} When the value is not a boolean.
        250 */
        251goog.asserts.assertBoolean = function(value, opt_message, var_args) {
        252 if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
        253 goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
        254 [goog.typeOf(value), value], opt_message,
        255 Array.prototype.slice.call(arguments, 2));
        256 }
        257 return /** @type {boolean} */ (value);
        258};
        259
        260
        261/**
        262 * Checks if the value is an instance of the user-defined type if
        263 * goog.asserts.ENABLE_ASSERTS is true.
        264 *
        265 * The compiler may tighten the type returned by this function.
        266 *
        267 * @param {*} value The value to check.
        268 * @param {function(new: T, ...)} type A user-defined constructor.
        269 * @param {string=} opt_message Error message in case of failure.
        270 * @param {...*} var_args The items to substitute into the failure message.
        271 * @throws {goog.asserts.AssertionError} When the value is not an instance of
        272 * type.
        273 * @return {!T}
        274 * @template T
        275 */
        276goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
        277 if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
        278 goog.asserts.doAssertFailure_('instanceof check failed.', null,
        279 opt_message, Array.prototype.slice.call(arguments, 3));
        280 }
        281 return value;
        282};
        283
        \ No newline at end of file +asserts.js

        lib/goog/asserts/asserts.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities to check the preconditions, postconditions and
        17 * invariants runtime.
        18 *
        19 * Methods in this package should be given special treatment by the compiler
        20 * for type-inference. For example, <code>goog.asserts.assert(foo)</code>
        21 * will restrict <code>foo</code> to a truthy value.
        22 *
        23 * The compiler has an option to disable asserts. So code like:
        24 * <code>
        25 * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar());
        26 * </code>
        27 * will be transformed into:
        28 * <code>
        29 * var x = foo();
        30 * </code>
        31 * The compiler will leave in foo() (because its return value is used),
        32 * but it will remove bar() because it assumes it does not have side-effects.
        33 *
        34 * @author agrieve@google.com (Andrew Grieve)
        35 */
        36
        37goog.provide('goog.asserts');
        38goog.provide('goog.asserts.AssertionError');
        39
        40goog.require('goog.debug.Error');
        41goog.require('goog.dom.NodeType');
        42goog.require('goog.string');
        43
        44
        45/**
        46 * @define {boolean} Whether to strip out asserts or to leave them in.
        47 */
        48goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG);
        49
        50
        51
        52/**
        53 * Error object for failed assertions.
        54 * @param {string} messagePattern The pattern that was used to form message.
        55 * @param {!Array<*>} messageArgs The items to substitute into the pattern.
        56 * @constructor
        57 * @extends {goog.debug.Error}
        58 * @final
        59 */
        60goog.asserts.AssertionError = function(messagePattern, messageArgs) {
        61 messageArgs.unshift(messagePattern);
        62 goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs));
        63 // Remove the messagePattern afterwards to avoid permenantly modifying the
        64 // passed in array.
        65 messageArgs.shift();
        66
        67 /**
        68 * The message pattern used to format the error message. Error handlers can
        69 * use this to uniquely identify the assertion.
        70 * @type {string}
        71 */
        72 this.messagePattern = messagePattern;
        73};
        74goog.inherits(goog.asserts.AssertionError, goog.debug.Error);
        75
        76
        77/** @override */
        78goog.asserts.AssertionError.prototype.name = 'AssertionError';
        79
        80
        81/**
        82 * The default error handler.
        83 * @param {!goog.asserts.AssertionError} e The exception to be handled.
        84 */
        85goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; };
        86
        87
        88/**
        89 * The handler responsible for throwing or logging assertion errors.
        90 * @private {function(!goog.asserts.AssertionError)}
        91 */
        92goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER;
        93
        94
        95/**
        96 * Throws an exception with the given message and "Assertion failed" prefixed
        97 * onto it.
        98 * @param {string} defaultMessage The message to use if givenMessage is empty.
        99 * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage.
        100 * @param {string|undefined} givenMessage Message supplied by the caller.
        101 * @param {Array<*>} givenArgs The substitution arguments for givenMessage.
        102 * @throws {goog.asserts.AssertionError} When the value is not a number.
        103 * @private
        104 */
        105goog.asserts.doAssertFailure_ =
        106 function(defaultMessage, defaultArgs, givenMessage, givenArgs) {
        107 var message = 'Assertion failed';
        108 if (givenMessage) {
        109 message += ': ' + givenMessage;
        110 var args = givenArgs;
        111 } else if (defaultMessage) {
        112 message += ': ' + defaultMessage;
        113 args = defaultArgs;
        114 }
        115 // The '' + works around an Opera 10 bug in the unit tests. Without it,
        116 // a stack trace is added to var message above. With this, a stack trace is
        117 // not added until this line (it causes the extra garbage to be added after
        118 // the assertion message instead of in the middle of it).
        119 var e = new goog.asserts.AssertionError('' + message, args || []);
        120 goog.asserts.errorHandler_(e);
        121};
        122
        123
        124/**
        125 * Sets a custom error handler that can be used to customize the behavior of
        126 * assertion failures, for example by turning all assertion failures into log
        127 * messages.
        128 * @param {function(!goog.asserts.AssertionError)} errorHandler
        129 */
        130goog.asserts.setErrorHandler = function(errorHandler) {
        131 if (goog.asserts.ENABLE_ASSERTS) {
        132 goog.asserts.errorHandler_ = errorHandler;
        133 }
        134};
        135
        136
        137/**
        138 * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is
        139 * true.
        140 * @template T
        141 * @param {T} condition The condition to check.
        142 * @param {string=} opt_message Error message in case of failure.
        143 * @param {...*} var_args The items to substitute into the failure message.
        144 * @return {T} The value of the condition.
        145 * @throws {goog.asserts.AssertionError} When the condition evaluates to false.
        146 */
        147goog.asserts.assert = function(condition, opt_message, var_args) {
        148 if (goog.asserts.ENABLE_ASSERTS && !condition) {
        149 goog.asserts.doAssertFailure_('', null, opt_message,
        150 Array.prototype.slice.call(arguments, 2));
        151 }
        152 return condition;
        153};
        154
        155
        156/**
        157 * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case
        158 * when we want to add a check in the unreachable area like switch-case
        159 * statement:
        160 *
        161 * <pre>
        162 * switch(type) {
        163 * case FOO: doSomething(); break;
        164 * case BAR: doSomethingElse(); break;
        165 * default: goog.assert.fail('Unrecognized type: ' + type);
        166 * // We have only 2 types - "default:" section is unreachable code.
        167 * }
        168 * </pre>
        169 *
        170 * @param {string=} opt_message Error message in case of failure.
        171 * @param {...*} var_args The items to substitute into the failure message.
        172 * @throws {goog.asserts.AssertionError} Failure.
        173 */
        174goog.asserts.fail = function(opt_message, var_args) {
        175 if (goog.asserts.ENABLE_ASSERTS) {
        176 goog.asserts.errorHandler_(new goog.asserts.AssertionError(
        177 'Failure' + (opt_message ? ': ' + opt_message : ''),
        178 Array.prototype.slice.call(arguments, 1)));
        179 }
        180};
        181
        182
        183/**
        184 * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true.
        185 * @param {*} value The value to check.
        186 * @param {string=} opt_message Error message in case of failure.
        187 * @param {...*} var_args The items to substitute into the failure message.
        188 * @return {number} The value, guaranteed to be a number when asserts enabled.
        189 * @throws {goog.asserts.AssertionError} When the value is not a number.
        190 */
        191goog.asserts.assertNumber = function(value, opt_message, var_args) {
        192 if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) {
        193 goog.asserts.doAssertFailure_('Expected number but got %s: %s.',
        194 [goog.typeOf(value), value], opt_message,
        195 Array.prototype.slice.call(arguments, 2));
        196 }
        197 return /** @type {number} */ (value);
        198};
        199
        200
        201/**
        202 * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true.
        203 * @param {*} value The value to check.
        204 * @param {string=} opt_message Error message in case of failure.
        205 * @param {...*} var_args The items to substitute into the failure message.
        206 * @return {string} The value, guaranteed to be a string when asserts enabled.
        207 * @throws {goog.asserts.AssertionError} When the value is not a string.
        208 */
        209goog.asserts.assertString = function(value, opt_message, var_args) {
        210 if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) {
        211 goog.asserts.doAssertFailure_('Expected string but got %s: %s.',
        212 [goog.typeOf(value), value], opt_message,
        213 Array.prototype.slice.call(arguments, 2));
        214 }
        215 return /** @type {string} */ (value);
        216};
        217
        218
        219/**
        220 * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true.
        221 * @param {*} value The value to check.
        222 * @param {string=} opt_message Error message in case of failure.
        223 * @param {...*} var_args The items to substitute into the failure message.
        224 * @return {!Function} The value, guaranteed to be a function when asserts
        225 * enabled.
        226 * @throws {goog.asserts.AssertionError} When the value is not a function.
        227 */
        228goog.asserts.assertFunction = function(value, opt_message, var_args) {
        229 if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) {
        230 goog.asserts.doAssertFailure_('Expected function but got %s: %s.',
        231 [goog.typeOf(value), value], opt_message,
        232 Array.prototype.slice.call(arguments, 2));
        233 }
        234 return /** @type {!Function} */ (value);
        235};
        236
        237
        238/**
        239 * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true.
        240 * @param {*} value The value to check.
        241 * @param {string=} opt_message Error message in case of failure.
        242 * @param {...*} var_args The items to substitute into the failure message.
        243 * @return {!Object} The value, guaranteed to be a non-null object.
        244 * @throws {goog.asserts.AssertionError} When the value is not an object.
        245 */
        246goog.asserts.assertObject = function(value, opt_message, var_args) {
        247 if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) {
        248 goog.asserts.doAssertFailure_('Expected object but got %s: %s.',
        249 [goog.typeOf(value), value],
        250 opt_message, Array.prototype.slice.call(arguments, 2));
        251 }
        252 return /** @type {!Object} */ (value);
        253};
        254
        255
        256/**
        257 * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true.
        258 * @param {*} value The value to check.
        259 * @param {string=} opt_message Error message in case of failure.
        260 * @param {...*} var_args The items to substitute into the failure message.
        261 * @return {!Array<?>} The value, guaranteed to be a non-null array.
        262 * @throws {goog.asserts.AssertionError} When the value is not an array.
        263 */
        264goog.asserts.assertArray = function(value, opt_message, var_args) {
        265 if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) {
        266 goog.asserts.doAssertFailure_('Expected array but got %s: %s.',
        267 [goog.typeOf(value), value], opt_message,
        268 Array.prototype.slice.call(arguments, 2));
        269 }
        270 return /** @type {!Array<?>} */ (value);
        271};
        272
        273
        274/**
        275 * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true.
        276 * @param {*} value The value to check.
        277 * @param {string=} opt_message Error message in case of failure.
        278 * @param {...*} var_args The items to substitute into the failure message.
        279 * @return {boolean} The value, guaranteed to be a boolean when asserts are
        280 * enabled.
        281 * @throws {goog.asserts.AssertionError} When the value is not a boolean.
        282 */
        283goog.asserts.assertBoolean = function(value, opt_message, var_args) {
        284 if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) {
        285 goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.',
        286 [goog.typeOf(value), value], opt_message,
        287 Array.prototype.slice.call(arguments, 2));
        288 }
        289 return /** @type {boolean} */ (value);
        290};
        291
        292
        293/**
        294 * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true.
        295 * @param {*} value The value to check.
        296 * @param {string=} opt_message Error message in case of failure.
        297 * @param {...*} var_args The items to substitute into the failure message.
        298 * @return {!Element} The value, likely to be a DOM Element when asserts are
        299 * enabled.
        300 * @throws {goog.asserts.AssertionError} When the value is not an Element.
        301 */
        302goog.asserts.assertElement = function(value, opt_message, var_args) {
        303 if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) ||
        304 value.nodeType != goog.dom.NodeType.ELEMENT)) {
        305 goog.asserts.doAssertFailure_('Expected Element but got %s: %s.',
        306 [goog.typeOf(value), value], opt_message,
        307 Array.prototype.slice.call(arguments, 2));
        308 }
        309 return /** @type {!Element} */ (value);
        310};
        311
        312
        313/**
        314 * Checks if the value is an instance of the user-defined type if
        315 * goog.asserts.ENABLE_ASSERTS is true.
        316 *
        317 * The compiler may tighten the type returned by this function.
        318 *
        319 * @param {*} value The value to check.
        320 * @param {function(new: T, ...)} type A user-defined constructor.
        321 * @param {string=} opt_message Error message in case of failure.
        322 * @param {...*} var_args The items to substitute into the failure message.
        323 * @throws {goog.asserts.AssertionError} When the value is not an instance of
        324 * type.
        325 * @return {T}
        326 * @template T
        327 */
        328goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) {
        329 if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) {
        330 goog.asserts.doAssertFailure_('Expected instanceof %s but got %s.',
        331 [goog.asserts.getType_(type), goog.asserts.getType_(value)],
        332 opt_message, Array.prototype.slice.call(arguments, 3));
        333 }
        334 return value;
        335};
        336
        337
        338/**
        339 * Checks that no enumerable keys are present in Object.prototype. Such keys
        340 * would break most code that use {@code for (var ... in ...)} loops.
        341 */
        342goog.asserts.assertObjectPrototypeIsIntact = function() {
        343 for (var key in Object.prototype) {
        344 goog.asserts.fail(key + ' should not be enumerable in Object.prototype.');
        345 }
        346};
        347
        348
        349/**
        350 * Returns the type of a value. If a constructor is passed, and a suitable
        351 * string cannot be found, 'unknown type name' will be returned.
        352 * @param {*} value A constructor, object, or primitive.
        353 * @return {string} The best display name for the value, or 'unknown type name'.
        354 * @private
        355 */
        356goog.asserts.getType_ = function(value) {
        357 if (value instanceof Function) {
        358 return value.displayName || value.name || 'unknown type name';
        359 } else if (value instanceof Object) {
        360 return value.constructor.displayName || value.constructor.name ||
        361 Object.prototype.toString.call(value);
        362 } else {
        363 return value === null ? 'null' : typeof value;
        364 }
        365};
        \ No newline at end of file diff --git a/docs/source/lib/goog/async/freelist.js.src.html b/docs/source/lib/goog/async/freelist.js.src.html new file mode 100644 index 0000000..6c76c1c --- /dev/null +++ b/docs/source/lib/goog/async/freelist.js.src.html @@ -0,0 +1 @@ +freelist.js

        lib/goog/async/freelist.js

        1// Copyright 2015 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Simple freelist.
        17 *
        18 * An anterative to goog.structs.SimplePool, it imposes the requirement that the
        19 * objects in the list contain a "next" property that can be used to maintain
        20 * the pool.
        21 */
        22
        23goog.provide('goog.async.FreeList');
        24
        25
        26/**
        27 * @template ITEM
        28 */
        29goog.async.FreeList = goog.defineClass(null, {
        30 /**
        31 * @param {function():ITEM} create
        32 * @param {function(ITEM):void} reset
        33 * @param {number} limit
        34 */
        35 constructor: function(create, reset, limit) {
        36 /** @const {number} */
        37 this.limit_ = limit;
        38 /** @const {function()} */
        39 this.create_ = create;
        40 /** @const {function(ITEM):void} */
        41 this.reset_ = reset;
        42
        43 /** @type {number} */
        44 this.occupants_ = 0;
        45 /** @type {ITEM} */
        46 this.head_ = null;
        47 },
        48
        49 /**
        50 * @return {ITEM}
        51 */
        52 get: function() {
        53 var item;
        54 if (this.occupants_ > 0) {
        55 this.occupants_--;
        56 item = this.head_;
        57 this.head_ = item.next;
        58 item.next = null;
        59 } else {
        60 item = this.create_();
        61 }
        62 return item;
        63 },
        64
        65 /**
        66 * @param {ITEM} item An item available for possible future reuse.
        67 */
        68 put: function(item) {
        69 this.reset_(item);
        70 if (this.occupants_ < this.limit_) {
        71 this.occupants_++;
        72 item.next = this.head_;
        73 this.head_ = item;
        74 }
        75 },
        76
        77 /**
        78 * Visible for testing.
        79 * @package
        80 * @return {number}
        81 */
        82 occupants: function() {
        83 return this.occupants_;
        84 }
        85});
        86
        87
        88
        \ No newline at end of file diff --git a/docs/source/lib/goog/async/nexttick.js.src.html b/docs/source/lib/goog/async/nexttick.js.src.html new file mode 100644 index 0000000..c1b8f1d --- /dev/null +++ b/docs/source/lib/goog/async/nexttick.js.src.html @@ -0,0 +1 @@ +nexttick.js

        lib/goog/async/nexttick.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides a function to schedule running a function as soon
        17 * as possible after the current JS execution stops and yields to the event
        18 * loop.
        19 *
        20 */
        21
        22goog.provide('goog.async.nextTick');
        23goog.provide('goog.async.throwException');
        24
        25goog.require('goog.debug.entryPointRegistry');
        26goog.require('goog.dom.TagName');
        27goog.require('goog.functions');
        28goog.require('goog.labs.userAgent.browser');
        29goog.require('goog.labs.userAgent.engine');
        30
        31
        32/**
        33 * Throw an item without interrupting the current execution context. For
        34 * example, if processing a group of items in a loop, sometimes it is useful
        35 * to report an error while still allowing the rest of the batch to be
        36 * processed.
        37 * @param {*} exception
        38 */
        39goog.async.throwException = function(exception) {
        40 // Each throw needs to be in its own context.
        41 goog.global.setTimeout(function() { throw exception; }, 0);
        42};
        43
        44
        45/**
        46 * Fires the provided callbacks as soon as possible after the current JS
        47 * execution context. setTimeout(…, 0) takes at least 4ms when called from
        48 * within another setTimeout(…, 0) for legacy reasons.
        49 *
        50 * This will not schedule the callback as a microtask (i.e. a task that can
        51 * preempt user input or networking callbacks). It is meant to emulate what
        52 * setTimeout(_, 0) would do if it were not throttled. If you desire microtask
        53 * behavior, use {@see goog.Promise} instead.
        54 *
        55 * @param {function(this:SCOPE)} callback Callback function to fire as soon as
        56 * possible.
        57 * @param {SCOPE=} opt_context Object in whose scope to call the listener.
        58 * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that
        59 * ensures correctness at the cost of speed. See comments for details.
        60 * @template SCOPE
        61 */
        62goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) {
        63 var cb = callback;
        64 if (opt_context) {
        65 cb = goog.bind(callback, opt_context);
        66 }
        67 cb = goog.async.nextTick.wrapCallback_(cb);
        68 // window.setImmediate was introduced and currently only supported by IE10+,
        69 // but due to a bug in the implementation it is not guaranteed that
        70 // setImmediate is faster than setTimeout nor that setImmediate N is before
        71 // setImmediate N+1. That is why we do not use the native version if
        72 // available. We do, however, call setImmediate if it is a normal function
        73 // because that indicates that it has been replaced by goog.testing.MockClock
        74 // which we do want to support.
        75 // See
        76 // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10
        77 //
        78 // Note we do allow callers to also request setImmediate if they are willing
        79 // to accept the possible tradeoffs of incorrectness in exchange for speed.
        80 // The IE fallback of readystate change is much slower.
        81 if (goog.isFunction(goog.global.setImmediate) &&
        82 // Opt in.
        83 (opt_useSetImmediate ||
        84 // or it isn't a browser or the environment is weird
        85 !goog.global.Window || !goog.global.Window.prototype ||
        86 // or something redefined setImmediate in which case we (YOLO) decide
        87 // to use it (This is so that we use the mockClock setImmediate. sigh).
        88 goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) {
        89 goog.global.setImmediate(cb);
        90 return;
        91 }
        92
        93 // Look for and cache the custom fallback version of setImmediate.
        94 if (!goog.async.nextTick.setImmediate_) {
        95 goog.async.nextTick.setImmediate_ =
        96 goog.async.nextTick.getSetImmediateEmulator_();
        97 }
        98 goog.async.nextTick.setImmediate_(cb);
        99};
        100
        101
        102/**
        103 * Cache for the setImmediate implementation.
        104 * @type {function(function())}
        105 * @private
        106 */
        107goog.async.nextTick.setImmediate_;
        108
        109
        110/**
        111 * Determines the best possible implementation to run a function as soon as
        112 * the JS event loop is idle.
        113 * @return {function(function())} The "setImmediate" implementation.
        114 * @private
        115 */
        116goog.async.nextTick.getSetImmediateEmulator_ = function() {
        117 // Create a private message channel and use it to postMessage empty messages
        118 // to ourselves.
        119 var Channel = goog.global['MessageChannel'];
        120 // If MessageChannel is not available and we are in a browser, implement
        121 // an iframe based polyfill in browsers that have postMessage and
        122 // document.addEventListener. The latter excludes IE8 because it has a
        123 // synchronous postMessage implementation.
        124 if (typeof Channel === 'undefined' && typeof window !== 'undefined' &&
        125 window.postMessage && window.addEventListener &&
        126 // Presto (The old pre-blink Opera engine) has problems with iframes
        127 // and contentWindow.
        128 !goog.labs.userAgent.engine.isPresto()) {
        129 /** @constructor */
        130 Channel = function() {
        131 // Make an empty, invisible iframe.
        132 var iframe = document.createElement(goog.dom.TagName.IFRAME);
        133 iframe.style.display = 'none';
        134 iframe.src = '';
        135 document.documentElement.appendChild(iframe);
        136 var win = iframe.contentWindow;
        137 var doc = win.document;
        138 doc.open();
        139 doc.write('');
        140 doc.close();
        141 // Do not post anything sensitive over this channel, as the workaround for
        142 // pages with file: origin could allow that information to be modified or
        143 // intercepted.
        144 var message = 'callImmediate' + Math.random();
        145 // The same origin policy rejects attempts to postMessage from file: urls
        146 // unless the origin is '*'.
        147 // TODO(b/16335441): Use '*' origin for data: and other similar protocols.
        148 var origin = win.location.protocol == 'file:' ?
        149 '*' : win.location.protocol + '//' + win.location.host;
        150 var onmessage = goog.bind(function(e) {
        151 // Validate origin and message to make sure that this message was
        152 // intended for us. If the origin is set to '*' (see above) only the
        153 // message needs to match since, for example, '*' != 'file://'. Allowing
        154 // the wildcard is ok, as we are not concerned with security here.
        155 if ((origin != '*' && e.origin != origin) || e.data != message) {
        156 return;
        157 }
        158 this['port1'].onmessage();
        159 }, this);
        160 win.addEventListener('message', onmessage, false);
        161 this['port1'] = {};
        162 this['port2'] = {
        163 postMessage: function() {
        164 win.postMessage(message, origin);
        165 }
        166 };
        167 };
        168 }
        169 if (typeof Channel !== 'undefined' &&
        170 (!goog.labs.userAgent.browser.isIE())) {
        171 // Exclude all of IE due to
        172 // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/
        173 // which allows starving postMessage with a busy setTimeout loop.
        174 // This currently affects IE10 and IE11 which would otherwise be able
        175 // to use the postMessage based fallbacks.
        176 var channel = new Channel();
        177 // Use a fifo linked list to call callbacks in the right order.
        178 var head = {};
        179 var tail = head;
        180 channel['port1'].onmessage = function() {
        181 if (goog.isDef(head.next)) {
        182 head = head.next;
        183 var cb = head.cb;
        184 head.cb = null;
        185 cb();
        186 }
        187 };
        188 return function(cb) {
        189 tail.next = {
        190 cb: cb
        191 };
        192 tail = tail.next;
        193 channel['port2'].postMessage(0);
        194 };
        195 }
        196 // Implementation for IE6+: Script elements fire an asynchronous
        197 // onreadystatechange event when inserted into the DOM.
        198 if (typeof document !== 'undefined' && 'onreadystatechange' in
        199 document.createElement(goog.dom.TagName.SCRIPT)) {
        200 return function(cb) {
        201 var script = document.createElement(goog.dom.TagName.SCRIPT);
        202 script.onreadystatechange = function() {
        203 // Clean up and call the callback.
        204 script.onreadystatechange = null;
        205 script.parentNode.removeChild(script);
        206 script = null;
        207 cb();
        208 cb = null;
        209 };
        210 document.documentElement.appendChild(script);
        211 };
        212 }
        213 // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms
        214 // or more.
        215 return function(cb) {
        216 goog.global.setTimeout(cb, 0);
        217 };
        218};
        219
        220
        221/**
        222 * Helper function that is overrided to protect callbacks with entry point
        223 * monitor if the application monitors entry points.
        224 * @param {function()} callback Callback function to fire as soon as possible.
        225 * @return {function()} The wrapped callback.
        226 * @private
        227 */
        228goog.async.nextTick.wrapCallback_ = goog.functions.identity;
        229
        230
        231// Register the callback function as an entry point, so that it can be
        232// monitored for exception handling, etc. This has to be done in this file
        233// since it requires special code to handle all browsers.
        234goog.debug.entryPointRegistry.register(
        235 /**
        236 * @param {function(!Function): !Function} transformer The transforming
        237 * function.
        238 */
        239 function(transformer) {
        240 goog.async.nextTick.wrapCallback_ = transformer;
        241 });
        \ No newline at end of file diff --git a/docs/source/lib/goog/async/run.js.src.html b/docs/source/lib/goog/async/run.js.src.html new file mode 100644 index 0000000..aa75694 --- /dev/null +++ b/docs/source/lib/goog/async/run.js.src.html @@ -0,0 +1 @@ +run.js

        lib/goog/async/run.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.async.run');
        16
        17goog.require('goog.async.WorkQueue');
        18goog.require('goog.async.nextTick');
        19goog.require('goog.async.throwException');
        20goog.require('goog.testing.watchers');
        21
        22
        23/**
        24 * Fires the provided callback just before the current callstack unwinds, or as
        25 * soon as possible after the current JS execution context.
        26 * @param {function(this:THIS)} callback
        27 * @param {THIS=} opt_context Object to use as the "this value" when calling
        28 * the provided function.
        29 * @template THIS
        30 */
        31goog.async.run = function(callback, opt_context) {
        32 if (!goog.async.run.schedule_) {
        33 goog.async.run.initializeRunner_();
        34 }
        35 if (!goog.async.run.workQueueScheduled_) {
        36 // Nothing is currently scheduled, schedule it now.
        37 goog.async.run.schedule_();
        38 goog.async.run.workQueueScheduled_ = true;
        39 }
        40
        41 goog.async.run.workQueue_.add(callback, opt_context);
        42};
        43
        44
        45/**
        46 * Initializes the function to use to process the work queue.
        47 * @private
        48 */
        49goog.async.run.initializeRunner_ = function() {
        50 // If native Promises are available in the browser, just schedule the callback
        51 // on a fulfilled promise, which is specified to be async, but as fast as
        52 // possible.
        53 if (goog.global.Promise && goog.global.Promise.resolve) {
        54 var promise = goog.global.Promise.resolve();
        55 goog.async.run.schedule_ = function() {
        56 promise.then(goog.async.run.processWorkQueue);
        57 };
        58 } else {
        59 goog.async.run.schedule_ = function() {
        60 goog.async.nextTick(goog.async.run.processWorkQueue);
        61 };
        62 }
        63};
        64
        65
        66/**
        67 * Forces goog.async.run to use nextTick instead of Promise.
        68 *
        69 * This should only be done in unit tests. It's useful because MockClock
        70 * replaces nextTick, but not the browser Promise implementation, so it allows
        71 * Promise-based code to be tested with MockClock.
        72 *
        73 * However, we also want to run promises if the MockClock is no longer in
        74 * control so we schedule a backup "setTimeout" to the unmocked timeout if
        75 * provided.
        76 *
        77 * @param {function(function())=} opt_realSetTimeout
        78 */
        79goog.async.run.forceNextTick = function(opt_realSetTimeout) {
        80 goog.async.run.schedule_ = function() {
        81 goog.async.nextTick(goog.async.run.processWorkQueue);
        82 if (opt_realSetTimeout) {
        83 opt_realSetTimeout(goog.async.run.processWorkQueue);
        84 }
        85 };
        86};
        87
        88
        89/**
        90 * The function used to schedule work asynchronousely.
        91 * @private {function()}
        92 */
        93goog.async.run.schedule_;
        94
        95
        96/** @private {boolean} */
        97goog.async.run.workQueueScheduled_ = false;
        98
        99
        100/** @private {!goog.async.WorkQueue} */
        101goog.async.run.workQueue_ = new goog.async.WorkQueue();
        102
        103
        104if (goog.DEBUG) {
        105 /**
        106 * Reset the work queue.
        107 * @private
        108 */
        109 goog.async.run.resetQueue_ = function() {
        110 goog.async.run.workQueueScheduled_ = false;
        111 goog.async.run.workQueue_ = new goog.async.WorkQueue();
        112 };
        113
        114 // If there is a clock implemenation in use for testing
        115 // and it is reset, reset the queue.
        116 goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_);
        117}
        118
        119
        120/**
        121 * Run any pending goog.async.run work items. This function is not intended
        122 * for general use, but for use by entry point handlers to run items ahead of
        123 * goog.async.nextTick.
        124 */
        125goog.async.run.processWorkQueue = function() {
        126 // NOTE: additional work queue items may be added while processing.
        127 var item = null;
        128 while (item = goog.async.run.workQueue_.remove()) {
        129 try {
        130 item.fn.call(item.scope);
        131 } catch (e) {
        132 goog.async.throwException(e);
        133 }
        134 goog.async.run.workQueue_.returnUnused(item);
        135 }
        136
        137 // There are no more work items, allow processing to be scheduled again.
        138 goog.async.run.workQueueScheduled_ = false;
        139};
        \ No newline at end of file diff --git a/docs/source/lib/goog/async/workqueue.js.src.html b/docs/source/lib/goog/async/workqueue.js.src.html new file mode 100644 index 0000000..bc75c92 --- /dev/null +++ b/docs/source/lib/goog/async/workqueue.js.src.html @@ -0,0 +1 @@ +workqueue.js

        lib/goog/async/workqueue.js

        1// Copyright 2015 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.async.WorkItem');
        16goog.provide('goog.async.WorkQueue');
        17
        18goog.require('goog.asserts');
        19goog.require('goog.async.FreeList');
        20
        21
        22// TODO(johnlenz): generalize the WorkQueue if this is used by more
        23// than goog.async.run.
        24
        25
        26
        27/**
        28 * A low GC workqueue. The key elements of this design:
        29 * - avoids the need for goog.bind or equivalent by carrying scope
        30 * - avoids the need for array reallocation by using a linked list
        31 * - minimizes work entry objects allocation by recycling objects
        32 * @constructor
        33 * @final
        34 * @struct
        35 */
        36goog.async.WorkQueue = function() {
        37 this.workHead_ = null;
        38 this.workTail_ = null;
        39};
        40
        41
        42/** @define {number} The maximum number of entries to keep for recycling. */
        43goog.define('goog.async.WorkQueue.DEFAULT_MAX_UNUSED', 100);
        44
        45
        46/** @const @private {goog.async.FreeList<goog.async.WorkItem>} */
        47goog.async.WorkQueue.freelist_ = new goog.async.FreeList(
        48 function() {return new goog.async.WorkItem(); },
        49 function(item) {item.reset()},
        50 goog.async.WorkQueue.DEFAULT_MAX_UNUSED);
        51
        52
        53/**
        54 * @param {function()} fn
        55 * @param {Object|null|undefined} scope
        56 */
        57goog.async.WorkQueue.prototype.add = function(fn, scope) {
        58 var item = this.getUnusedItem_();
        59 item.set(fn, scope);
        60
        61 if (this.workTail_) {
        62 this.workTail_.next = item;
        63 this.workTail_ = item;
        64 } else {
        65 goog.asserts.assert(!this.workHead_);
        66 this.workHead_ = item;
        67 this.workTail_ = item;
        68 }
        69};
        70
        71
        72/**
        73 * @return {goog.async.WorkItem}
        74 */
        75goog.async.WorkQueue.prototype.remove = function() {
        76 var item = null;
        77
        78 if (this.workHead_) {
        79 item = this.workHead_;
        80 this.workHead_ = this.workHead_.next;
        81 if (!this.workHead_) {
        82 this.workTail_ = null;
        83 }
        84 item.next = null;
        85 }
        86 return item;
        87};
        88
        89
        90/**
        91 * @param {goog.async.WorkItem} item
        92 */
        93goog.async.WorkQueue.prototype.returnUnused = function(item) {
        94 goog.async.WorkQueue.freelist_.put(item);
        95};
        96
        97
        98/**
        99 * @return {goog.async.WorkItem}
        100 * @private
        101 */
        102goog.async.WorkQueue.prototype.getUnusedItem_ = function() {
        103 return goog.async.WorkQueue.freelist_.get();
        104};
        105
        106
        107
        108/**
        109 * @constructor
        110 * @final
        111 * @struct
        112 */
        113goog.async.WorkItem = function() {
        114 /** @type {?function()} */
        115 this.fn = null;
        116 /** @type {Object|null|undefined} */
        117 this.scope = null;
        118 /** @type {?goog.async.WorkItem} */
        119 this.next = null;
        120};
        121
        122
        123/**
        124 * @param {function()} fn
        125 * @param {Object|null|undefined} scope
        126 */
        127goog.async.WorkItem.prototype.set = function(fn, scope) {
        128 this.fn = fn;
        129 this.scope = scope;
        130 this.next = null;
        131};
        132
        133
        134/** Reset the work item so they don't prevent GC before reuse */
        135goog.async.WorkItem.prototype.reset = function() {
        136 this.fn = null;
        137 this.scope = null;
        138 this.next = null;
        139};
        \ No newline at end of file diff --git a/docs/source/lib/goog/base.js.src.html b/docs/source/lib/goog/base.js.src.html index dd4e1b2..cbe7f1b 100644 --- a/docs/source/lib/goog/base.js.src.html +++ b/docs/source/lib/goog/base.js.src.html @@ -1 +1 @@ -base.js

        lib/goog/base.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Bootstrap for the Google JS Library (Closure).
        17 *
        18 * In uncompiled mode base.js will write out Closure's deps file, unless the
        19 * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
        20 * include their own deps file(s) from different locations.
        21 *
        22 *
        23 * @provideGoog
        24 */
        25
        26
        27/**
        28 * @define {boolean} Overridden to true by the compiler when --closure_pass
        29 * or --mark_as_compiled is specified.
        30 */
        31var COMPILED = false;
        32
        33
        34/**
        35 * Base namespace for the Closure library. Checks to see goog is
        36 * already defined in the current scope before assigning to prevent
        37 * clobbering if base.js is loaded more than once.
        38 *
        39 * @const
        40 */
        41var goog = goog || {};
        42
        43
        44/**
        45 * Reference to the global context. In most cases this will be 'window'.
        46 */
        47goog.global = this;
        48
        49
        50/**
        51 * A hook for overriding the define values in uncompiled mode.
        52 *
        53 * In uncompiled mode, {@code CLOSURE_DEFINES} may be defined before loading
        54 * base.js. If a key is defined in {@code CLOSURE_DEFINES}, {@code goog.define}
        55 * will use the value instead of the default value. This allows flags to be
        56 * overwritten without compilation (this is normally accomplished with the
        57 * compiler's "define" flag).
        58 *
        59 * Example:
        60 * <pre>
        61 * var CLOSURE_DEFINES = {'goog.DEBUG', false};
        62 * </pre>
        63 *
        64 * @type {Object.<string, (string|number|boolean)>|undefined}
        65 */
        66goog.global.CLOSURE_DEFINES;
        67
        68
        69/**
        70 * Builds an object structure for the provided namespace path,
        71 * ensuring that names that already exist are not overwritten. For
        72 * example:
        73 * "a.b.c" -> a = {};a.b={};a.b.c={};
        74 * Used by goog.provide and goog.exportSymbol.
        75 * @param {string} name name of the object that this file defines.
        76 * @param {*=} opt_object the object to expose at the end of the path.
        77 * @param {Object=} opt_objectToExportTo The object to add the path to; default
        78 * is |goog.global|.
        79 * @private
        80 */
        81goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
        82 var parts = name.split('.');
        83 var cur = opt_objectToExportTo || goog.global;
        84
        85 // Internet Explorer exhibits strange behavior when throwing errors from
        86 // methods externed in this manner. See the testExportSymbolExceptions in
        87 // base_test.html for an example.
        88 if (!(parts[0] in cur) && cur.execScript) {
        89 cur.execScript('var ' + parts[0]);
        90 }
        91
        92 // Certain browsers cannot parse code in the form for((a in b); c;);
        93 // This pattern is produced by the JSCompiler when it collapses the
        94 // statement above into the conditional loop below. To prevent this from
        95 // happening, use a for-loop and reserve the init logic as below.
        96
        97 // Parentheses added to eliminate strict JS warning in Firefox.
        98 for (var part; parts.length && (part = parts.shift());) {
        99 if (!parts.length && opt_object !== undefined) {
        100 // last part and we have an object; use it
        101 cur[part] = opt_object;
        102 } else if (cur[part]) {
        103 cur = cur[part];
        104 } else {
        105 cur = cur[part] = {};
        106 }
        107 }
        108};
        109
        110
        111/**
        112 * Defines a named value. In uncompiled mode, the value is retreived from
        113 * CLOSURE_DEFINES if the object is defined and has the property specified,
        114 * and otherwise used the defined defaultValue. When compiled, the default
        115 * can be overridden using compiler command-line options.
        116 *
        117 * @param {string} name The distinguished name to provide.
        118 * @param {string|number|boolean} defaultValue
        119 */
        120goog.define = function(name, defaultValue) {
        121 var value = defaultValue;
        122 if (!COMPILED) {
        123 if (goog.global.CLOSURE_DEFINES && Object.prototype.hasOwnProperty.call(
        124 goog.global.CLOSURE_DEFINES, name)) {
        125 value = goog.global.CLOSURE_DEFINES[name];
        126 }
        127 }
        128 goog.exportPath_(name, value);
        129};
        130
        131
        132/**
        133 * @define {boolean} DEBUG is provided as a convenience so that debugging code
        134 * that should not be included in a production js_binary can be easily stripped
        135 * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
        136 * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
        137 * because they are generally used for debugging purposes and it is difficult
        138 * for the JSCompiler to statically determine whether they are used.
        139 */
        140goog.DEBUG = true;
        141
        142
        143/**
        144 * @define {string} LOCALE defines the locale being used for compilation. It is
        145 * used to select locale specific data to be compiled in js binary. BUILD rule
        146 * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
        147 * option.
        148 *
        149 * Take into account that the locale code format is important. You should use
        150 * the canonical Unicode format with hyphen as a delimiter. Language must be
        151 * lowercase, Language Script - Capitalized, Region - UPPERCASE.
        152 * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
        153 *
        154 * See more info about locale codes here:
        155 * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
        156 *
        157 * For language codes you should use values defined by ISO 693-1. See it here
        158 * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
        159 * this rule: the Hebrew language. For legacy reasons the old code (iw) should
        160 * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
        161 */
        162goog.define('goog.LOCALE', 'en'); // default to en
        163
        164
        165/**
        166 * @define {boolean} Whether this code is running on trusted sites.
        167 *
        168 * On untrusted sites, several native functions can be defined or overridden by
        169 * external libraries like Prototype, Datejs, and JQuery and setting this flag
        170 * to false forces closure to use its own implementations when possible.
        171 *
        172 * If your javascript can be loaded by a third party site and you are wary about
        173 * relying on non-standard implementations, specify
        174 * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
        175 */
        176goog.define('goog.TRUSTED_SITE', true);
        177
        178
        179/**
        180 * Creates object stubs for a namespace. The presence of one or more
        181 * goog.provide() calls indicate that the file defines the given
        182 * objects/namespaces. Build tools also scan for provide/require statements
        183 * to discern dependencies, build dependency files (see deps.js), etc.
        184 * @see goog.require
        185 * @param {string} name Namespace provided by this file in the form
        186 * "goog.package.part".
        187 */
        188goog.provide = function(name) {
        189 if (!COMPILED) {
        190 // Ensure that the same namespace isn't provided twice. This is intended
        191 // to teach new developers that 'goog.provide' is effectively a variable
        192 // declaration. And when JSCompiler transforms goog.provide into a real
        193 // variable declaration, the compiled JS should work the same as the raw
        194 // JS--even when the raw JS uses goog.provide incorrectly.
        195 if (goog.isProvided_(name)) {
        196 throw Error('Namespace "' + name + '" already declared.');
        197 }
        198 delete goog.implicitNamespaces_[name];
        199
        200 var namespace = name;
        201 while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
        202 if (goog.getObjectByName(namespace)) {
        203 break;
        204 }
        205 goog.implicitNamespaces_[namespace] = true;
        206 }
        207 }
        208
        209 goog.exportPath_(name);
        210};
        211
        212
        213/**
        214 * Marks that the current file should only be used for testing, and never for
        215 * live code in production.
        216 *
        217 * In the case of unit tests, the message may optionally be an exact
        218 * namespace for the test (e.g. 'goog.stringTest'). The linter will then
        219 * ignore the extra provide (if not explicitly defined in the code).
        220 *
        221 * @param {string=} opt_message Optional message to add to the error that's
        222 * raised when used in production code.
        223 */
        224goog.setTestOnly = function(opt_message) {
        225 if (COMPILED && !goog.DEBUG) {
        226 opt_message = opt_message || '';
        227 throw Error('Importing test-only code into non-debug environment' +
        228 opt_message ? ': ' + opt_message : '.');
        229 }
        230};
        231
        232
        233if (!COMPILED) {
        234
        235 /**
        236 * Check if the given name has been goog.provided. This will return false for
        237 * names that are available only as implicit namespaces.
        238 * @param {string} name name of the object to look for.
        239 * @return {boolean} Whether the name has been provided.
        240 * @private
        241 */
        242 goog.isProvided_ = function(name) {
        243 return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name);
        244 };
        245
        246 /**
        247 * Namespaces implicitly defined by goog.provide. For example,
        248 * goog.provide('goog.events.Event') implicitly declares
        249 * that 'goog' and 'goog.events' must be namespaces.
        250 *
        251 * @type {Object}
        252 * @private
        253 */
        254 goog.implicitNamespaces_ = {};
        255}
        256
        257
        258/**
        259 * Returns an object based on its fully qualified external name. If you are
        260 * using a compilation pass that renames property names beware that using this
        261 * function will not find renamed properties.
        262 *
        263 * @param {string} name The fully qualified name.
        264 * @param {Object=} opt_obj The object within which to look; default is
        265 * |goog.global|.
        266 * @return {?} The value (object or primitive) or, if not found, null.
        267 */
        268goog.getObjectByName = function(name, opt_obj) {
        269 var parts = name.split('.');
        270 var cur = opt_obj || goog.global;
        271 for (var part; part = parts.shift(); ) {
        272 if (goog.isDefAndNotNull(cur[part])) {
        273 cur = cur[part];
        274 } else {
        275 return null;
        276 }
        277 }
        278 return cur;
        279};
        280
        281
        282/**
        283 * Globalizes a whole namespace, such as goog or goog.lang.
        284 *
        285 * @param {Object} obj The namespace to globalize.
        286 * @param {Object=} opt_global The object to add the properties to.
        287 * @deprecated Properties may be explicitly exported to the global scope, but
        288 * this should no longer be done in bulk.
        289 */
        290goog.globalize = function(obj, opt_global) {
        291 var global = opt_global || goog.global;
        292 for (var x in obj) {
        293 global[x] = obj[x];
        294 }
        295};
        296
        297
        298/**
        299 * Adds a dependency from a file to the files it requires.
        300 * @param {string} relPath The path to the js file.
        301 * @param {Array} provides An array of strings with the names of the objects
        302 * this file provides.
        303 * @param {Array} requires An array of strings with the names of the objects
        304 * this file requires.
        305 */
        306goog.addDependency = function(relPath, provides, requires) {
        307 if (goog.DEPENDENCIES_ENABLED) {
        308 var provide, require;
        309 var path = relPath.replace(/\\/g, '/');
        310 var deps = goog.dependencies_;
        311 for (var i = 0; provide = provides[i]; i++) {
        312 deps.nameToPath[provide] = path;
        313 if (!(path in deps.pathToNames)) {
        314 deps.pathToNames[path] = {};
        315 }
        316 deps.pathToNames[path][provide] = true;
        317 }
        318 for (var j = 0; require = requires[j]; j++) {
        319 if (!(path in deps.requires)) {
        320 deps.requires[path] = {};
        321 }
        322 deps.requires[path][require] = true;
        323 }
        324 }
        325};
        326
        327
        328
        329
        330// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal
        331// way to do "debug-mode" development. The dependency system can sometimes
        332// be confusing, as can the debug DOM loader's asyncronous nature.
        333//
        334// With the DOM loader, a call to goog.require() is not blocking -- the
        335// script will not load until some point after the current script. If a
        336// namespace is needed at runtime, it needs to be defined in a previous
        337// script, or loaded via require() with its registered dependencies.
        338// User-defined namespaces may need their own deps file. See http://go/js_deps,
        339// http://go/genjsdeps, or, externally, DepsWriter.
        340// http://code.google.com/closure/library/docs/depswriter.html
        341//
        342// Because of legacy clients, the DOM loader can't be easily removed from
        343// base.js. Work is being done to make it disableable or replaceable for
        344// different environments (DOM-less JavaScript interpreters like Rhino or V8,
        345// for example). See bootstrap/ for more information.
        346
        347
        348/**
        349 * @define {boolean} Whether to enable the debug loader.
        350 *
        351 * If enabled, a call to goog.require() will attempt to load the namespace by
        352 * appending a script tag to the DOM (if the namespace has been registered).
        353 *
        354 * If disabled, goog.require() will simply assert that the namespace has been
        355 * provided (and depend on the fact that some outside tool correctly ordered
        356 * the script).
        357 */
        358goog.define('goog.ENABLE_DEBUG_LOADER', true);
        359
        360
        361/**
        362 * Implements a system for the dynamic resolution of dependencies
        363 * that works in parallel with the BUILD system. Note that all calls
        364 * to goog.require will be stripped by the JSCompiler when the
        365 * --closure_pass option is used.
        366 * @see goog.provide
        367 * @param {string} name Namespace to include (as was given in goog.provide())
        368 * in the form "goog.package.part".
        369 */
        370goog.require = function(name) {
        371
        372 // if the object already exists we do not need do do anything
        373 // TODO(arv): If we start to support require based on file name this has
        374 // to change
        375 // TODO(arv): If we allow goog.foo.* this has to change
        376 // TODO(arv): If we implement dynamic load after page load we should probably
        377 // not remove this code for the compiled output
        378 if (!COMPILED) {
        379 if (goog.isProvided_(name)) {
        380 return;
        381 }
        382
        383 if (goog.ENABLE_DEBUG_LOADER) {
        384 var path = goog.getPathFromDeps_(name);
        385 if (path) {
        386 goog.included_[path] = true;
        387 goog.writeScripts_();
        388 return;
        389 }
        390 }
        391
        392 var errorMessage = 'goog.require could not find: ' + name;
        393 if (goog.global.console) {
        394 goog.global.console['error'](errorMessage);
        395 }
        396
        397
        398 throw Error(errorMessage);
        399
        400 }
        401};
        402
        403
        404/**
        405 * Path for included scripts
        406 * @type {string}
        407 */
        408goog.basePath = '';
        409
        410
        411/**
        412 * A hook for overriding the base path.
        413 * @type {string|undefined}
        414 */
        415goog.global.CLOSURE_BASE_PATH;
        416
        417
        418/**
        419 * Whether to write out Closure's deps file. By default,
        420 * the deps are written.
        421 * @type {boolean|undefined}
        422 */
        423goog.global.CLOSURE_NO_DEPS;
        424
        425
        426/**
        427 * A function to import a single script. This is meant to be overridden when
        428 * Closure is being run in non-HTML contexts, such as web workers. It's defined
        429 * in the global scope so that it can be set before base.js is loaded, which
        430 * allows deps.js to be imported properly.
        431 *
        432 * The function is passed the script source, which is a relative URI. It should
        433 * return true if the script was imported, false otherwise.
        434 */
        435goog.global.CLOSURE_IMPORT_SCRIPT;
        436
        437
        438/**
        439 * Null function used for default values of callbacks, etc.
        440 * @return {void} Nothing.
        441 */
        442goog.nullFunction = function() {};
        443
        444
        445/**
        446 * The identity function. Returns its first argument.
        447 *
        448 * @param {*=} opt_returnValue The single value that will be returned.
        449 * @param {...*} var_args Optional trailing arguments. These are ignored.
        450 * @return {?} The first argument. We can't know the type -- just pass it along
        451 * without type.
        452 * @deprecated Use goog.functions.identity instead.
        453 */
        454goog.identityFunction = function(opt_returnValue, var_args) {
        455 return opt_returnValue;
        456};
        457
        458
        459/**
        460 * When defining a class Foo with an abstract method bar(), you can do:
        461 *
        462 * Foo.prototype.bar = goog.abstractMethod
        463 *
        464 * Now if a subclass of Foo fails to override bar(), an error
        465 * will be thrown when bar() is invoked.
        466 *
        467 * Note: This does not take the name of the function to override as
        468 * an argument because that would make it more difficult to obfuscate
        469 * our JavaScript code.
        470 *
        471 * @type {!Function}
        472 * @throws {Error} when invoked to indicate the method should be
        473 * overridden.
        474 */
        475goog.abstractMethod = function() {
        476 throw Error('unimplemented abstract method');
        477};
        478
        479
        480/**
        481 * Adds a {@code getInstance} static method that always return the same instance
        482 * object.
        483 * @param {!Function} ctor The constructor for the class to add the static
        484 * method to.
        485 */
        486goog.addSingletonGetter = function(ctor) {
        487 ctor.getInstance = function() {
        488 if (ctor.instance_) {
        489 return ctor.instance_;
        490 }
        491 if (goog.DEBUG) {
        492 // NOTE: JSCompiler can't optimize away Array#push.
        493 goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
        494 }
        495 return ctor.instance_ = new ctor;
        496 };
        497};
        498
        499
        500/**
        501 * All singleton classes that have been instantiated, for testing. Don't read
        502 * it directly, use the {@code goog.testing.singleton} module. The compiler
        503 * removes this variable if unused.
        504 * @type {!Array.<!Function>}
        505 * @private
        506 */
        507goog.instantiatedSingletons_ = [];
        508
        509
        510/**
        511 * True if goog.dependencies_ is available.
        512 * @const {boolean}
        513 */
        514goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
        515
        516
        517if (goog.DEPENDENCIES_ENABLED) {
        518 /**
        519 * Object used to keep track of urls that have already been added. This
        520 * record allows the prevention of circular dependencies.
        521 * @type {Object}
        522 * @private
        523 */
        524 goog.included_ = {};
        525
        526
        527 /**
        528 * This object is used to keep track of dependencies and other data that is
        529 * used for loading scripts
        530 * @private
        531 * @type {Object}
        532 */
        533 goog.dependencies_ = {
        534 pathToNames: {}, // 1 to many
        535 nameToPath: {}, // 1 to 1
        536 requires: {}, // 1 to many
        537 // used when resolving dependencies to prevent us from
        538 // visiting the file twice
        539 visited: {},
        540 written: {} // used to keep track of script files we have written
        541 };
        542
        543
        544 /**
        545 * Tries to detect whether is in the context of an HTML document.
        546 * @return {boolean} True if it looks like HTML document.
        547 * @private
        548 */
        549 goog.inHtmlDocument_ = function() {
        550 var doc = goog.global.document;
        551 return typeof doc != 'undefined' &&
        552 'write' in doc; // XULDocument misses write.
        553 };
        554
        555
        556 /**
        557 * Tries to detect the base path of the base.js script that bootstraps Closure
        558 * @private
        559 */
        560 goog.findBasePath_ = function() {
        561 if (goog.global.CLOSURE_BASE_PATH) {
        562 goog.basePath = goog.global.CLOSURE_BASE_PATH;
        563 return;
        564 } else if (!goog.inHtmlDocument_()) {
        565 return;
        566 }
        567 var doc = goog.global.document;
        568 var scripts = doc.getElementsByTagName('script');
        569 // Search backwards since the current script is in almost all cases the one
        570 // that has base.js.
        571 for (var i = scripts.length - 1; i >= 0; --i) {
        572 var src = scripts[i].src;
        573 var qmark = src.lastIndexOf('?');
        574 var l = qmark == -1 ? src.length : qmark;
        575 if (src.substr(l - 7, 7) == 'base.js') {
        576 goog.basePath = src.substr(0, l - 7);
        577 return;
        578 }
        579 }
        580 };
        581
        582
        583 /**
        584 * Imports a script if, and only if, that script hasn't already been imported.
        585 * (Must be called at execution time)
        586 * @param {string} src Script source.
        587 * @private
        588 */
        589 goog.importScript_ = function(src) {
        590 var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
        591 goog.writeScriptTag_;
        592 if (!goog.dependencies_.written[src] && importScript(src)) {
        593 goog.dependencies_.written[src] = true;
        594 }
        595 };
        596
        597
        598 /**
        599 * The default implementation of the import function. Writes a script tag to
        600 * import the script.
        601 *
        602 * @param {string} src The script source.
        603 * @return {boolean} True if the script was imported, false otherwise.
        604 * @private
        605 */
        606 goog.writeScriptTag_ = function(src) {
        607 if (goog.inHtmlDocument_()) {
        608 var doc = goog.global.document;
        609
        610 // If the user tries to require a new symbol after document load,
        611 // something has gone terribly wrong. Doing a document.write would
        612 // wipe out the page.
        613 if (doc.readyState == 'complete') {
        614 // Certain test frameworks load base.js multiple times, which tries
        615 // to write deps.js each time. If that happens, just fail silently.
        616 // These frameworks wipe the page between each load of base.js, so this
        617 // is OK.
        618 var isDeps = /\bdeps.js$/.test(src);
        619 if (isDeps) {
        620 return false;
        621 } else {
        622 throw Error('Cannot write "' + src + '" after document load');
        623 }
        624 }
        625
        626 doc.write(
        627 '<script type="text/javascript" src="' + src + '"></' + 'script>');
        628 return true;
        629 } else {
        630 return false;
        631 }
        632 };
        633
        634
        635 /**
        636 * Resolves dependencies based on the dependencies added using addDependency
        637 * and calls importScript_ in the correct order.
        638 * @private
        639 */
        640 goog.writeScripts_ = function() {
        641 // the scripts we need to write this time
        642 var scripts = [];
        643 var seenScript = {};
        644 var deps = goog.dependencies_;
        645
        646 function visitNode(path) {
        647 if (path in deps.written) {
        648 return;
        649 }
        650
        651 // we have already visited this one. We can get here if we have cyclic
        652 // dependencies
        653 if (path in deps.visited) {
        654 if (!(path in seenScript)) {
        655 seenScript[path] = true;
        656 scripts.push(path);
        657 }
        658 return;
        659 }
        660
        661 deps.visited[path] = true;
        662
        663 if (path in deps.requires) {
        664 for (var requireName in deps.requires[path]) {
        665 // If the required name is defined, we assume that it was already
        666 // bootstrapped by other means.
        667 if (!goog.isProvided_(requireName)) {
        668 if (requireName in deps.nameToPath) {
        669 visitNode(deps.nameToPath[requireName]);
        670 } else {
        671 throw Error('Undefined nameToPath for ' + requireName);
        672 }
        673 }
        674 }
        675 }
        676
        677 if (!(path in seenScript)) {
        678 seenScript[path] = true;
        679 scripts.push(path);
        680 }
        681 }
        682
        683 for (var path in goog.included_) {
        684 if (!deps.written[path]) {
        685 visitNode(path);
        686 }
        687 }
        688
        689 for (var i = 0; i < scripts.length; i++) {
        690 if (scripts[i]) {
        691 goog.importScript_(goog.basePath + scripts[i]);
        692 } else {
        693 throw Error('Undefined script input');
        694 }
        695 }
        696 };
        697
        698
        699 /**
        700 * Looks at the dependency rules and tries to determine the script file that
        701 * fulfills a particular rule.
        702 * @param {string} rule In the form goog.namespace.Class or project.script.
        703 * @return {?string} Url corresponding to the rule, or null.
        704 * @private
        705 */
        706 goog.getPathFromDeps_ = function(rule) {
        707 if (rule in goog.dependencies_.nameToPath) {
        708 return goog.dependencies_.nameToPath[rule];
        709 } else {
        710 return null;
        711 }
        712 };
        713
        714 goog.findBasePath_();
        715
        716 // Allow projects to manage the deps files themselves.
        717 if (!goog.global.CLOSURE_NO_DEPS) {
        718 goog.importScript_(goog.basePath + 'deps.js');
        719 }
        720}
        721
        722
        723
        724//==============================================================================
        725// Language Enhancements
        726//==============================================================================
        727
        728
        729/**
        730 * This is a "fixed" version of the typeof operator. It differs from the typeof
        731 * operator in such a way that null returns 'null' and arrays return 'array'.
        732 * @param {*} value The value to get the type of.
        733 * @return {string} The name of the type.
        734 */
        735goog.typeOf = function(value) {
        736 var s = typeof value;
        737 if (s == 'object') {
        738 if (value) {
        739 // Check these first, so we can avoid calling Object.prototype.toString if
        740 // possible.
        741 //
        742 // IE improperly marshals tyepof across execution contexts, but a
        743 // cross-context object will still return false for "instanceof Object".
        744 if (value instanceof Array) {
        745 return 'array';
        746 } else if (value instanceof Object) {
        747 return s;
        748 }
        749
        750 // HACK: In order to use an Object prototype method on the arbitrary
        751 // value, the compiler requires the value be cast to type Object,
        752 // even though the ECMA spec explicitly allows it.
        753 var className = Object.prototype.toString.call(
        754 /** @type {Object} */ (value));
        755 // In Firefox 3.6, attempting to access iframe window objects' length
        756 // property throws an NS_ERROR_FAILURE, so we need to special-case it
        757 // here.
        758 if (className == '[object Window]') {
        759 return 'object';
        760 }
        761
        762 // We cannot always use constructor == Array or instanceof Array because
        763 // different frames have different Array objects. In IE6, if the iframe
        764 // where the array was created is destroyed, the array loses its
        765 // prototype. Then dereferencing val.splice here throws an exception, so
        766 // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
        767 // so that will work. In this case, this function will return false and
        768 // most array functions will still work because the array is still
        769 // array-like (supports length and []) even though it has lost its
        770 // prototype.
        771 // Mark Miller noticed that Object.prototype.toString
        772 // allows access to the unforgeable [[Class]] property.
        773 // 15.2.4.2 Object.prototype.toString ( )
        774 // When the toString method is called, the following steps are taken:
        775 // 1. Get the [[Class]] property of this object.
        776 // 2. Compute a string value by concatenating the three strings
        777 // "[object ", Result(1), and "]".
        778 // 3. Return Result(2).
        779 // and this behavior survives the destruction of the execution context.
        780 if ((className == '[object Array]' ||
        781 // In IE all non value types are wrapped as objects across window
        782 // boundaries (not iframe though) so we have to do object detection
        783 // for this edge case
        784 typeof value.length == 'number' &&
        785 typeof value.splice != 'undefined' &&
        786 typeof value.propertyIsEnumerable != 'undefined' &&
        787 !value.propertyIsEnumerable('splice')
        788
        789 )) {
        790 return 'array';
        791 }
        792 // HACK: There is still an array case that fails.
        793 // function ArrayImpostor() {}
        794 // ArrayImpostor.prototype = [];
        795 // var impostor = new ArrayImpostor;
        796 // this can be fixed by getting rid of the fast path
        797 // (value instanceof Array) and solely relying on
        798 // (value && Object.prototype.toString.vall(value) === '[object Array]')
        799 // but that would require many more function calls and is not warranted
        800 // unless closure code is receiving objects from untrusted sources.
        801
        802 // IE in cross-window calls does not correctly marshal the function type
        803 // (it appears just as an object) so we cannot use just typeof val ==
        804 // 'function'. However, if the object has a call property, it is a
        805 // function.
        806 if ((className == '[object Function]' ||
        807 typeof value.call != 'undefined' &&
        808 typeof value.propertyIsEnumerable != 'undefined' &&
        809 !value.propertyIsEnumerable('call'))) {
        810 return 'function';
        811 }
        812
        813
        814 } else {
        815 return 'null';
        816 }
        817
        818 } else if (s == 'function' && typeof value.call == 'undefined') {
        819 // In Safari typeof nodeList returns 'function', and on Firefox
        820 // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
        821 // and RegExps. We would like to return object for those and we can
        822 // detect an invalid function by making sure that the function
        823 // object has a call method.
        824 return 'object';
        825 }
        826 return s;
        827};
        828
        829
        830/**
        831 * Returns true if the specified value is not |undefined|.
        832 * WARNING: Do not use this to test if an object has a property. Use the in
        833 * operator instead. Additionally, this function assumes that the global
        834 * undefined variable has not been redefined.
        835 * @param {*} val Variable to test.
        836 * @return {boolean} Whether variable is defined.
        837 */
        838goog.isDef = function(val) {
        839 return val !== undefined;
        840};
        841
        842
        843/**
        844 * Returns true if the specified value is |null|
        845 * @param {*} val Variable to test.
        846 * @return {boolean} Whether variable is null.
        847 */
        848goog.isNull = function(val) {
        849 return val === null;
        850};
        851
        852
        853/**
        854 * Returns true if the specified value is defined and not null
        855 * @param {*} val Variable to test.
        856 * @return {boolean} Whether variable is defined and not null.
        857 */
        858goog.isDefAndNotNull = function(val) {
        859 // Note that undefined == null.
        860 return val != null;
        861};
        862
        863
        864/**
        865 * Returns true if the specified value is an array
        866 * @param {*} val Variable to test.
        867 * @return {boolean} Whether variable is an array.
        868 */
        869goog.isArray = function(val) {
        870 return goog.typeOf(val) == 'array';
        871};
        872
        873
        874/**
        875 * Returns true if the object looks like an array. To qualify as array like
        876 * the value needs to be either a NodeList or an object with a Number length
        877 * property.
        878 * @param {*} val Variable to test.
        879 * @return {boolean} Whether variable is an array.
        880 */
        881goog.isArrayLike = function(val) {
        882 var type = goog.typeOf(val);
        883 return type == 'array' || type == 'object' && typeof val.length == 'number';
        884};
        885
        886
        887/**
        888 * Returns true if the object looks like a Date. To qualify as Date-like
        889 * the value needs to be an object and have a getFullYear() function.
        890 * @param {*} val Variable to test.
        891 * @return {boolean} Whether variable is a like a Date.
        892 */
        893goog.isDateLike = function(val) {
        894 return goog.isObject(val) && typeof val.getFullYear == 'function';
        895};
        896
        897
        898/**
        899 * Returns true if the specified value is a string
        900 * @param {*} val Variable to test.
        901 * @return {boolean} Whether variable is a string.
        902 */
        903goog.isString = function(val) {
        904 return typeof val == 'string';
        905};
        906
        907
        908/**
        909 * Returns true if the specified value is a boolean
        910 * @param {*} val Variable to test.
        911 * @return {boolean} Whether variable is boolean.
        912 */
        913goog.isBoolean = function(val) {
        914 return typeof val == 'boolean';
        915};
        916
        917
        918/**
        919 * Returns true if the specified value is a number
        920 * @param {*} val Variable to test.
        921 * @return {boolean} Whether variable is a number.
        922 */
        923goog.isNumber = function(val) {
        924 return typeof val == 'number';
        925};
        926
        927
        928/**
        929 * Returns true if the specified value is a function
        930 * @param {*} val Variable to test.
        931 * @return {boolean} Whether variable is a function.
        932 */
        933goog.isFunction = function(val) {
        934 return goog.typeOf(val) == 'function';
        935};
        936
        937
        938/**
        939 * Returns true if the specified value is an object. This includes arrays
        940 * and functions.
        941 * @param {*} val Variable to test.
        942 * @return {boolean} Whether variable is an object.
        943 */
        944goog.isObject = function(val) {
        945 var type = typeof val;
        946 return type == 'object' && val != null || type == 'function';
        947 // return Object(val) === val also works, but is slower, especially if val is
        948 // not an object.
        949};
        950
        951
        952/**
        953 * Gets a unique ID for an object. This mutates the object so that further
        954 * calls with the same object as a parameter returns the same value. The unique
        955 * ID is guaranteed to be unique across the current session amongst objects that
        956 * are passed into {@code getUid}. There is no guarantee that the ID is unique
        957 * or consistent across sessions. It is unsafe to generate unique ID for
        958 * function prototypes.
        959 *
        960 * @param {Object} obj The object to get the unique ID for.
        961 * @return {number} The unique ID for the object.
        962 */
        963goog.getUid = function(obj) {
        964 // TODO(arv): Make the type stricter, do not accept null.
        965
        966 // In Opera window.hasOwnProperty exists but always returns false so we avoid
        967 // using it. As a consequence the unique ID generated for BaseClass.prototype
        968 // and SubClass.prototype will be the same.
        969 return obj[goog.UID_PROPERTY_] ||
        970 (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
        971};
        972
        973
        974/**
        975 * Removes the unique ID from an object. This is useful if the object was
        976 * previously mutated using {@code goog.getUid} in which case the mutation is
        977 * undone.
        978 * @param {Object} obj The object to remove the unique ID field from.
        979 */
        980goog.removeUid = function(obj) {
        981 // TODO(arv): Make the type stricter, do not accept null.
        982
        983 // DOM nodes in IE are not instance of Object and throws exception
        984 // for delete. Instead we try to use removeAttribute
        985 if ('removeAttribute' in obj) {
        986 obj.removeAttribute(goog.UID_PROPERTY_);
        987 }
        988 /** @preserveTry */
        989 try {
        990 delete obj[goog.UID_PROPERTY_];
        991 } catch (ex) {
        992 }
        993};
        994
        995
        996/**
        997 * Name for unique ID property. Initialized in a way to help avoid collisions
        998 * with other closure javascript on the same page.
        999 * @type {string}
        1000 * @private
        1001 */
        1002goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
        1003
        1004
        1005/**
        1006 * Counter for UID.
        1007 * @type {number}
        1008 * @private
        1009 */
        1010goog.uidCounter_ = 0;
        1011
        1012
        1013/**
        1014 * Adds a hash code field to an object. The hash code is unique for the
        1015 * given object.
        1016 * @param {Object} obj The object to get the hash code for.
        1017 * @return {number} The hash code for the object.
        1018 * @deprecated Use goog.getUid instead.
        1019 */
        1020goog.getHashCode = goog.getUid;
        1021
        1022
        1023/**
        1024 * Removes the hash code field from an object.
        1025 * @param {Object} obj The object to remove the field from.
        1026 * @deprecated Use goog.removeUid instead.
        1027 */
        1028goog.removeHashCode = goog.removeUid;
        1029
        1030
        1031/**
        1032 * Clones a value. The input may be an Object, Array, or basic type. Objects and
        1033 * arrays will be cloned recursively.
        1034 *
        1035 * WARNINGS:
        1036 * <code>goog.cloneObject</code> does not detect reference loops. Objects that
        1037 * refer to themselves will cause infinite recursion.
        1038 *
        1039 * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
        1040 * UIDs created by <code>getUid</code> into cloned results.
        1041 *
        1042 * @param {*} obj The value to clone.
        1043 * @return {*} A clone of the input value.
        1044 * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
        1045 */
        1046goog.cloneObject = function(obj) {
        1047 var type = goog.typeOf(obj);
        1048 if (type == 'object' || type == 'array') {
        1049 if (obj.clone) {
        1050 return obj.clone();
        1051 }
        1052 var clone = type == 'array' ? [] : {};
        1053 for (var key in obj) {
        1054 clone[key] = goog.cloneObject(obj[key]);
        1055 }
        1056 return clone;
        1057 }
        1058
        1059 return obj;
        1060};
        1061
        1062
        1063/**
        1064 * A native implementation of goog.bind.
        1065 * @param {Function} fn A function to partially apply.
        1066 * @param {Object|undefined} selfObj Specifies the object which |this| should
        1067 * point to when the function is run.
        1068 * @param {...*} var_args Additional arguments that are partially
        1069 * applied to the function.
        1070 * @return {!Function} A partially-applied form of the function bind() was
        1071 * invoked as a method of.
        1072 * @private
        1073 * @suppress {deprecated} The compiler thinks that Function.prototype.bind
        1074 * is deprecated because some people have declared a pure-JS version.
        1075 * Only the pure-JS version is truly deprecated.
        1076 */
        1077goog.bindNative_ = function(fn, selfObj, var_args) {
        1078 return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
        1079};
        1080
        1081
        1082/**
        1083 * A pure-JS implementation of goog.bind.
        1084 * @param {Function} fn A function to partially apply.
        1085 * @param {Object|undefined} selfObj Specifies the object which |this| should
        1086 * point to when the function is run.
        1087 * @param {...*} var_args Additional arguments that are partially
        1088 * applied to the function.
        1089 * @return {!Function} A partially-applied form of the function bind() was
        1090 * invoked as a method of.
        1091 * @private
        1092 */
        1093goog.bindJs_ = function(fn, selfObj, var_args) {
        1094 if (!fn) {
        1095 throw new Error();
        1096 }
        1097
        1098 if (arguments.length > 2) {
        1099 var boundArgs = Array.prototype.slice.call(arguments, 2);
        1100 return function() {
        1101 // Prepend the bound arguments to the current arguments.
        1102 var newArgs = Array.prototype.slice.call(arguments);
        1103 Array.prototype.unshift.apply(newArgs, boundArgs);
        1104 return fn.apply(selfObj, newArgs);
        1105 };
        1106
        1107 } else {
        1108 return function() {
        1109 return fn.apply(selfObj, arguments);
        1110 };
        1111 }
        1112};
        1113
        1114
        1115/**
        1116 * Partially applies this function to a particular 'this object' and zero or
        1117 * more arguments. The result is a new function with some arguments of the first
        1118 * function pre-filled and the value of |this| 'pre-specified'.<br><br>
        1119 *
        1120 * Remaining arguments specified at call-time are appended to the pre-
        1121 * specified ones.<br><br>
        1122 *
        1123 * Also see: {@link #partial}.<br><br>
        1124 *
        1125 * Usage:
        1126 * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
        1127 * barMethBound('arg3', 'arg4');</pre>
        1128 *
        1129 * @param {?function(this:T, ...)} fn A function to partially apply.
        1130 * @param {T} selfObj Specifies the object which |this| should
        1131 * point to when the function is run.
        1132 * @param {...*} var_args Additional arguments that are partially
        1133 * applied to the function.
        1134 * @return {!Function} A partially-applied form of the function bind() was
        1135 * invoked as a method of.
        1136 * @template T
        1137 * @suppress {deprecated} See above.
        1138 */
        1139goog.bind = function(fn, selfObj, var_args) {
        1140 // TODO(nicksantos): narrow the type signature.
        1141 if (Function.prototype.bind &&
        1142 // NOTE(nicksantos): Somebody pulled base.js into the default
        1143 // Chrome extension environment. This means that for Chrome extensions,
        1144 // they get the implementation of Function.prototype.bind that
        1145 // calls goog.bind instead of the native one. Even worse, we don't want
        1146 // to introduce a circular dependency between goog.bind and
        1147 // Function.prototype.bind, so we have to hack this to make sure it
        1148 // works correctly.
        1149 Function.prototype.bind.toString().indexOf('native code') != -1) {
        1150 goog.bind = goog.bindNative_;
        1151 } else {
        1152 goog.bind = goog.bindJs_;
        1153 }
        1154 return goog.bind.apply(null, arguments);
        1155};
        1156
        1157
        1158/**
        1159 * Like bind(), except that a 'this object' is not required. Useful when the
        1160 * target function is already bound.
        1161 *
        1162 * Usage:
        1163 * var g = partial(f, arg1, arg2);
        1164 * g(arg3, arg4);
        1165 *
        1166 * @param {Function} fn A function to partially apply.
        1167 * @param {...*} var_args Additional arguments that are partially
        1168 * applied to fn.
        1169 * @return {!Function} A partially-applied form of the function bind() was
        1170 * invoked as a method of.
        1171 */
        1172goog.partial = function(fn, var_args) {
        1173 var args = Array.prototype.slice.call(arguments, 1);
        1174 return function() {
        1175 // Prepend the bound arguments to the current arguments.
        1176 var newArgs = Array.prototype.slice.call(arguments);
        1177 newArgs.unshift.apply(newArgs, args);
        1178 return fn.apply(this, newArgs);
        1179 };
        1180};
        1181
        1182
        1183/**
        1184 * Copies all the members of a source object to a target object. This method
        1185 * does not work on all browsers for all objects that contain keys such as
        1186 * toString or hasOwnProperty. Use goog.object.extend for this purpose.
        1187 * @param {Object} target Target.
        1188 * @param {Object} source Source.
        1189 */
        1190goog.mixin = function(target, source) {
        1191 for (var x in source) {
        1192 target[x] = source[x];
        1193 }
        1194
        1195 // For IE7 or lower, the for-in-loop does not contain any properties that are
        1196 // not enumerable on the prototype object (for example, isPrototypeOf from
        1197 // Object.prototype) but also it will not include 'replace' on objects that
        1198 // extend String and change 'replace' (not that it is common for anyone to
        1199 // extend anything except Object).
        1200};
        1201
        1202
        1203/**
        1204 * @return {number} An integer value representing the number of milliseconds
        1205 * between midnight, January 1, 1970 and the current time.
        1206 */
        1207goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
        1208 // Unary plus operator converts its operand to a number which in the case of
        1209 // a date is done by calling getTime().
        1210 return +new Date();
        1211});
        1212
        1213
        1214/**
        1215 * Evals javascript in the global scope. In IE this uses execScript, other
        1216 * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
        1217 * global scope (for example, in Safari), appends a script tag instead.
        1218 * Throws an exception if neither execScript or eval is defined.
        1219 * @param {string} script JavaScript string.
        1220 */
        1221goog.globalEval = function(script) {
        1222 if (goog.global.execScript) {
        1223 goog.global.execScript(script, 'JavaScript');
        1224 } else if (goog.global.eval) {
        1225 // Test to see if eval works
        1226 if (goog.evalWorksForGlobals_ == null) {
        1227 goog.global.eval('var _et_ = 1;');
        1228 if (typeof goog.global['_et_'] != 'undefined') {
        1229 delete goog.global['_et_'];
        1230 goog.evalWorksForGlobals_ = true;
        1231 } else {
        1232 goog.evalWorksForGlobals_ = false;
        1233 }
        1234 }
        1235
        1236 if (goog.evalWorksForGlobals_) {
        1237 goog.global.eval(script);
        1238 } else {
        1239 var doc = goog.global.document;
        1240 var scriptElt = doc.createElement('script');
        1241 scriptElt.type = 'text/javascript';
        1242 scriptElt.defer = false;
        1243 // Note(user): can't use .innerHTML since "t('<test>')" will fail and
        1244 // .text doesn't work in Safari 2. Therefore we append a text node.
        1245 scriptElt.appendChild(doc.createTextNode(script));
        1246 doc.body.appendChild(scriptElt);
        1247 doc.body.removeChild(scriptElt);
        1248 }
        1249 } else {
        1250 throw Error('goog.globalEval not available');
        1251 }
        1252};
        1253
        1254
        1255/**
        1256 * Indicates whether or not we can call 'eval' directly to eval code in the
        1257 * global scope. Set to a Boolean by the first call to goog.globalEval (which
        1258 * empirically tests whether eval works for globals). @see goog.globalEval
        1259 * @type {?boolean}
        1260 * @private
        1261 */
        1262goog.evalWorksForGlobals_ = null;
        1263
        1264
        1265/**
        1266 * Optional map of CSS class names to obfuscated names used with
        1267 * goog.getCssName().
        1268 * @type {Object|undefined}
        1269 * @private
        1270 * @see goog.setCssNameMapping
        1271 */
        1272goog.cssNameMapping_;
        1273
        1274
        1275/**
        1276 * Optional obfuscation style for CSS class names. Should be set to either
        1277 * 'BY_WHOLE' or 'BY_PART' if defined.
        1278 * @type {string|undefined}
        1279 * @private
        1280 * @see goog.setCssNameMapping
        1281 */
        1282goog.cssNameMappingStyle_;
        1283
        1284
        1285/**
        1286 * Handles strings that are intended to be used as CSS class names.
        1287 *
        1288 * This function works in tandem with @see goog.setCssNameMapping.
        1289 *
        1290 * Without any mapping set, the arguments are simple joined with a
        1291 * hyphen and passed through unaltered.
        1292 *
        1293 * When there is a mapping, there are two possible styles in which
        1294 * these mappings are used. In the BY_PART style, each part (i.e. in
        1295 * between hyphens) of the passed in css name is rewritten according
        1296 * to the map. In the BY_WHOLE style, the full css name is looked up in
        1297 * the map directly. If a rewrite is not specified by the map, the
        1298 * compiler will output a warning.
        1299 *
        1300 * When the mapping is passed to the compiler, it will replace calls
        1301 * to goog.getCssName with the strings from the mapping, e.g.
        1302 * var x = goog.getCssName('foo');
        1303 * var y = goog.getCssName(this.baseClass, 'active');
        1304 * becomes:
        1305 * var x= 'foo';
        1306 * var y = this.baseClass + '-active';
        1307 *
        1308 * If one argument is passed it will be processed, if two are passed
        1309 * only the modifier will be processed, as it is assumed the first
        1310 * argument was generated as a result of calling goog.getCssName.
        1311 *
        1312 * @param {string} className The class name.
        1313 * @param {string=} opt_modifier A modifier to be appended to the class name.
        1314 * @return {string} The class name or the concatenation of the class name and
        1315 * the modifier.
        1316 */
        1317goog.getCssName = function(className, opt_modifier) {
        1318 var getMapping = function(cssName) {
        1319 return goog.cssNameMapping_[cssName] || cssName;
        1320 };
        1321
        1322 var renameByParts = function(cssName) {
        1323 // Remap all the parts individually.
        1324 var parts = cssName.split('-');
        1325 var mapped = [];
        1326 for (var i = 0; i < parts.length; i++) {
        1327 mapped.push(getMapping(parts[i]));
        1328 }
        1329 return mapped.join('-');
        1330 };
        1331
        1332 var rename;
        1333 if (goog.cssNameMapping_) {
        1334 rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
        1335 getMapping : renameByParts;
        1336 } else {
        1337 rename = function(a) {
        1338 return a;
        1339 };
        1340 }
        1341
        1342 if (opt_modifier) {
        1343 return className + '-' + rename(opt_modifier);
        1344 } else {
        1345 return rename(className);
        1346 }
        1347};
        1348
        1349
        1350/**
        1351 * Sets the map to check when returning a value from goog.getCssName(). Example:
        1352 * <pre>
        1353 * goog.setCssNameMapping({
        1354 * "goog": "a",
        1355 * "disabled": "b",
        1356 * });
        1357 *
        1358 * var x = goog.getCssName('goog');
        1359 * // The following evaluates to: "a a-b".
        1360 * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
        1361 * </pre>
        1362 * When declared as a map of string literals to string literals, the JSCompiler
        1363 * will replace all calls to goog.getCssName() using the supplied map if the
        1364 * --closure_pass flag is set.
        1365 *
        1366 * @param {!Object} mapping A map of strings to strings where keys are possible
        1367 * arguments to goog.getCssName() and values are the corresponding values
        1368 * that should be returned.
        1369 * @param {string=} opt_style The style of css name mapping. There are two valid
        1370 * options: 'BY_PART', and 'BY_WHOLE'.
        1371 * @see goog.getCssName for a description.
        1372 */
        1373goog.setCssNameMapping = function(mapping, opt_style) {
        1374 goog.cssNameMapping_ = mapping;
        1375 goog.cssNameMappingStyle_ = opt_style;
        1376};
        1377
        1378
        1379/**
        1380 * To use CSS renaming in compiled mode, one of the input files should have a
        1381 * call to goog.setCssNameMapping() with an object literal that the JSCompiler
        1382 * can extract and use to replace all calls to goog.getCssName(). In uncompiled
        1383 * mode, JavaScript code should be loaded before this base.js file that declares
        1384 * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
        1385 * to ensure that the mapping is loaded before any calls to goog.getCssName()
        1386 * are made in uncompiled mode.
        1387 *
        1388 * A hook for overriding the CSS name mapping.
        1389 * @type {Object|undefined}
        1390 */
        1391goog.global.CLOSURE_CSS_NAME_MAPPING;
        1392
        1393
        1394if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
        1395 // This does not call goog.setCssNameMapping() because the JSCompiler
        1396 // requires that goog.setCssNameMapping() be called with an object literal.
        1397 goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
        1398}
        1399
        1400
        1401/**
        1402 * Gets a localized message.
        1403 *
        1404 * This function is a compiler primitive. If you give the compiler a localized
        1405 * message bundle, it will replace the string at compile-time with a localized
        1406 * version, and expand goog.getMsg call to a concatenated string.
        1407 *
        1408 * Messages must be initialized in the form:
        1409 * <code>
        1410 * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
        1411 * </code>
        1412 *
        1413 * @param {string} str Translatable string, places holders in the form {$foo}.
        1414 * @param {Object=} opt_values Map of place holder name to value.
        1415 * @return {string} message with placeholders filled.
        1416 */
        1417goog.getMsg = function(str, opt_values) {
        1418 var values = opt_values || {};
        1419 for (var key in values) {
        1420 var value = ('' + values[key]).replace(/\$/g, '$$$$');
        1421 str = str.replace(new RegExp('\\{\\$' + key + '\\}', 'gi'), value);
        1422 }
        1423 return str;
        1424};
        1425
        1426
        1427/**
        1428 * Gets a localized message. If the message does not have a translation, gives a
        1429 * fallback message.
        1430 *
        1431 * This is useful when introducing a new message that has not yet been
        1432 * translated into all languages.
        1433 *
        1434 * This function is a compiler primtive. Must be used in the form:
        1435 * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
        1436 * where MSG_A and MSG_B were initialized with goog.getMsg.
        1437 *
        1438 * @param {string} a The preferred message.
        1439 * @param {string} b The fallback message.
        1440 * @return {string} The best translated message.
        1441 */
        1442goog.getMsgWithFallback = function(a, b) {
        1443 return a;
        1444};
        1445
        1446
        1447/**
        1448 * Exposes an unobfuscated global namespace path for the given object.
        1449 * Note that fields of the exported object *will* be obfuscated,
        1450 * unless they are exported in turn via this function or
        1451 * goog.exportProperty
        1452 *
        1453 * <p>Also handy for making public items that are defined in anonymous
        1454 * closures.
        1455 *
        1456 * ex. goog.exportSymbol('public.path.Foo', Foo);
        1457 *
        1458 * ex. goog.exportSymbol('public.path.Foo.staticFunction',
        1459 * Foo.staticFunction);
        1460 * public.path.Foo.staticFunction();
        1461 *
        1462 * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
        1463 * Foo.prototype.myMethod);
        1464 * new public.path.Foo().myMethod();
        1465 *
        1466 * @param {string} publicPath Unobfuscated name to export.
        1467 * @param {*} object Object the name should point to.
        1468 * @param {Object=} opt_objectToExportTo The object to add the path to; default
        1469 * is |goog.global|.
        1470 */
        1471goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
        1472 goog.exportPath_(publicPath, object, opt_objectToExportTo);
        1473};
        1474
        1475
        1476/**
        1477 * Exports a property unobfuscated into the object's namespace.
        1478 * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
        1479 * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
        1480 * @param {Object} object Object whose static property is being exported.
        1481 * @param {string} publicName Unobfuscated name to export.
        1482 * @param {*} symbol Object the name should point to.
        1483 */
        1484goog.exportProperty = function(object, publicName, symbol) {
        1485 object[publicName] = symbol;
        1486};
        1487
        1488
        1489/**
        1490 * Inherit the prototype methods from one constructor into another.
        1491 *
        1492 * Usage:
        1493 * <pre>
        1494 * function ParentClass(a, b) { }
        1495 * ParentClass.prototype.foo = function(a) { }
        1496 *
        1497 * function ChildClass(a, b, c) {
        1498 * goog.base(this, a, b);
        1499 * }
        1500 * goog.inherits(ChildClass, ParentClass);
        1501 *
        1502 * var child = new ChildClass('a', 'b', 'see');
        1503 * child.foo(); // works
        1504 * </pre>
        1505 *
        1506 * In addition, a superclass' implementation of a method can be invoked
        1507 * as follows:
        1508 *
        1509 * <pre>
        1510 * ChildClass.prototype.foo = function(a) {
        1511 * ChildClass.superClass_.foo.call(this, a);
        1512 * // other code
        1513 * };
        1514 * </pre>
        1515 *
        1516 * @param {Function} childCtor Child class.
        1517 * @param {Function} parentCtor Parent class.
        1518 */
        1519goog.inherits = function(childCtor, parentCtor) {
        1520 /** @constructor */
        1521 function tempCtor() {};
        1522 tempCtor.prototype = parentCtor.prototype;
        1523 childCtor.superClass_ = parentCtor.prototype;
        1524 childCtor.prototype = new tempCtor();
        1525 /** @override */
        1526 childCtor.prototype.constructor = childCtor;
        1527};
        1528
        1529
        1530/**
        1531 * Call up to the superclass.
        1532 *
        1533 * If this is called from a constructor, then this calls the superclass
        1534 * contructor with arguments 1-N.
        1535 *
        1536 * If this is called from a prototype method, then you must pass
        1537 * the name of the method as the second argument to this function. If
        1538 * you do not, you will get a runtime error. This calls the superclass'
        1539 * method with arguments 2-N.
        1540 *
        1541 * This function only works if you use goog.inherits to express
        1542 * inheritance relationships between your classes.
        1543 *
        1544 * This function is a compiler primitive. At compile-time, the
        1545 * compiler will do macro expansion to remove a lot of
        1546 * the extra overhead that this function introduces. The compiler
        1547 * will also enforce a lot of the assumptions that this function
        1548 * makes, and treat it as a compiler error if you break them.
        1549 *
        1550 * @param {!Object} me Should always be "this".
        1551 * @param {*=} opt_methodName The method name if calling a super method.
        1552 * @param {...*} var_args The rest of the arguments.
        1553 * @return {*} The return value of the superclass method.
        1554 */
        1555goog.base = function(me, opt_methodName, var_args) {
        1556 var caller = arguments.callee.caller;
        1557
        1558 if (goog.DEBUG) {
        1559 if (!caller) {
        1560 throw Error('arguments.caller not defined. goog.base() expects not ' +
        1561 'to be running in strict mode. See ' +
        1562 'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
        1563 }
        1564 }
        1565
        1566 if (caller.superClass_) {
        1567 // This is a constructor. Call the superclass constructor.
        1568 return caller.superClass_.constructor.apply(
        1569 me, Array.prototype.slice.call(arguments, 1));
        1570 }
        1571
        1572 var args = Array.prototype.slice.call(arguments, 2);
        1573 var foundCaller = false;
        1574 for (var ctor = me.constructor;
        1575 ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
        1576 if (ctor.prototype[opt_methodName] === caller) {
        1577 foundCaller = true;
        1578 } else if (foundCaller) {
        1579 return ctor.prototype[opt_methodName].apply(me, args);
        1580 }
        1581 }
        1582
        1583 // If we did not find the caller in the prototype chain,
        1584 // then one of two things happened:
        1585 // 1) The caller is an instance method.
        1586 // 2) This method was not called by the right caller.
        1587 if (me[opt_methodName] === caller) {
        1588 return me.constructor.prototype[opt_methodName].apply(me, args);
        1589 } else {
        1590 throw Error(
        1591 'goog.base called from a method of one name ' +
        1592 'to a method of a different name');
        1593 }
        1594};
        1595
        1596
        1597/**
        1598 * Allow for aliasing within scope functions. This function exists for
        1599 * uncompiled code - in compiled code the calls will be inlined and the
        1600 * aliases applied. In uncompiled code the function is simply run since the
        1601 * aliases as written are valid JavaScript.
        1602 * @param {function()} fn Function to call. This function can contain aliases
        1603 * to namespaces (e.g. "var dom = goog.dom") or classes
        1604 * (e.g. "var Timer = goog.Timer").
        1605 */
        1606goog.scope = function(fn) {
        1607 fn.call(goog.global);
        1608};
        1609
        1610
        \ No newline at end of file +base.js

        lib/goog/base.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Bootstrap for the Google JS Library (Closure).
        17 *
        18 * In uncompiled mode base.js will write out Closure's deps file, unless the
        19 * global <code>CLOSURE_NO_DEPS</code> is set to true. This allows projects to
        20 * include their own deps file(s) from different locations.
        21 *
        22 * @author arv@google.com (Erik Arvidsson)
        23 *
        24 * @provideGoog
        25 */
        26
        27
        28/**
        29 * @define {boolean} Overridden to true by the compiler when --closure_pass
        30 * or --mark_as_compiled is specified.
        31 */
        32var COMPILED = false;
        33
        34
        35/**
        36 * Base namespace for the Closure library. Checks to see goog is already
        37 * defined in the current scope before assigning to prevent clobbering if
        38 * base.js is loaded more than once.
        39 *
        40 * @const
        41 */
        42var goog = goog || {};
        43
        44
        45/**
        46 * Reference to the global context. In most cases this will be 'window'.
        47 */
        48goog.global = this;
        49
        50
        51/**
        52 * A hook for overriding the define values in uncompiled mode.
        53 *
        54 * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
        55 * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
        56 * {@code goog.define} will use the value instead of the default value. This
        57 * allows flags to be overwritten without compilation (this is normally
        58 * accomplished with the compiler's "define" flag).
        59 *
        60 * Example:
        61 * <pre>
        62 * var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
        63 * </pre>
        64 *
        65 * @type {Object<string, (string|number|boolean)>|undefined}
        66 */
        67goog.global.CLOSURE_UNCOMPILED_DEFINES;
        68
        69
        70/**
        71 * A hook for overriding the define values in uncompiled or compiled mode,
        72 * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In
        73 * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
        74 *
        75 * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
        76 * string literals or the compiler will emit an error.
        77 *
        78 * While any @define value may be set, only those set with goog.define will be
        79 * effective for uncompiled code.
        80 *
        81 * Example:
        82 * <pre>
        83 * var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
        84 * </pre>
        85 *
        86 * @type {Object<string, (string|number|boolean)>|undefined}
        87 */
        88goog.global.CLOSURE_DEFINES;
        89
        90
        91/**
        92 * Returns true if the specified value is not undefined.
        93 * WARNING: Do not use this to test if an object has a property. Use the in
        94 * operator instead.
        95 *
        96 * @param {?} val Variable to test.
        97 * @return {boolean} Whether variable is defined.
        98 */
        99goog.isDef = function(val) {
        100 // void 0 always evaluates to undefined and hence we do not need to depend on
        101 // the definition of the global variable named 'undefined'.
        102 return val !== void 0;
        103};
        104
        105
        106/**
        107 * Builds an object structure for the provided namespace path, ensuring that
        108 * names that already exist are not overwritten. For example:
        109 * "a.b.c" -> a = {};a.b={};a.b.c={};
        110 * Used by goog.provide and goog.exportSymbol.
        111 * @param {string} name name of the object that this file defines.
        112 * @param {*=} opt_object the object to expose at the end of the path.
        113 * @param {Object=} opt_objectToExportTo The object to add the path to; default
        114 * is |goog.global|.
        115 * @private
        116 */
        117goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
        118 var parts = name.split('.');
        119 var cur = opt_objectToExportTo || goog.global;
        120
        121 // Internet Explorer exhibits strange behavior when throwing errors from
        122 // methods externed in this manner. See the testExportSymbolExceptions in
        123 // base_test.html for an example.
        124 if (!(parts[0] in cur) && cur.execScript) {
        125 cur.execScript('var ' + parts[0]);
        126 }
        127
        128 // Certain browsers cannot parse code in the form for((a in b); c;);
        129 // This pattern is produced by the JSCompiler when it collapses the
        130 // statement above into the conditional loop below. To prevent this from
        131 // happening, use a for-loop and reserve the init logic as below.
        132
        133 // Parentheses added to eliminate strict JS warning in Firefox.
        134 for (var part; parts.length && (part = parts.shift());) {
        135 if (!parts.length && goog.isDef(opt_object)) {
        136 // last part and we have an object; use it
        137 cur[part] = opt_object;
        138 } else if (cur[part]) {
        139 cur = cur[part];
        140 } else {
        141 cur = cur[part] = {};
        142 }
        143 }
        144};
        145
        146
        147/**
        148 * Defines a named value. In uncompiled mode, the value is retrieved from
        149 * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
        150 * has the property specified, and otherwise used the defined defaultValue.
        151 * When compiled the default can be overridden using the compiler
        152 * options or the value set in the CLOSURE_DEFINES object.
        153 *
        154 * @param {string} name The distinguished name to provide.
        155 * @param {string|number|boolean} defaultValue
        156 */
        157goog.define = function(name, defaultValue) {
        158 var value = defaultValue;
        159 if (!COMPILED) {
        160 if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
        161 Object.prototype.hasOwnProperty.call(
        162 goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
        163 value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
        164 } else if (goog.global.CLOSURE_DEFINES &&
        165 Object.prototype.hasOwnProperty.call(
        166 goog.global.CLOSURE_DEFINES, name)) {
        167 value = goog.global.CLOSURE_DEFINES[name];
        168 }
        169 }
        170 goog.exportPath_(name, value);
        171};
        172
        173
        174/**
        175 * @define {boolean} DEBUG is provided as a convenience so that debugging code
        176 * that should not be included in a production js_binary can be easily stripped
        177 * by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
        178 * toString() methods should be declared inside an "if (goog.DEBUG)" conditional
        179 * because they are generally used for debugging purposes and it is difficult
        180 * for the JSCompiler to statically determine whether they are used.
        181 */
        182goog.define('goog.DEBUG', true);
        183
        184
        185/**
        186 * @define {string} LOCALE defines the locale being used for compilation. It is
        187 * used to select locale specific data to be compiled in js binary. BUILD rule
        188 * can specify this value by "--define goog.LOCALE=<locale_name>" as JSCompiler
        189 * option.
        190 *
        191 * Take into account that the locale code format is important. You should use
        192 * the canonical Unicode format with hyphen as a delimiter. Language must be
        193 * lowercase, Language Script - Capitalized, Region - UPPERCASE.
        194 * There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
        195 *
        196 * See more info about locale codes here:
        197 * http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
        198 *
        199 * For language codes you should use values defined by ISO 693-1. See it here
        200 * http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
        201 * this rule: the Hebrew language. For legacy reasons the old code (iw) should
        202 * be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
        203 */
        204goog.define('goog.LOCALE', 'en'); // default to en
        205
        206
        207/**
        208 * @define {boolean} Whether this code is running on trusted sites.
        209 *
        210 * On untrusted sites, several native functions can be defined or overridden by
        211 * external libraries like Prototype, Datejs, and JQuery and setting this flag
        212 * to false forces closure to use its own implementations when possible.
        213 *
        214 * If your JavaScript can be loaded by a third party site and you are wary about
        215 * relying on non-standard implementations, specify
        216 * "--define goog.TRUSTED_SITE=false" to the JSCompiler.
        217 */
        218goog.define('goog.TRUSTED_SITE', true);
        219
        220
        221/**
        222 * @define {boolean} Whether a project is expected to be running in strict mode.
        223 *
        224 * This define can be used to trigger alternate implementations compatible with
        225 * running in EcmaScript Strict mode or warn about unavailable functionality.
        226 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
        227 *
        228 */
        229goog.define('goog.STRICT_MODE_COMPATIBLE', false);
        230
        231
        232/**
        233 * @define {boolean} Whether code that calls {@link goog.setTestOnly} should
        234 * be disallowed in the compilation unit.
        235 */
        236goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);
        237
        238
        239/**
        240 * @define {boolean} Whether to use a Chrome app CSP-compliant method for
        241 * loading scripts via goog.require. @see appendScriptSrcNode_.
        242 */
        243goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
        244
        245
        246/**
        247 * Defines a namespace in Closure.
        248 *
        249 * A namespace may only be defined once in a codebase. It may be defined using
        250 * goog.provide() or goog.module().
        251 *
        252 * The presence of one or more goog.provide() calls in a file indicates
        253 * that the file defines the given objects/namespaces.
        254 * Provided symbols must not be null or undefined.
        255 *
        256 * In addition, goog.provide() creates the object stubs for a namespace
        257 * (for example, goog.provide("goog.foo.bar") will create the object
        258 * goog.foo.bar if it does not already exist).
        259 *
        260 * Build tools also scan for provide/require/module statements
        261 * to discern dependencies, build dependency files (see deps.js), etc.
        262 *
        263 * @see goog.require
        264 * @see goog.module
        265 * @param {string} name Namespace provided by this file in the form
        266 * "goog.package.part".
        267 */
        268goog.provide = function(name) {
        269 if (!COMPILED) {
        270 // Ensure that the same namespace isn't provided twice.
        271 // A goog.module/goog.provide maps a goog.require to a specific file
        272 if (goog.isProvided_(name)) {
        273 throw Error('Namespace "' + name + '" already declared.');
        274 }
        275 }
        276
        277 goog.constructNamespace_(name);
        278};
        279
        280
        281/**
        282 * @param {string} name Namespace provided by this file in the form
        283 * "goog.package.part".
        284 * @param {Object=} opt_obj The object to embed in the namespace.
        285 * @private
        286 */
        287goog.constructNamespace_ = function(name, opt_obj) {
        288 if (!COMPILED) {
        289 delete goog.implicitNamespaces_[name];
        290
        291 var namespace = name;
        292 while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
        293 if (goog.getObjectByName(namespace)) {
        294 break;
        295 }
        296 goog.implicitNamespaces_[namespace] = true;
        297 }
        298 }
        299
        300 goog.exportPath_(name, opt_obj);
        301};
        302
        303
        304/**
        305 * Module identifier validation regexp.
        306 * Note: This is a conservative check, it is very possible to be more lenient,
        307 * the primary exclusion here is "/" and "\" and a leading ".", these
        308 * restrictions are intended to leave the door open for using goog.require
        309 * with relative file paths rather than module identifiers.
        310 * @private
        311 */
        312goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
        313
        314
        315/**
        316 * Defines a module in Closure.
        317 *
        318 * Marks that this file must be loaded as a module and claims the namespace.
        319 *
        320 * A namespace may only be defined once in a codebase. It may be defined using
        321 * goog.provide() or goog.module().
        322 *
        323 * goog.module() has three requirements:
        324 * - goog.module may not be used in the same file as goog.provide.
        325 * - goog.module must be the first statement in the file.
        326 * - only one goog.module is allowed per file.
        327 *
        328 * When a goog.module annotated file is loaded, it is enclosed in
        329 * a strict function closure. This means that:
        330 * - any variables declared in a goog.module file are private to the file
        331 * (not global), though the compiler is expected to inline the module.
        332 * - The code must obey all the rules of "strict" JavaScript.
        333 * - the file will be marked as "use strict"
        334 *
        335 * NOTE: unlike goog.provide, goog.module does not declare any symbols by
        336 * itself. If declared symbols are desired, use
        337 * goog.module.declareLegacyNamespace().
        338 *
        339 *
        340 * See the public goog.module proposal: http://goo.gl/Va1hin
        341 *
        342 * @param {string} name Namespace provided by this file in the form
        343 * "goog.package.part", is expected but not required.
        344 */
        345goog.module = function(name) {
        346 if (!goog.isString(name) ||
        347 !name ||
        348 name.search(goog.VALID_MODULE_RE_) == -1) {
        349 throw Error('Invalid module identifier');
        350 }
        351 if (!goog.isInModuleLoader_()) {
        352 throw Error('Module ' + name + ' has been loaded incorrectly.');
        353 }
        354 if (goog.moduleLoaderState_.moduleName) {
        355 throw Error('goog.module may only be called once per module.');
        356 }
        357
        358 // Store the module name for the loader.
        359 goog.moduleLoaderState_.moduleName = name;
        360 if (!COMPILED) {
        361 // Ensure that the same namespace isn't provided twice.
        362 // A goog.module/goog.provide maps a goog.require to a specific file
        363 if (goog.isProvided_(name)) {
        364 throw Error('Namespace "' + name + '" already declared.');
        365 }
        366 delete goog.implicitNamespaces_[name];
        367 }
        368};
        369
        370
        371/**
        372 * @param {string} name The module identifier.
        373 * @return {?} The module exports for an already loaded module or null.
        374 *
        375 * Note: This is not an alternative to goog.require, it does not
        376 * indicate a hard dependency, instead it is used to indicate
        377 * an optional dependency or to access the exports of a module
        378 * that has already been loaded.
        379 * @suppress {missingProvide}
        380 */
        381goog.module.get = function(name) {
        382 return goog.module.getInternal_(name);
        383};
        384
        385
        386/**
        387 * @param {string} name The module identifier.
        388 * @return {?} The module exports for an already loaded module or null.
        389 * @private
        390 */
        391goog.module.getInternal_ = function(name) {
        392 if (!COMPILED) {
        393 if (goog.isProvided_(name)) {
        394 // goog.require only return a value with-in goog.module files.
        395 return name in goog.loadedModules_ ?
        396 goog.loadedModules_[name] :
        397 goog.getObjectByName(name);
        398 } else {
        399 return null;
        400 }
        401 }
        402};
        403
        404
        405/**
        406 * @private {?{
        407 * moduleName: (string|undefined),
        408 * declareTestMethods: boolean
        409 * }}
        410 */
        411goog.moduleLoaderState_ = null;
        412
        413
        414/**
        415 * @private
        416 * @return {boolean} Whether a goog.module is currently being initialized.
        417 */
        418goog.isInModuleLoader_ = function() {
        419 return goog.moduleLoaderState_ != null;
        420};
        421
        422
        423/**
        424 * Indicate that a module's exports that are known test methods should
        425 * be copied to the global object. This makes the test methods visible to
        426 * test runners that inspect the global object.
        427 *
        428 * TODO(johnlenz): Make the test framework aware of goog.module so
        429 * that this isn't necessary. Alternately combine this with goog.setTestOnly
        430 * to minimize boiler plate.
        431 * @suppress {missingProvide}
        432 * @deprecated This approach does not translate to ES6 module syntax, instead
        433 * use goog.testing.testSuite to declare the test methods.
        434 */
        435goog.module.declareTestMethods = function() {
        436 if (!goog.isInModuleLoader_()) {
        437 throw new Error('goog.module.declareTestMethods must be called from ' +
        438 'within a goog.module');
        439 }
        440 goog.moduleLoaderState_.declareTestMethods = true;
        441};
        442
        443
        444/**
        445 * Provide the module's exports as a globally accessible object under the
        446 * module's declared name. This is intended to ease migration to goog.module
        447 * for files that have existing usages.
        448 * @suppress {missingProvide}
        449 */
        450goog.module.declareLegacyNamespace = function() {
        451 if (!COMPILED && !goog.isInModuleLoader_()) {
        452 throw new Error('goog.module.declareLegacyNamespace must be called from ' +
        453 'within a goog.module');
        454 }
        455 if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
        456 throw Error('goog.module must be called prior to ' +
        457 'goog.module.declareLegacyNamespace.');
        458 }
        459 goog.moduleLoaderState_.declareLegacyNamespace = true;
        460};
        461
        462
        463/**
        464 * Marks that the current file should only be used for testing, and never for
        465 * live code in production.
        466 *
        467 * In the case of unit tests, the message may optionally be an exact namespace
        468 * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
        469 * provide (if not explicitly defined in the code).
        470 *
        471 * @param {string=} opt_message Optional message to add to the error that's
        472 * raised when used in production code.
        473 */
        474goog.setTestOnly = function(opt_message) {
        475 if (goog.DISALLOW_TEST_ONLY_CODE) {
        476 opt_message = opt_message || '';
        477 throw Error('Importing test-only code into non-debug environment' +
        478 (opt_message ? ': ' + opt_message : '.'));
        479 }
        480};
        481
        482
        483/**
        484 * Forward declares a symbol. This is an indication to the compiler that the
        485 * symbol may be used in the source yet is not required and may not be provided
        486 * in compilation.
        487 *
        488 * The most common usage of forward declaration is code that takes a type as a
        489 * function parameter but does not need to require it. By forward declaring
        490 * instead of requiring, no hard dependency is made, and (if not required
        491 * elsewhere) the namespace may never be required and thus, not be pulled
        492 * into the JavaScript binary. If it is required elsewhere, it will be type
        493 * checked as normal.
        494 *
        495 *
        496 * @param {string} name The namespace to forward declare in the form of
        497 * "goog.package.part".
        498 */
        499goog.forwardDeclare = function(name) {};
        500
        501
        502if (!COMPILED) {
        503
        504 /**
        505 * Check if the given name has been goog.provided. This will return false for
        506 * names that are available only as implicit namespaces.
        507 * @param {string} name name of the object to look for.
        508 * @return {boolean} Whether the name has been provided.
        509 * @private
        510 */
        511 goog.isProvided_ = function(name) {
        512 return (name in goog.loadedModules_) ||
        513 (!goog.implicitNamespaces_[name] &&
        514 goog.isDefAndNotNull(goog.getObjectByName(name)));
        515 };
        516
        517 /**
        518 * Namespaces implicitly defined by goog.provide. For example,
        519 * goog.provide('goog.events.Event') implicitly declares that 'goog' and
        520 * 'goog.events' must be namespaces.
        521 *
        522 * @type {!Object<string, (boolean|undefined)>}
        523 * @private
        524 */
        525 goog.implicitNamespaces_ = {'goog.module': true};
        526
        527 // NOTE: We add goog.module as an implicit namespace as goog.module is defined
        528 // here and because the existing module package has not been moved yet out of
        529 // the goog.module namespace. This satisifies both the debug loader and
        530 // ahead-of-time dependency management.
        531}
        532
        533
        534/**
        535 * Returns an object based on its fully qualified external name. The object
        536 * is not found if null or undefined. If you are using a compilation pass that
        537 * renames property names beware that using this function will not find renamed
        538 * properties.
        539 *
        540 * @param {string} name The fully qualified name.
        541 * @param {Object=} opt_obj The object within which to look; default is
        542 * |goog.global|.
        543 * @return {?} The value (object or primitive) or, if not found, null.
        544 */
        545goog.getObjectByName = function(name, opt_obj) {
        546 var parts = name.split('.');
        547 var cur = opt_obj || goog.global;
        548 for (var part; part = parts.shift(); ) {
        549 if (goog.isDefAndNotNull(cur[part])) {
        550 cur = cur[part];
        551 } else {
        552 return null;
        553 }
        554 }
        555 return cur;
        556};
        557
        558
        559/**
        560 * Globalizes a whole namespace, such as goog or goog.lang.
        561 *
        562 * @param {!Object} obj The namespace to globalize.
        563 * @param {Object=} opt_global The object to add the properties to.
        564 * @deprecated Properties may be explicitly exported to the global scope, but
        565 * this should no longer be done in bulk.
        566 */
        567goog.globalize = function(obj, opt_global) {
        568 var global = opt_global || goog.global;
        569 for (var x in obj) {
        570 global[x] = obj[x];
        571 }
        572};
        573
        574
        575/**
        576 * Adds a dependency from a file to the files it requires.
        577 * @param {string} relPath The path to the js file.
        578 * @param {!Array<string>} provides An array of strings with
        579 * the names of the objects this file provides.
        580 * @param {!Array<string>} requires An array of strings with
        581 * the names of the objects this file requires.
        582 * @param {boolean=} opt_isModule Whether this dependency must be loaded as
        583 * a module as declared by goog.module.
        584 */
        585goog.addDependency = function(relPath, provides, requires, opt_isModule) {
        586 if (goog.DEPENDENCIES_ENABLED) {
        587 var provide, require;
        588 var path = relPath.replace(/\\/g, '/');
        589 var deps = goog.dependencies_;
        590 for (var i = 0; provide = provides[i]; i++) {
        591 deps.nameToPath[provide] = path;
        592 deps.pathIsModule[path] = !!opt_isModule;
        593 }
        594 for (var j = 0; require = requires[j]; j++) {
        595 if (!(path in deps.requires)) {
        596 deps.requires[path] = {};
        597 }
        598 deps.requires[path][require] = true;
        599 }
        600 }
        601};
        602
        603
        604
        605
        606// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
        607// to do "debug-mode" development. The dependency system can sometimes be
        608// confusing, as can the debug DOM loader's asynchronous nature.
        609//
        610// With the DOM loader, a call to goog.require() is not blocking -- the script
        611// will not load until some point after the current script. If a namespace is
        612// needed at runtime, it needs to be defined in a previous script, or loaded via
        613// require() with its registered dependencies.
        614// User-defined namespaces may need their own deps file. See http://go/js_deps,
        615// http://go/genjsdeps, or, externally, DepsWriter.
        616// https://developers.google.com/closure/library/docs/depswriter
        617//
        618// Because of legacy clients, the DOM loader can't be easily removed from
        619// base.js. Work is being done to make it disableable or replaceable for
        620// different environments (DOM-less JavaScript interpreters like Rhino or V8,
        621// for example). See bootstrap/ for more information.
        622
        623
        624/**
        625 * @define {boolean} Whether to enable the debug loader.
        626 *
        627 * If enabled, a call to goog.require() will attempt to load the namespace by
        628 * appending a script tag to the DOM (if the namespace has been registered).
        629 *
        630 * If disabled, goog.require() will simply assert that the namespace has been
        631 * provided (and depend on the fact that some outside tool correctly ordered
        632 * the script).
        633 */
        634goog.define('goog.ENABLE_DEBUG_LOADER', true);
        635
        636
        637/**
        638 * @param {string} msg
        639 * @private
        640 */
        641goog.logToConsole_ = function(msg) {
        642 if (goog.global.console) {
        643 goog.global.console['error'](msg);
        644 }
        645};
        646
        647
        648/**
        649 * Implements a system for the dynamic resolution of dependencies that works in
        650 * parallel with the BUILD system. Note that all calls to goog.require will be
        651 * stripped by the JSCompiler when the --closure_pass option is used.
        652 * @see goog.provide
        653 * @param {string} name Namespace to include (as was given in goog.provide()) in
        654 * the form "goog.package.part".
        655 * @return {?} If called within a goog.module file, the associated namespace or
        656 * module otherwise null.
        657 */
        658goog.require = function(name) {
        659
        660 // If the object already exists we do not need do do anything.
        661 if (!COMPILED) {
        662 if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
        663 goog.maybeProcessDeferredDep_(name);
        664 }
        665
        666 if (goog.isProvided_(name)) {
        667 if (goog.isInModuleLoader_()) {
        668 return goog.module.getInternal_(name);
        669 } else {
        670 return null;
        671 }
        672 }
        673
        674 if (goog.ENABLE_DEBUG_LOADER) {
        675 var path = goog.getPathFromDeps_(name);
        676 if (path) {
        677 goog.included_[path] = true;
        678 goog.writeScripts_();
        679 return null;
        680 }
        681 }
        682
        683 var errorMessage = 'goog.require could not find: ' + name;
        684 goog.logToConsole_(errorMessage);
        685
        686 throw Error(errorMessage);
        687 }
        688};
        689
        690
        691/**
        692 * Path for included scripts.
        693 * @type {string}
        694 */
        695goog.basePath = '';
        696
        697
        698/**
        699 * A hook for overriding the base path.
        700 * @type {string|undefined}
        701 */
        702goog.global.CLOSURE_BASE_PATH;
        703
        704
        705/**
        706 * Whether to write out Closure's deps file. By default, the deps are written.
        707 * @type {boolean|undefined}
        708 */
        709goog.global.CLOSURE_NO_DEPS;
        710
        711
        712/**
        713 * A function to import a single script. This is meant to be overridden when
        714 * Closure is being run in non-HTML contexts, such as web workers. It's defined
        715 * in the global scope so that it can be set before base.js is loaded, which
        716 * allows deps.js to be imported properly.
        717 *
        718 * The function is passed the script source, which is a relative URI. It should
        719 * return true if the script was imported, false otherwise.
        720 * @type {(function(string): boolean)|undefined}
        721 */
        722goog.global.CLOSURE_IMPORT_SCRIPT;
        723
        724
        725/**
        726 * Null function used for default values of callbacks, etc.
        727 * @return {void} Nothing.
        728 */
        729goog.nullFunction = function() {};
        730
        731
        732
        733/**
        734 * When defining a class Foo with an abstract method bar(), you can do:
        735 * Foo.prototype.bar = goog.abstractMethod
        736 *
        737 * Now if a subclass of Foo fails to override bar(), an error will be thrown
        738 * when bar() is invoked.
        739 *
        740 * Note: This does not take the name of the function to override as an argument
        741 * because that would make it more difficult to obfuscate our JavaScript code.
        742 *
        743 * @type {!Function}
        744 * @throws {Error} when invoked to indicate the method should be overridden.
        745 */
        746goog.abstractMethod = function() {
        747 throw Error('unimplemented abstract method');
        748};
        749
        750
        751/**
        752 * Adds a {@code getInstance} static method that always returns the same
        753 * instance object.
        754 * @param {!Function} ctor The constructor for the class to add the static
        755 * method to.
        756 */
        757goog.addSingletonGetter = function(ctor) {
        758 ctor.getInstance = function() {
        759 if (ctor.instance_) {
        760 return ctor.instance_;
        761 }
        762 if (goog.DEBUG) {
        763 // NOTE: JSCompiler can't optimize away Array#push.
        764 goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
        765 }
        766 return ctor.instance_ = new ctor;
        767 };
        768};
        769
        770
        771/**
        772 * All singleton classes that have been instantiated, for testing. Don't read
        773 * it directly, use the {@code goog.testing.singleton} module. The compiler
        774 * removes this variable if unused.
        775 * @type {!Array<!Function>}
        776 * @private
        777 */
        778goog.instantiatedSingletons_ = [];
        779
        780
        781/**
        782 * @define {boolean} Whether to load goog.modules using {@code eval} when using
        783 * the debug loader. This provides a better debugging experience as the
        784 * source is unmodified and can be edited using Chrome Workspaces or similar.
        785 * However in some environments the use of {@code eval} is banned
        786 * so we provide an alternative.
        787 */
        788goog.define('goog.LOAD_MODULE_USING_EVAL', true);
        789
        790
        791/**
        792 * @define {boolean} Whether the exports of goog.modules should be sealed when
        793 * possible.
        794 */
        795goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);
        796
        797
        798/**
        799 * The registry of initialized modules:
        800 * the module identifier to module exports map.
        801 * @private @const {!Object<string, ?>}
        802 */
        803goog.loadedModules_ = {};
        804
        805
        806/**
        807 * True if goog.dependencies_ is available.
        808 * @const {boolean}
        809 */
        810goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
        811
        812
        813if (goog.DEPENDENCIES_ENABLED) {
        814 /**
        815 * Object used to keep track of urls that have already been added. This record
        816 * allows the prevention of circular dependencies.
        817 * @private {!Object<string, boolean>}
        818 */
        819 goog.included_ = {};
        820
        821
        822 /**
        823 * This object is used to keep track of dependencies and other data that is
        824 * used for loading scripts.
        825 * @private
        826 * @type {{
        827 * pathIsModule: !Object<string, boolean>,
        828 * nameToPath: !Object<string, string>,
        829 * requires: !Object<string, !Object<string, boolean>>,
        830 * visited: !Object<string, boolean>,
        831 * written: !Object<string, boolean>,
        832 * deferred: !Object<string, string>
        833 * }}
        834 */
        835 goog.dependencies_ = {
        836 pathIsModule: {}, // 1 to 1
        837
        838 nameToPath: {}, // 1 to 1
        839
        840 requires: {}, // 1 to many
        841
        842 // Used when resolving dependencies to prevent us from visiting file twice.
        843 visited: {},
        844
        845 written: {}, // Used to keep track of script files we have written.
        846
        847 deferred: {} // Used to track deferred module evaluations in old IEs
        848 };
        849
        850
        851 /**
        852 * Tries to detect whether is in the context of an HTML document.
        853 * @return {boolean} True if it looks like HTML document.
        854 * @private
        855 */
        856 goog.inHtmlDocument_ = function() {
        857 var doc = goog.global.document;
        858 return typeof doc != 'undefined' &&
        859 'write' in doc; // XULDocument misses write.
        860 };
        861
        862
        863 /**
        864 * Tries to detect the base path of base.js script that bootstraps Closure.
        865 * @private
        866 */
        867 goog.findBasePath_ = function() {
        868 if (goog.global.CLOSURE_BASE_PATH) {
        869 goog.basePath = goog.global.CLOSURE_BASE_PATH;
        870 return;
        871 } else if (!goog.inHtmlDocument_()) {
        872 return;
        873 }
        874 var doc = goog.global.document;
        875 var scripts = doc.getElementsByTagName('SCRIPT');
        876 // Search backwards since the current script is in almost all cases the one
        877 // that has base.js.
        878 for (var i = scripts.length - 1; i >= 0; --i) {
        879 var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
        880 var src = script.src;
        881 var qmark = src.lastIndexOf('?');
        882 var l = qmark == -1 ? src.length : qmark;
        883 if (src.substr(l - 7, 7) == 'base.js') {
        884 goog.basePath = src.substr(0, l - 7);
        885 return;
        886 }
        887 }
        888 };
        889
        890
        891 /**
        892 * Imports a script if, and only if, that script hasn't already been imported.
        893 * (Must be called at execution time)
        894 * @param {string} src Script source.
        895 * @param {string=} opt_sourceText The optionally source text to evaluate
        896 * @private
        897 */
        898 goog.importScript_ = function(src, opt_sourceText) {
        899 var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
        900 goog.writeScriptTag_;
        901 if (importScript(src, opt_sourceText)) {
        902 goog.dependencies_.written[src] = true;
        903 }
        904 };
        905
        906
        907 /** @const @private {boolean} */
        908 goog.IS_OLD_IE_ = !goog.global.atob && goog.global.document &&
        909 goog.global.document.all;
        910
        911
        912 /**
        913 * Given a URL initiate retrieval and execution of the module.
        914 * @param {string} src Script source URL.
        915 * @private
        916 */
        917 goog.importModule_ = function(src) {
        918 // In an attempt to keep browsers from timing out loading scripts using
        919 // synchronous XHRs, put each load in its own script block.
        920 var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");';
        921
        922 if (goog.importScript_('', bootstrap)) {
        923 goog.dependencies_.written[src] = true;
        924 }
        925 };
        926
        927
        928 /** @private {!Array<string>} */
        929 goog.queuedModules_ = [];
        930
        931
        932 /**
        933 * Return an appropriate module text. Suitable to insert into
        934 * a script tag (that is unescaped).
        935 * @param {string} srcUrl
        936 * @param {string} scriptText
        937 * @return {string}
        938 * @private
        939 */
        940 goog.wrapModule_ = function(srcUrl, scriptText) {
        941 if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
        942 return '' +
        943 'goog.loadModule(function(exports) {' +
        944 '"use strict";' +
        945 scriptText +
        946 '\n' + // terminate any trailing single line comment.
        947 ';return exports' +
        948 '});' +
        949 '\n//# sourceURL=' + srcUrl + '\n';
        950 } else {
        951 return '' +
        952 'goog.loadModule(' +
        953 goog.global.JSON.stringify(
        954 scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
        955 ');';
        956 }
        957 };
        958
        959 // On IE9 and earlier, it is necessary to handle
        960 // deferred module loads. In later browsers, the
        961 // code to be evaluated is simply inserted as a script
        962 // block in the correct order. To eval deferred
        963 // code at the right time, we piggy back on goog.require to call
        964 // goog.maybeProcessDeferredDep_.
        965 //
        966 // The goog.requires are used both to bootstrap
        967 // the loading process (when no deps are available) and
        968 // declare that they should be available.
        969 //
        970 // Here we eval the sources, if all the deps are available
        971 // either already eval'd or goog.require'd. This will
        972 // be the case when all the dependencies have already
        973 // been loaded, and the dependent module is loaded.
        974 //
        975 // But this alone isn't sufficient because it is also
        976 // necessary to handle the case where there is no root
        977 // that is not deferred. For that there we register for an event
        978 // and trigger goog.loadQueuedModules_ handle any remaining deferred
        979 // evaluations.
        980
        981 /**
        982 * Handle any remaining deferred goog.module evals.
        983 * @private
        984 */
        985 goog.loadQueuedModules_ = function() {
        986 var count = goog.queuedModules_.length;
        987 if (count > 0) {
        988 var queue = goog.queuedModules_;
        989 goog.queuedModules_ = [];
        990 for (var i = 0; i < count; i++) {
        991 var path = queue[i];
        992 goog.maybeProcessDeferredPath_(path);
        993 }
        994 }
        995 };
        996
        997
        998 /**
        999 * Eval the named module if its dependencies are
        1000 * available.
        1001 * @param {string} name The module to load.
        1002 * @private
        1003 */
        1004 goog.maybeProcessDeferredDep_ = function(name) {
        1005 if (goog.isDeferredModule_(name) &&
        1006 goog.allDepsAreAvailable_(name)) {
        1007 var path = goog.getPathFromDeps_(name);
        1008 goog.maybeProcessDeferredPath_(goog.basePath + path);
        1009 }
        1010 };
        1011
        1012 /**
        1013 * @param {string} name The module to check.
        1014 * @return {boolean} Whether the name represents a
        1015 * module whose evaluation has been deferred.
        1016 * @private
        1017 */
        1018 goog.isDeferredModule_ = function(name) {
        1019 var path = goog.getPathFromDeps_(name);
        1020 if (path && goog.dependencies_.pathIsModule[path]) {
        1021 var abspath = goog.basePath + path;
        1022 return (abspath) in goog.dependencies_.deferred;
        1023 }
        1024 return false;
        1025 };
        1026
        1027 /**
        1028 * @param {string} name The module to check.
        1029 * @return {boolean} Whether the name represents a
        1030 * module whose declared dependencies have all been loaded
        1031 * (eval'd or a deferred module load)
        1032 * @private
        1033 */
        1034 goog.allDepsAreAvailable_ = function(name) {
        1035 var path = goog.getPathFromDeps_(name);
        1036 if (path && (path in goog.dependencies_.requires)) {
        1037 for (var requireName in goog.dependencies_.requires[path]) {
        1038 if (!goog.isProvided_(requireName) &&
        1039 !goog.isDeferredModule_(requireName)) {
        1040 return false;
        1041 }
        1042 }
        1043 }
        1044 return true;
        1045 };
        1046
        1047
        1048 /**
        1049 * @param {string} abspath
        1050 * @private
        1051 */
        1052 goog.maybeProcessDeferredPath_ = function(abspath) {
        1053 if (abspath in goog.dependencies_.deferred) {
        1054 var src = goog.dependencies_.deferred[abspath];
        1055 delete goog.dependencies_.deferred[abspath];
        1056 goog.globalEval(src);
        1057 }
        1058 };
        1059
        1060
        1061 /**
        1062 * @param {function(?):?|string} moduleDef The module definition.
        1063 */
        1064 goog.loadModule = function(moduleDef) {
        1065 // NOTE: we allow function definitions to be either in the from
        1066 // of a string to eval (which keeps the original source intact) or
        1067 // in a eval forbidden environment (CSP) we allow a function definition
        1068 // which in its body must call {@code goog.module}, and return the exports
        1069 // of the module.
        1070 var previousState = goog.moduleLoaderState_;
        1071 try {
        1072 goog.moduleLoaderState_ = {
        1073 moduleName: undefined, declareTestMethods: false};
        1074 var exports;
        1075 if (goog.isFunction(moduleDef)) {
        1076 exports = moduleDef.call(goog.global, {});
        1077 } else if (goog.isString(moduleDef)) {
        1078 exports = goog.loadModuleFromSource_.call(goog.global, moduleDef);
        1079 } else {
        1080 throw Error('Invalid module definition');
        1081 }
        1082
        1083 var moduleName = goog.moduleLoaderState_.moduleName;
        1084 if (!goog.isString(moduleName) || !moduleName) {
        1085 throw Error('Invalid module name \"' + moduleName + '\"');
        1086 }
        1087
        1088 // Don't seal legacy namespaces as they may be uses as a parent of
        1089 // another namespace
        1090 if (goog.moduleLoaderState_.declareLegacyNamespace) {
        1091 goog.constructNamespace_(moduleName, exports);
        1092 } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
        1093 Object.seal(exports);
        1094 }
        1095
        1096 goog.loadedModules_[moduleName] = exports;
        1097 if (goog.moduleLoaderState_.declareTestMethods) {
        1098 for (var entry in exports) {
        1099 if (entry.indexOf('test', 0) === 0 ||
        1100 entry == 'tearDown' ||
        1101 entry == 'setUp' ||
        1102 entry == 'setUpPage' ||
        1103 entry == 'tearDownPage') {
        1104 goog.global[entry] = exports[entry];
        1105 }
        1106 }
        1107 }
        1108 } finally {
        1109 goog.moduleLoaderState_ = previousState;
        1110 }
        1111 };
        1112
        1113
        1114 /**
        1115 * @param {string} source
        1116 * @return {!Object}
        1117 * @private
        1118 */
        1119 goog.loadModuleFromSource_ = function(source) {
        1120 // NOTE: we avoid declaring parameters or local variables here to avoid
        1121 // masking globals or leaking values into the module definition.
        1122 'use strict';
        1123 var exports = {};
        1124 eval(arguments[0]);
        1125 return exports;
        1126 };
        1127
        1128
        1129 /**
        1130 * Writes a new script pointing to {@code src} directly into the DOM.
        1131 *
        1132 * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for
        1133 * the fallback mechanism.
        1134 *
        1135 * @param {string} src The script URL.
        1136 * @private
        1137 */
        1138 goog.writeScriptSrcNode_ = function(src) {
        1139 goog.global.document.write(
        1140 '<script type="text/javascript" src="' + src + '"></' + 'script>');
        1141 };
        1142
        1143
        1144 /**
        1145 * Appends a new script node to the DOM using a CSP-compliant mechanism. This
        1146 * method exists as a fallback for document.write (which is not allowed in a
        1147 * strict CSP context, e.g., Chrome apps).
        1148 *
        1149 * NOTE: This method is not analogous to using document.write to insert a
        1150 * <script> tag; specifically, the user agent will execute a script added by
        1151 * document.write immediately after the current script block finishes
        1152 * executing, whereas the DOM-appended script node will not be executed until
        1153 * the entire document is parsed and executed. That is to say, this script is
        1154 * added to the end of the script execution queue.
        1155 *
        1156 * The page must not attempt to call goog.required entities until after the
        1157 * document has loaded, e.g., in or after the window.onload callback.
        1158 *
        1159 * @param {string} src The script URL.
        1160 * @private
        1161 */
        1162 goog.appendScriptSrcNode_ = function(src) {
        1163 var doc = goog.global.document;
        1164 var scriptEl = doc.createElement('script');
        1165 scriptEl.type = 'text/javascript';
        1166 scriptEl.src = src;
        1167 scriptEl.defer = false;
        1168 scriptEl.async = false;
        1169 doc.head.appendChild(scriptEl);
        1170 };
        1171
        1172
        1173 /**
        1174 * The default implementation of the import function. Writes a script tag to
        1175 * import the script.
        1176 *
        1177 * @param {string} src The script url.
        1178 * @param {string=} opt_sourceText The optionally source text to evaluate
        1179 * @return {boolean} True if the script was imported, false otherwise.
        1180 * @private
        1181 */
        1182 goog.writeScriptTag_ = function(src, opt_sourceText) {
        1183 if (goog.inHtmlDocument_()) {
        1184 var doc = goog.global.document;
        1185
        1186 // If the user tries to require a new symbol after document load,
        1187 // something has gone terribly wrong. Doing a document.write would
        1188 // wipe out the page. This does not apply to the CSP-compliant method
        1189 // of writing script tags.
        1190 if (!goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING &&
        1191 doc.readyState == 'complete') {
        1192 // Certain test frameworks load base.js multiple times, which tries
        1193 // to write deps.js each time. If that happens, just fail silently.
        1194 // These frameworks wipe the page between each load of base.js, so this
        1195 // is OK.
        1196 var isDeps = /\bdeps.js$/.test(src);
        1197 if (isDeps) {
        1198 return false;
        1199 } else {
        1200 throw Error('Cannot write "' + src + '" after document load');
        1201 }
        1202 }
        1203
        1204 var isOldIE = goog.IS_OLD_IE_;
        1205
        1206 if (opt_sourceText === undefined) {
        1207 if (!isOldIE) {
        1208 if (goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING) {
        1209 goog.appendScriptSrcNode_(src);
        1210 } else {
        1211 goog.writeScriptSrcNode_(src);
        1212 }
        1213 } else {
        1214 var state = " onreadystatechange='goog.onScriptLoad_(this, " +
        1215 ++goog.lastNonModuleScriptIndex_ + ")' ";
        1216 doc.write(
        1217 '<script type="text/javascript" src="' +
        1218 src + '"' + state + '></' + 'script>');
        1219 }
        1220 } else {
        1221 doc.write(
        1222 '<script type="text/javascript">' +
        1223 opt_sourceText +
        1224 '</' + 'script>');
        1225 }
        1226 return true;
        1227 } else {
        1228 return false;
        1229 }
        1230 };
        1231
        1232
        1233 /** @private {number} */
        1234 goog.lastNonModuleScriptIndex_ = 0;
        1235
        1236
        1237 /**
        1238 * A readystatechange handler for legacy IE
        1239 * @param {!HTMLScriptElement} script
        1240 * @param {number} scriptIndex
        1241 * @return {boolean}
        1242 * @private
        1243 */
        1244 goog.onScriptLoad_ = function(script, scriptIndex) {
        1245 // for now load the modules when we reach the last script,
        1246 // later allow more inter-mingling.
        1247 if (script.readyState == 'complete' &&
        1248 goog.lastNonModuleScriptIndex_ == scriptIndex) {
        1249 goog.loadQueuedModules_();
        1250 }
        1251 return true;
        1252 };
        1253
        1254 /**
        1255 * Resolves dependencies based on the dependencies added using addDependency
        1256 * and calls importScript_ in the correct order.
        1257 * @private
        1258 */
        1259 goog.writeScripts_ = function() {
        1260 /** @type {!Array<string>} The scripts we need to write this time. */
        1261 var scripts = [];
        1262 var seenScript = {};
        1263 var deps = goog.dependencies_;
        1264
        1265 /** @param {string} path */
        1266 function visitNode(path) {
        1267 if (path in deps.written) {
        1268 return;
        1269 }
        1270
        1271 // We have already visited this one. We can get here if we have cyclic
        1272 // dependencies.
        1273 if (path in deps.visited) {
        1274 if (!(path in seenScript)) {
        1275 seenScript[path] = true;
        1276 scripts.push(path);
        1277 }
        1278 return;
        1279 }
        1280
        1281 deps.visited[path] = true;
        1282
        1283 if (path in deps.requires) {
        1284 for (var requireName in deps.requires[path]) {
        1285 // If the required name is defined, we assume that it was already
        1286 // bootstrapped by other means.
        1287 if (!goog.isProvided_(requireName)) {
        1288 if (requireName in deps.nameToPath) {
        1289 visitNode(deps.nameToPath[requireName]);
        1290 } else {
        1291 throw Error('Undefined nameToPath for ' + requireName);
        1292 }
        1293 }
        1294 }
        1295 }
        1296
        1297 if (!(path in seenScript)) {
        1298 seenScript[path] = true;
        1299 scripts.push(path);
        1300 }
        1301 }
        1302
        1303 for (var path in goog.included_) {
        1304 if (!deps.written[path]) {
        1305 visitNode(path);
        1306 }
        1307 }
        1308
        1309 // record that we are going to load all these scripts.
        1310 for (var i = 0; i < scripts.length; i++) {
        1311 var path = scripts[i];
        1312 goog.dependencies_.written[path] = true;
        1313 }
        1314
        1315 // If a module is loaded synchronously then we need to
        1316 // clear the current inModuleLoader value, and restore it when we are
        1317 // done loading the current "requires".
        1318 var moduleState = goog.moduleLoaderState_;
        1319 goog.moduleLoaderState_ = null;
        1320
        1321 var loadingModule = false;
        1322 for (var i = 0; i < scripts.length; i++) {
        1323 var path = scripts[i];
        1324 if (path) {
        1325 if (!deps.pathIsModule[path]) {
        1326 goog.importScript_(goog.basePath + path);
        1327 } else {
        1328 loadingModule = true;
        1329 goog.importModule_(goog.basePath + path);
        1330 }
        1331 } else {
        1332 goog.moduleLoaderState_ = moduleState;
        1333 throw Error('Undefined script input');
        1334 }
        1335 }
        1336
        1337 // restore the current "module loading state"
        1338 goog.moduleLoaderState_ = moduleState;
        1339 };
        1340
        1341
        1342 /**
        1343 * Looks at the dependency rules and tries to determine the script file that
        1344 * fulfills a particular rule.
        1345 * @param {string} rule In the form goog.namespace.Class or project.script.
        1346 * @return {?string} Url corresponding to the rule, or null.
        1347 * @private
        1348 */
        1349 goog.getPathFromDeps_ = function(rule) {
        1350 if (rule in goog.dependencies_.nameToPath) {
        1351 return goog.dependencies_.nameToPath[rule];
        1352 } else {
        1353 return null;
        1354 }
        1355 };
        1356
        1357 goog.findBasePath_();
        1358
        1359 // Allow projects to manage the deps files themselves.
        1360 if (!goog.global.CLOSURE_NO_DEPS) {
        1361 goog.importScript_(goog.basePath + 'deps.js');
        1362 }
        1363}
        1364
        1365
        1366/**
        1367 * Normalize a file path by removing redundant ".." and extraneous "." file
        1368 * path components.
        1369 * @param {string} path
        1370 * @return {string}
        1371 * @private
        1372 */
        1373goog.normalizePath_ = function(path) {
        1374 var components = path.split('/');
        1375 var i = 0;
        1376 while (i < components.length) {
        1377 if (components[i] == '.') {
        1378 components.splice(i, 1);
        1379 } else if (i && components[i] == '..' &&
        1380 components[i - 1] && components[i - 1] != '..') {
        1381 components.splice(--i, 2);
        1382 } else {
        1383 i++;
        1384 }
        1385 }
        1386 return components.join('/');
        1387};
        1388
        1389
        1390/**
        1391 * Loads file by synchronous XHR. Should not be used in production environments.
        1392 * @param {string} src Source URL.
        1393 * @return {string} File contents.
        1394 * @private
        1395 */
        1396goog.loadFileSync_ = function(src) {
        1397 if (goog.global.CLOSURE_LOAD_FILE_SYNC) {
        1398 return goog.global.CLOSURE_LOAD_FILE_SYNC(src);
        1399 } else {
        1400 var xhr = new goog.global['XMLHttpRequest']();
        1401 xhr.open('get', src, false);
        1402 xhr.send();
        1403 return xhr.responseText;
        1404 }
        1405};
        1406
        1407
        1408/**
        1409 * Retrieve and execute a module.
        1410 * @param {string} src Script source URL.
        1411 * @private
        1412 */
        1413goog.retrieveAndExecModule_ = function(src) {
        1414 if (!COMPILED) {
        1415 // The full but non-canonicalized URL for later use.
        1416 var originalPath = src;
        1417 // Canonicalize the path, removing any /./ or /../ since Chrome's debugging
        1418 // console doesn't auto-canonicalize XHR loads as it does <script> srcs.
        1419 src = goog.normalizePath_(src);
        1420
        1421 var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
        1422 goog.writeScriptTag_;
        1423
        1424 var scriptText = goog.loadFileSync_(src);
        1425
        1426 if (scriptText != null) {
        1427 var execModuleScript = goog.wrapModule_(src, scriptText);
        1428 var isOldIE = goog.IS_OLD_IE_;
        1429 if (isOldIE) {
        1430 goog.dependencies_.deferred[originalPath] = execModuleScript;
        1431 goog.queuedModules_.push(originalPath);
        1432 } else {
        1433 importScript(src, execModuleScript);
        1434 }
        1435 } else {
        1436 throw new Error('load of ' + src + 'failed');
        1437 }
        1438 }
        1439};
        1440
        1441
        1442//==============================================================================
        1443// Language Enhancements
        1444//==============================================================================
        1445
        1446
        1447/**
        1448 * This is a "fixed" version of the typeof operator. It differs from the typeof
        1449 * operator in such a way that null returns 'null' and arrays return 'array'.
        1450 * @param {*} value The value to get the type of.
        1451 * @return {string} The name of the type.
        1452 */
        1453goog.typeOf = function(value) {
        1454 var s = typeof value;
        1455 if (s == 'object') {
        1456 if (value) {
        1457 // Check these first, so we can avoid calling Object.prototype.toString if
        1458 // possible.
        1459 //
        1460 // IE improperly marshals tyepof across execution contexts, but a
        1461 // cross-context object will still return false for "instanceof Object".
        1462 if (value instanceof Array) {
        1463 return 'array';
        1464 } else if (value instanceof Object) {
        1465 return s;
        1466 }
        1467
        1468 // HACK: In order to use an Object prototype method on the arbitrary
        1469 // value, the compiler requires the value be cast to type Object,
        1470 // even though the ECMA spec explicitly allows it.
        1471 var className = Object.prototype.toString.call(
        1472 /** @type {Object} */ (value));
        1473 // In Firefox 3.6, attempting to access iframe window objects' length
        1474 // property throws an NS_ERROR_FAILURE, so we need to special-case it
        1475 // here.
        1476 if (className == '[object Window]') {
        1477 return 'object';
        1478 }
        1479
        1480 // We cannot always use constructor == Array or instanceof Array because
        1481 // different frames have different Array objects. In IE6, if the iframe
        1482 // where the array was created is destroyed, the array loses its
        1483 // prototype. Then dereferencing val.splice here throws an exception, so
        1484 // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
        1485 // so that will work. In this case, this function will return false and
        1486 // most array functions will still work because the array is still
        1487 // array-like (supports length and []) even though it has lost its
        1488 // prototype.
        1489 // Mark Miller noticed that Object.prototype.toString
        1490 // allows access to the unforgeable [[Class]] property.
        1491 // 15.2.4.2 Object.prototype.toString ( )
        1492 // When the toString method is called, the following steps are taken:
        1493 // 1. Get the [[Class]] property of this object.
        1494 // 2. Compute a string value by concatenating the three strings
        1495 // "[object ", Result(1), and "]".
        1496 // 3. Return Result(2).
        1497 // and this behavior survives the destruction of the execution context.
        1498 if ((className == '[object Array]' ||
        1499 // In IE all non value types are wrapped as objects across window
        1500 // boundaries (not iframe though) so we have to do object detection
        1501 // for this edge case.
        1502 typeof value.length == 'number' &&
        1503 typeof value.splice != 'undefined' &&
        1504 typeof value.propertyIsEnumerable != 'undefined' &&
        1505 !value.propertyIsEnumerable('splice')
        1506
        1507 )) {
        1508 return 'array';
        1509 }
        1510 // HACK: There is still an array case that fails.
        1511 // function ArrayImpostor() {}
        1512 // ArrayImpostor.prototype = [];
        1513 // var impostor = new ArrayImpostor;
        1514 // this can be fixed by getting rid of the fast path
        1515 // (value instanceof Array) and solely relying on
        1516 // (value && Object.prototype.toString.vall(value) === '[object Array]')
        1517 // but that would require many more function calls and is not warranted
        1518 // unless closure code is receiving objects from untrusted sources.
        1519
        1520 // IE in cross-window calls does not correctly marshal the function type
        1521 // (it appears just as an object) so we cannot use just typeof val ==
        1522 // 'function'. However, if the object has a call property, it is a
        1523 // function.
        1524 if ((className == '[object Function]' ||
        1525 typeof value.call != 'undefined' &&
        1526 typeof value.propertyIsEnumerable != 'undefined' &&
        1527 !value.propertyIsEnumerable('call'))) {
        1528 return 'function';
        1529 }
        1530
        1531 } else {
        1532 return 'null';
        1533 }
        1534
        1535 } else if (s == 'function' && typeof value.call == 'undefined') {
        1536 // In Safari typeof nodeList returns 'function', and on Firefox typeof
        1537 // behaves similarly for HTML{Applet,Embed,Object}, Elements and RegExps. We
        1538 // would like to return object for those and we can detect an invalid
        1539 // function by making sure that the function object has a call method.
        1540 return 'object';
        1541 }
        1542 return s;
        1543};
        1544
        1545
        1546/**
        1547 * Returns true if the specified value is null.
        1548 * @param {?} val Variable to test.
        1549 * @return {boolean} Whether variable is null.
        1550 */
        1551goog.isNull = function(val) {
        1552 return val === null;
        1553};
        1554
        1555
        1556/**
        1557 * Returns true if the specified value is defined and not null.
        1558 * @param {?} val Variable to test.
        1559 * @return {boolean} Whether variable is defined and not null.
        1560 */
        1561goog.isDefAndNotNull = function(val) {
        1562 // Note that undefined == null.
        1563 return val != null;
        1564};
        1565
        1566
        1567/**
        1568 * Returns true if the specified value is an array.
        1569 * @param {?} val Variable to test.
        1570 * @return {boolean} Whether variable is an array.
        1571 */
        1572goog.isArray = function(val) {
        1573 return goog.typeOf(val) == 'array';
        1574};
        1575
        1576
        1577/**
        1578 * Returns true if the object looks like an array. To qualify as array like
        1579 * the value needs to be either a NodeList or an object with a Number length
        1580 * property. As a special case, a function value is not array like, because its
        1581 * length property is fixed to correspond to the number of expected arguments.
        1582 * @param {?} val Variable to test.
        1583 * @return {boolean} Whether variable is an array.
        1584 */
        1585goog.isArrayLike = function(val) {
        1586 var type = goog.typeOf(val);
        1587 // We do not use goog.isObject here in order to exclude function values.
        1588 return type == 'array' || type == 'object' && typeof val.length == 'number';
        1589};
        1590
        1591
        1592/**
        1593 * Returns true if the object looks like a Date. To qualify as Date-like the
        1594 * value needs to be an object and have a getFullYear() function.
        1595 * @param {?} val Variable to test.
        1596 * @return {boolean} Whether variable is a like a Date.
        1597 */
        1598goog.isDateLike = function(val) {
        1599 return goog.isObject(val) && typeof val.getFullYear == 'function';
        1600};
        1601
        1602
        1603/**
        1604 * Returns true if the specified value is a string.
        1605 * @param {?} val Variable to test.
        1606 * @return {boolean} Whether variable is a string.
        1607 */
        1608goog.isString = function(val) {
        1609 return typeof val == 'string';
        1610};
        1611
        1612
        1613/**
        1614 * Returns true if the specified value is a boolean.
        1615 * @param {?} val Variable to test.
        1616 * @return {boolean} Whether variable is boolean.
        1617 */
        1618goog.isBoolean = function(val) {
        1619 return typeof val == 'boolean';
        1620};
        1621
        1622
        1623/**
        1624 * Returns true if the specified value is a number.
        1625 * @param {?} val Variable to test.
        1626 * @return {boolean} Whether variable is a number.
        1627 */
        1628goog.isNumber = function(val) {
        1629 return typeof val == 'number';
        1630};
        1631
        1632
        1633/**
        1634 * Returns true if the specified value is a function.
        1635 * @param {?} val Variable to test.
        1636 * @return {boolean} Whether variable is a function.
        1637 */
        1638goog.isFunction = function(val) {
        1639 return goog.typeOf(val) == 'function';
        1640};
        1641
        1642
        1643/**
        1644 * Returns true if the specified value is an object. This includes arrays and
        1645 * functions.
        1646 * @param {?} val Variable to test.
        1647 * @return {boolean} Whether variable is an object.
        1648 */
        1649goog.isObject = function(val) {
        1650 var type = typeof val;
        1651 return type == 'object' && val != null || type == 'function';
        1652 // return Object(val) === val also works, but is slower, especially if val is
        1653 // not an object.
        1654};
        1655
        1656
        1657/**
        1658 * Gets a unique ID for an object. This mutates the object so that further calls
        1659 * with the same object as a parameter returns the same value. The unique ID is
        1660 * guaranteed to be unique across the current session amongst objects that are
        1661 * passed into {@code getUid}. There is no guarantee that the ID is unique or
        1662 * consistent across sessions. It is unsafe to generate unique ID for function
        1663 * prototypes.
        1664 *
        1665 * @param {Object} obj The object to get the unique ID for.
        1666 * @return {number} The unique ID for the object.
        1667 */
        1668goog.getUid = function(obj) {
        1669 // TODO(arv): Make the type stricter, do not accept null.
        1670
        1671 // In Opera window.hasOwnProperty exists but always returns false so we avoid
        1672 // using it. As a consequence the unique ID generated for BaseClass.prototype
        1673 // and SubClass.prototype will be the same.
        1674 return obj[goog.UID_PROPERTY_] ||
        1675 (obj[goog.UID_PROPERTY_] = ++goog.uidCounter_);
        1676};
        1677
        1678
        1679/**
        1680 * Whether the given object is already assigned a unique ID.
        1681 *
        1682 * This does not modify the object.
        1683 *
        1684 * @param {!Object} obj The object to check.
        1685 * @return {boolean} Whether there is an assigned unique id for the object.
        1686 */
        1687goog.hasUid = function(obj) {
        1688 return !!obj[goog.UID_PROPERTY_];
        1689};
        1690
        1691
        1692/**
        1693 * Removes the unique ID from an object. This is useful if the object was
        1694 * previously mutated using {@code goog.getUid} in which case the mutation is
        1695 * undone.
        1696 * @param {Object} obj The object to remove the unique ID field from.
        1697 */
        1698goog.removeUid = function(obj) {
        1699 // TODO(arv): Make the type stricter, do not accept null.
        1700
        1701 // In IE, DOM nodes are not instances of Object and throw an exception if we
        1702 // try to delete. Instead we try to use removeAttribute.
        1703 if ('removeAttribute' in obj) {
        1704 obj.removeAttribute(goog.UID_PROPERTY_);
        1705 }
        1706 /** @preserveTry */
        1707 try {
        1708 delete obj[goog.UID_PROPERTY_];
        1709 } catch (ex) {
        1710 }
        1711};
        1712
        1713
        1714/**
        1715 * Name for unique ID property. Initialized in a way to help avoid collisions
        1716 * with other closure JavaScript on the same page.
        1717 * @type {string}
        1718 * @private
        1719 */
        1720goog.UID_PROPERTY_ = 'closure_uid_' + ((Math.random() * 1e9) >>> 0);
        1721
        1722
        1723/**
        1724 * Counter for UID.
        1725 * @type {number}
        1726 * @private
        1727 */
        1728goog.uidCounter_ = 0;
        1729
        1730
        1731/**
        1732 * Adds a hash code field to an object. The hash code is unique for the
        1733 * given object.
        1734 * @param {Object} obj The object to get the hash code for.
        1735 * @return {number} The hash code for the object.
        1736 * @deprecated Use goog.getUid instead.
        1737 */
        1738goog.getHashCode = goog.getUid;
        1739
        1740
        1741/**
        1742 * Removes the hash code field from an object.
        1743 * @param {Object} obj The object to remove the field from.
        1744 * @deprecated Use goog.removeUid instead.
        1745 */
        1746goog.removeHashCode = goog.removeUid;
        1747
        1748
        1749/**
        1750 * Clones a value. The input may be an Object, Array, or basic type. Objects and
        1751 * arrays will be cloned recursively.
        1752 *
        1753 * WARNINGS:
        1754 * <code>goog.cloneObject</code> does not detect reference loops. Objects that
        1755 * refer to themselves will cause infinite recursion.
        1756 *
        1757 * <code>goog.cloneObject</code> is unaware of unique identifiers, and copies
        1758 * UIDs created by <code>getUid</code> into cloned results.
        1759 *
        1760 * @param {*} obj The value to clone.
        1761 * @return {*} A clone of the input value.
        1762 * @deprecated goog.cloneObject is unsafe. Prefer the goog.object methods.
        1763 */
        1764goog.cloneObject = function(obj) {
        1765 var type = goog.typeOf(obj);
        1766 if (type == 'object' || type == 'array') {
        1767 if (obj.clone) {
        1768 return obj.clone();
        1769 }
        1770 var clone = type == 'array' ? [] : {};
        1771 for (var key in obj) {
        1772 clone[key] = goog.cloneObject(obj[key]);
        1773 }
        1774 return clone;
        1775 }
        1776
        1777 return obj;
        1778};
        1779
        1780
        1781/**
        1782 * A native implementation of goog.bind.
        1783 * @param {Function} fn A function to partially apply.
        1784 * @param {Object|undefined} selfObj Specifies the object which this should
        1785 * point to when the function is run.
        1786 * @param {...*} var_args Additional arguments that are partially applied to the
        1787 * function.
        1788 * @return {!Function} A partially-applied form of the function bind() was
        1789 * invoked as a method of.
        1790 * @private
        1791 * @suppress {deprecated} The compiler thinks that Function.prototype.bind is
        1792 * deprecated because some people have declared a pure-JS version.
        1793 * Only the pure-JS version is truly deprecated.
        1794 */
        1795goog.bindNative_ = function(fn, selfObj, var_args) {
        1796 return /** @type {!Function} */ (fn.call.apply(fn.bind, arguments));
        1797};
        1798
        1799
        1800/**
        1801 * A pure-JS implementation of goog.bind.
        1802 * @param {Function} fn A function to partially apply.
        1803 * @param {Object|undefined} selfObj Specifies the object which this should
        1804 * point to when the function is run.
        1805 * @param {...*} var_args Additional arguments that are partially applied to the
        1806 * function.
        1807 * @return {!Function} A partially-applied form of the function bind() was
        1808 * invoked as a method of.
        1809 * @private
        1810 */
        1811goog.bindJs_ = function(fn, selfObj, var_args) {
        1812 if (!fn) {
        1813 throw new Error();
        1814 }
        1815
        1816 if (arguments.length > 2) {
        1817 var boundArgs = Array.prototype.slice.call(arguments, 2);
        1818 return function() {
        1819 // Prepend the bound arguments to the current arguments.
        1820 var newArgs = Array.prototype.slice.call(arguments);
        1821 Array.prototype.unshift.apply(newArgs, boundArgs);
        1822 return fn.apply(selfObj, newArgs);
        1823 };
        1824
        1825 } else {
        1826 return function() {
        1827 return fn.apply(selfObj, arguments);
        1828 };
        1829 }
        1830};
        1831
        1832
        1833/**
        1834 * Partially applies this function to a particular 'this object' and zero or
        1835 * more arguments. The result is a new function with some arguments of the first
        1836 * function pre-filled and the value of this 'pre-specified'.
        1837 *
        1838 * Remaining arguments specified at call-time are appended to the pre-specified
        1839 * ones.
        1840 *
        1841 * Also see: {@link #partial}.
        1842 *
        1843 * Usage:
        1844 * <pre>var barMethBound = bind(myFunction, myObj, 'arg1', 'arg2');
        1845 * barMethBound('arg3', 'arg4');</pre>
        1846 *
        1847 * @param {?function(this:T, ...)} fn A function to partially apply.
        1848 * @param {T} selfObj Specifies the object which this should point to when the
        1849 * function is run.
        1850 * @param {...*} var_args Additional arguments that are partially applied to the
        1851 * function.
        1852 * @return {!Function} A partially-applied form of the function bind() was
        1853 * invoked as a method of.
        1854 * @template T
        1855 * @suppress {deprecated} See above.
        1856 */
        1857goog.bind = function(fn, selfObj, var_args) {
        1858 // TODO(nicksantos): narrow the type signature.
        1859 if (Function.prototype.bind &&
        1860 // NOTE(nicksantos): Somebody pulled base.js into the default Chrome
        1861 // extension environment. This means that for Chrome extensions, they get
        1862 // the implementation of Function.prototype.bind that calls goog.bind
        1863 // instead of the native one. Even worse, we don't want to introduce a
        1864 // circular dependency between goog.bind and Function.prototype.bind, so
        1865 // we have to hack this to make sure it works correctly.
        1866 Function.prototype.bind.toString().indexOf('native code') != -1) {
        1867 goog.bind = goog.bindNative_;
        1868 } else {
        1869 goog.bind = goog.bindJs_;
        1870 }
        1871 return goog.bind.apply(null, arguments);
        1872};
        1873
        1874
        1875/**
        1876 * Like bind(), except that a 'this object' is not required. Useful when the
        1877 * target function is already bound.
        1878 *
        1879 * Usage:
        1880 * var g = partial(f, arg1, arg2);
        1881 * g(arg3, arg4);
        1882 *
        1883 * @param {Function} fn A function to partially apply.
        1884 * @param {...*} var_args Additional arguments that are partially applied to fn.
        1885 * @return {!Function} A partially-applied form of the function bind() was
        1886 * invoked as a method of.
        1887 */
        1888goog.partial = function(fn, var_args) {
        1889 var args = Array.prototype.slice.call(arguments, 1);
        1890 return function() {
        1891 // Clone the array (with slice()) and append additional arguments
        1892 // to the existing arguments.
        1893 var newArgs = args.slice();
        1894 newArgs.push.apply(newArgs, arguments);
        1895 return fn.apply(this, newArgs);
        1896 };
        1897};
        1898
        1899
        1900/**
        1901 * Copies all the members of a source object to a target object. This method
        1902 * does not work on all browsers for all objects that contain keys such as
        1903 * toString or hasOwnProperty. Use goog.object.extend for this purpose.
        1904 * @param {Object} target Target.
        1905 * @param {Object} source Source.
        1906 */
        1907goog.mixin = function(target, source) {
        1908 for (var x in source) {
        1909 target[x] = source[x];
        1910 }
        1911
        1912 // For IE7 or lower, the for-in-loop does not contain any properties that are
        1913 // not enumerable on the prototype object (for example, isPrototypeOf from
        1914 // Object.prototype) but also it will not include 'replace' on objects that
        1915 // extend String and change 'replace' (not that it is common for anyone to
        1916 // extend anything except Object).
        1917};
        1918
        1919
        1920/**
        1921 * @return {number} An integer value representing the number of milliseconds
        1922 * between midnight, January 1, 1970 and the current time.
        1923 */
        1924goog.now = (goog.TRUSTED_SITE && Date.now) || (function() {
        1925 // Unary plus operator converts its operand to a number which in the case of
        1926 // a date is done by calling getTime().
        1927 return +new Date();
        1928});
        1929
        1930
        1931/**
        1932 * Evals JavaScript in the global scope. In IE this uses execScript, other
        1933 * browsers use goog.global.eval. If goog.global.eval does not evaluate in the
        1934 * global scope (for example, in Safari), appends a script tag instead.
        1935 * Throws an exception if neither execScript or eval is defined.
        1936 * @param {string} script JavaScript string.
        1937 */
        1938goog.globalEval = function(script) {
        1939 if (goog.global.execScript) {
        1940 goog.global.execScript(script, 'JavaScript');
        1941 } else if (goog.global.eval) {
        1942 // Test to see if eval works
        1943 if (goog.evalWorksForGlobals_ == null) {
        1944 goog.global.eval('var _et_ = 1;');
        1945 if (typeof goog.global['_et_'] != 'undefined') {
        1946 delete goog.global['_et_'];
        1947 goog.evalWorksForGlobals_ = true;
        1948 } else {
        1949 goog.evalWorksForGlobals_ = false;
        1950 }
        1951 }
        1952
        1953 if (goog.evalWorksForGlobals_) {
        1954 goog.global.eval(script);
        1955 } else {
        1956 var doc = goog.global.document;
        1957 var scriptElt = doc.createElement('SCRIPT');
        1958 scriptElt.type = 'text/javascript';
        1959 scriptElt.defer = false;
        1960 // Note(user): can't use .innerHTML since "t('<test>')" will fail and
        1961 // .text doesn't work in Safari 2. Therefore we append a text node.
        1962 scriptElt.appendChild(doc.createTextNode(script));
        1963 doc.body.appendChild(scriptElt);
        1964 doc.body.removeChild(scriptElt);
        1965 }
        1966 } else {
        1967 throw Error('goog.globalEval not available');
        1968 }
        1969};
        1970
        1971
        1972/**
        1973 * Indicates whether or not we can call 'eval' directly to eval code in the
        1974 * global scope. Set to a Boolean by the first call to goog.globalEval (which
        1975 * empirically tests whether eval works for globals). @see goog.globalEval
        1976 * @type {?boolean}
        1977 * @private
        1978 */
        1979goog.evalWorksForGlobals_ = null;
        1980
        1981
        1982/**
        1983 * Optional map of CSS class names to obfuscated names used with
        1984 * goog.getCssName().
        1985 * @private {!Object<string, string>|undefined}
        1986 * @see goog.setCssNameMapping
        1987 */
        1988goog.cssNameMapping_;
        1989
        1990
        1991/**
        1992 * Optional obfuscation style for CSS class names. Should be set to either
        1993 * 'BY_WHOLE' or 'BY_PART' if defined.
        1994 * @type {string|undefined}
        1995 * @private
        1996 * @see goog.setCssNameMapping
        1997 */
        1998goog.cssNameMappingStyle_;
        1999
        2000
        2001/**
        2002 * Handles strings that are intended to be used as CSS class names.
        2003 *
        2004 * This function works in tandem with @see goog.setCssNameMapping.
        2005 *
        2006 * Without any mapping set, the arguments are simple joined with a hyphen and
        2007 * passed through unaltered.
        2008 *
        2009 * When there is a mapping, there are two possible styles in which these
        2010 * mappings are used. In the BY_PART style, each part (i.e. in between hyphens)
        2011 * of the passed in css name is rewritten according to the map. In the BY_WHOLE
        2012 * style, the full css name is looked up in the map directly. If a rewrite is
        2013 * not specified by the map, the compiler will output a warning.
        2014 *
        2015 * When the mapping is passed to the compiler, it will replace calls to
        2016 * goog.getCssName with the strings from the mapping, e.g.
        2017 * var x = goog.getCssName('foo');
        2018 * var y = goog.getCssName(this.baseClass, 'active');
        2019 * becomes:
        2020 * var x= 'foo';
        2021 * var y = this.baseClass + '-active';
        2022 *
        2023 * If one argument is passed it will be processed, if two are passed only the
        2024 * modifier will be processed, as it is assumed the first argument was generated
        2025 * as a result of calling goog.getCssName.
        2026 *
        2027 * @param {string} className The class name.
        2028 * @param {string=} opt_modifier A modifier to be appended to the class name.
        2029 * @return {string} The class name or the concatenation of the class name and
        2030 * the modifier.
        2031 */
        2032goog.getCssName = function(className, opt_modifier) {
        2033 var getMapping = function(cssName) {
        2034 return goog.cssNameMapping_[cssName] || cssName;
        2035 };
        2036
        2037 var renameByParts = function(cssName) {
        2038 // Remap all the parts individually.
        2039 var parts = cssName.split('-');
        2040 var mapped = [];
        2041 for (var i = 0; i < parts.length; i++) {
        2042 mapped.push(getMapping(parts[i]));
        2043 }
        2044 return mapped.join('-');
        2045 };
        2046
        2047 var rename;
        2048 if (goog.cssNameMapping_) {
        2049 rename = goog.cssNameMappingStyle_ == 'BY_WHOLE' ?
        2050 getMapping : renameByParts;
        2051 } else {
        2052 rename = function(a) {
        2053 return a;
        2054 };
        2055 }
        2056
        2057 if (opt_modifier) {
        2058 return className + '-' + rename(opt_modifier);
        2059 } else {
        2060 return rename(className);
        2061 }
        2062};
        2063
        2064
        2065/**
        2066 * Sets the map to check when returning a value from goog.getCssName(). Example:
        2067 * <pre>
        2068 * goog.setCssNameMapping({
        2069 * "goog": "a",
        2070 * "disabled": "b",
        2071 * });
        2072 *
        2073 * var x = goog.getCssName('goog');
        2074 * // The following evaluates to: "a a-b".
        2075 * goog.getCssName('goog') + ' ' + goog.getCssName(x, 'disabled')
        2076 * </pre>
        2077 * When declared as a map of string literals to string literals, the JSCompiler
        2078 * will replace all calls to goog.getCssName() using the supplied map if the
        2079 * --closure_pass flag is set.
        2080 *
        2081 * @param {!Object} mapping A map of strings to strings where keys are possible
        2082 * arguments to goog.getCssName() and values are the corresponding values
        2083 * that should be returned.
        2084 * @param {string=} opt_style The style of css name mapping. There are two valid
        2085 * options: 'BY_PART', and 'BY_WHOLE'.
        2086 * @see goog.getCssName for a description.
        2087 */
        2088goog.setCssNameMapping = function(mapping, opt_style) {
        2089 goog.cssNameMapping_ = mapping;
        2090 goog.cssNameMappingStyle_ = opt_style;
        2091};
        2092
        2093
        2094/**
        2095 * To use CSS renaming in compiled mode, one of the input files should have a
        2096 * call to goog.setCssNameMapping() with an object literal that the JSCompiler
        2097 * can extract and use to replace all calls to goog.getCssName(). In uncompiled
        2098 * mode, JavaScript code should be loaded before this base.js file that declares
        2099 * a global variable, CLOSURE_CSS_NAME_MAPPING, which is used below. This is
        2100 * to ensure that the mapping is loaded before any calls to goog.getCssName()
        2101 * are made in uncompiled mode.
        2102 *
        2103 * A hook for overriding the CSS name mapping.
        2104 * @type {!Object<string, string>|undefined}
        2105 */
        2106goog.global.CLOSURE_CSS_NAME_MAPPING;
        2107
        2108
        2109if (!COMPILED && goog.global.CLOSURE_CSS_NAME_MAPPING) {
        2110 // This does not call goog.setCssNameMapping() because the JSCompiler
        2111 // requires that goog.setCssNameMapping() be called with an object literal.
        2112 goog.cssNameMapping_ = goog.global.CLOSURE_CSS_NAME_MAPPING;
        2113}
        2114
        2115
        2116/**
        2117 * Gets a localized message.
        2118 *
        2119 * This function is a compiler primitive. If you give the compiler a localized
        2120 * message bundle, it will replace the string at compile-time with a localized
        2121 * version, and expand goog.getMsg call to a concatenated string.
        2122 *
        2123 * Messages must be initialized in the form:
        2124 * <code>
        2125 * var MSG_NAME = goog.getMsg('Hello {$placeholder}', {'placeholder': 'world'});
        2126 * </code>
        2127 *
        2128 * @param {string} str Translatable string, places holders in the form {$foo}.
        2129 * @param {Object<string, string>=} opt_values Maps place holder name to value.
        2130 * @return {string} message with placeholders filled.
        2131 */
        2132goog.getMsg = function(str, opt_values) {
        2133 if (opt_values) {
        2134 str = str.replace(/\{\$([^}]+)}/g, function(match, key) {
        2135 return key in opt_values ? opt_values[key] : match;
        2136 });
        2137 }
        2138 return str;
        2139};
        2140
        2141
        2142/**
        2143 * Gets a localized message. If the message does not have a translation, gives a
        2144 * fallback message.
        2145 *
        2146 * This is useful when introducing a new message that has not yet been
        2147 * translated into all languages.
        2148 *
        2149 * This function is a compiler primitive. Must be used in the form:
        2150 * <code>var x = goog.getMsgWithFallback(MSG_A, MSG_B);</code>
        2151 * where MSG_A and MSG_B were initialized with goog.getMsg.
        2152 *
        2153 * @param {string} a The preferred message.
        2154 * @param {string} b The fallback message.
        2155 * @return {string} The best translated message.
        2156 */
        2157goog.getMsgWithFallback = function(a, b) {
        2158 return a;
        2159};
        2160
        2161
        2162/**
        2163 * Exposes an unobfuscated global namespace path for the given object.
        2164 * Note that fields of the exported object *will* be obfuscated, unless they are
        2165 * exported in turn via this function or goog.exportProperty.
        2166 *
        2167 * Also handy for making public items that are defined in anonymous closures.
        2168 *
        2169 * ex. goog.exportSymbol('public.path.Foo', Foo);
        2170 *
        2171 * ex. goog.exportSymbol('public.path.Foo.staticFunction', Foo.staticFunction);
        2172 * public.path.Foo.staticFunction();
        2173 *
        2174 * ex. goog.exportSymbol('public.path.Foo.prototype.myMethod',
        2175 * Foo.prototype.myMethod);
        2176 * new public.path.Foo().myMethod();
        2177 *
        2178 * @param {string} publicPath Unobfuscated name to export.
        2179 * @param {*} object Object the name should point to.
        2180 * @param {Object=} opt_objectToExportTo The object to add the path to; default
        2181 * is goog.global.
        2182 */
        2183goog.exportSymbol = function(publicPath, object, opt_objectToExportTo) {
        2184 goog.exportPath_(publicPath, object, opt_objectToExportTo);
        2185};
        2186
        2187
        2188/**
        2189 * Exports a property unobfuscated into the object's namespace.
        2190 * ex. goog.exportProperty(Foo, 'staticFunction', Foo.staticFunction);
        2191 * ex. goog.exportProperty(Foo.prototype, 'myMethod', Foo.prototype.myMethod);
        2192 * @param {Object} object Object whose static property is being exported.
        2193 * @param {string} publicName Unobfuscated name to export.
        2194 * @param {*} symbol Object the name should point to.
        2195 */
        2196goog.exportProperty = function(object, publicName, symbol) {
        2197 object[publicName] = symbol;
        2198};
        2199
        2200
        2201/**
        2202 * Inherit the prototype methods from one constructor into another.
        2203 *
        2204 * Usage:
        2205 * <pre>
        2206 * function ParentClass(a, b) { }
        2207 * ParentClass.prototype.foo = function(a) { };
        2208 *
        2209 * function ChildClass(a, b, c) {
        2210 * ChildClass.base(this, 'constructor', a, b);
        2211 * }
        2212 * goog.inherits(ChildClass, ParentClass);
        2213 *
        2214 * var child = new ChildClass('a', 'b', 'see');
        2215 * child.foo(); // This works.
        2216 * </pre>
        2217 *
        2218 * @param {Function} childCtor Child class.
        2219 * @param {Function} parentCtor Parent class.
        2220 */
        2221goog.inherits = function(childCtor, parentCtor) {
        2222 /** @constructor */
        2223 function tempCtor() {};
        2224 tempCtor.prototype = parentCtor.prototype;
        2225 childCtor.superClass_ = parentCtor.prototype;
        2226 childCtor.prototype = new tempCtor();
        2227 /** @override */
        2228 childCtor.prototype.constructor = childCtor;
        2229
        2230 /**
        2231 * Calls superclass constructor/method.
        2232 *
        2233 * This function is only available if you use goog.inherits to
        2234 * express inheritance relationships between classes.
        2235 *
        2236 * NOTE: This is a replacement for goog.base and for superClass_
        2237 * property defined in childCtor.
        2238 *
        2239 * @param {!Object} me Should always be "this".
        2240 * @param {string} methodName The method name to call. Calling
        2241 * superclass constructor can be done with the special string
        2242 * 'constructor'.
        2243 * @param {...*} var_args The arguments to pass to superclass
        2244 * method/constructor.
        2245 * @return {*} The return value of the superclass method/constructor.
        2246 */
        2247 childCtor.base = function(me, methodName, var_args) {
        2248 // Copying using loop to avoid deop due to passing arguments object to
        2249 // function. This is faster in many JS engines as of late 2014.
        2250 var args = new Array(arguments.length - 2);
        2251 for (var i = 2; i < arguments.length; i++) {
        2252 args[i - 2] = arguments[i];
        2253 }
        2254 return parentCtor.prototype[methodName].apply(me, args);
        2255 };
        2256};
        2257
        2258
        2259/**
        2260 * Call up to the superclass.
        2261 *
        2262 * If this is called from a constructor, then this calls the superclass
        2263 * constructor with arguments 1-N.
        2264 *
        2265 * If this is called from a prototype method, then you must pass the name of the
        2266 * method as the second argument to this function. If you do not, you will get a
        2267 * runtime error. This calls the superclass' method with arguments 2-N.
        2268 *
        2269 * This function only works if you use goog.inherits to express inheritance
        2270 * relationships between your classes.
        2271 *
        2272 * This function is a compiler primitive. At compile-time, the compiler will do
        2273 * macro expansion to remove a lot of the extra overhead that this function
        2274 * introduces. The compiler will also enforce a lot of the assumptions that this
        2275 * function makes, and treat it as a compiler error if you break them.
        2276 *
        2277 * @param {!Object} me Should always be "this".
        2278 * @param {*=} opt_methodName The method name if calling a super method.
        2279 * @param {...*} var_args The rest of the arguments.
        2280 * @return {*} The return value of the superclass method.
        2281 * @suppress {es5Strict} This method can not be used in strict mode, but
        2282 * all Closure Library consumers must depend on this file.
        2283 */
        2284goog.base = function(me, opt_methodName, var_args) {
        2285 var caller = arguments.callee.caller;
        2286
        2287 if (goog.STRICT_MODE_COMPATIBLE || (goog.DEBUG && !caller)) {
        2288 throw Error('arguments.caller not defined. goog.base() cannot be used ' +
        2289 'with strict mode code. See ' +
        2290 'http://www.ecma-international.org/ecma-262/5.1/#sec-C');
        2291 }
        2292
        2293 if (caller.superClass_) {
        2294 // Copying using loop to avoid deop due to passing arguments object to
        2295 // function. This is faster in many JS engines as of late 2014.
        2296 var ctorArgs = new Array(arguments.length - 1);
        2297 for (var i = 1; i < arguments.length; i++) {
        2298 ctorArgs[i - 1] = arguments[i];
        2299 }
        2300 // This is a constructor. Call the superclass constructor.
        2301 return caller.superClass_.constructor.apply(me, ctorArgs);
        2302 }
        2303
        2304 // Copying using loop to avoid deop due to passing arguments object to
        2305 // function. This is faster in many JS engines as of late 2014.
        2306 var args = new Array(arguments.length - 2);
        2307 for (var i = 2; i < arguments.length; i++) {
        2308 args[i - 2] = arguments[i];
        2309 }
        2310 var foundCaller = false;
        2311 for (var ctor = me.constructor;
        2312 ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
        2313 if (ctor.prototype[opt_methodName] === caller) {
        2314 foundCaller = true;
        2315 } else if (foundCaller) {
        2316 return ctor.prototype[opt_methodName].apply(me, args);
        2317 }
        2318 }
        2319
        2320 // If we did not find the caller in the prototype chain, then one of two
        2321 // things happened:
        2322 // 1) The caller is an instance method.
        2323 // 2) This method was not called by the right caller.
        2324 if (me[opt_methodName] === caller) {
        2325 return me.constructor.prototype[opt_methodName].apply(me, args);
        2326 } else {
        2327 throw Error(
        2328 'goog.base called from a method of one name ' +
        2329 'to a method of a different name');
        2330 }
        2331};
        2332
        2333
        2334/**
        2335 * Allow for aliasing within scope functions. This function exists for
        2336 * uncompiled code - in compiled code the calls will be inlined and the aliases
        2337 * applied. In uncompiled code the function is simply run since the aliases as
        2338 * written are valid JavaScript.
        2339 *
        2340 *
        2341 * @param {function()} fn Function to call. This function can contain aliases
        2342 * to namespaces (e.g. "var dom = goog.dom") or classes
        2343 * (e.g. "var Timer = goog.Timer").
        2344 */
        2345goog.scope = function(fn) {
        2346 fn.call(goog.global);
        2347};
        2348
        2349
        2350/*
        2351 * To support uncompiled, strict mode bundles that use eval to divide source
        2352 * like so:
        2353 * eval('someSource;//# sourceUrl sourcefile.js');
        2354 * We need to export the globally defined symbols "goog" and "COMPILED".
        2355 * Exporting "goog" breaks the compiler optimizations, so we required that
        2356 * be defined externally.
        2357 * NOTE: We don't use goog.exportSymbol here because we don't want to trigger
        2358 * extern generation when that compiler option is enabled.
        2359 */
        2360if (!COMPILED) {
        2361 goog.global['COMPILED'] = COMPILED;
        2362}
        2363
        2364
        2365
        2366//==============================================================================
        2367// goog.defineClass implementation
        2368//==============================================================================
        2369
        2370
        2371/**
        2372 * Creates a restricted form of a Closure "class":
        2373 * - from the compiler's perspective, the instance returned from the
        2374 * constructor is sealed (no new properties may be added). This enables
        2375 * better checks.
        2376 * - the compiler will rewrite this definition to a form that is optimal
        2377 * for type checking and optimization (initially this will be a more
        2378 * traditional form).
        2379 *
        2380 * @param {Function} superClass The superclass, Object or null.
        2381 * @param {goog.defineClass.ClassDescriptor} def
        2382 * An object literal describing
        2383 * the class. It may have the following properties:
        2384 * "constructor": the constructor function
        2385 * "statics": an object literal containing methods to add to the constructor
        2386 * as "static" methods or a function that will receive the constructor
        2387 * function as its only parameter to which static properties can
        2388 * be added.
        2389 * all other properties are added to the prototype.
        2390 * @return {!Function} The class constructor.
        2391 */
        2392goog.defineClass = function(superClass, def) {
        2393 // TODO(johnlenz): consider making the superClass an optional parameter.
        2394 var constructor = def.constructor;
        2395 var statics = def.statics;
        2396 // Wrap the constructor prior to setting up the prototype and static methods.
        2397 if (!constructor || constructor == Object.prototype.constructor) {
        2398 constructor = function() {
        2399 throw Error('cannot instantiate an interface (no constructor defined).');
        2400 };
        2401 }
        2402
        2403 var cls = goog.defineClass.createSealingConstructor_(constructor, superClass);
        2404 if (superClass) {
        2405 goog.inherits(cls, superClass);
        2406 }
        2407
        2408 // Remove all the properties that should not be copied to the prototype.
        2409 delete def.constructor;
        2410 delete def.statics;
        2411
        2412 goog.defineClass.applyProperties_(cls.prototype, def);
        2413 if (statics != null) {
        2414 if (statics instanceof Function) {
        2415 statics(cls);
        2416 } else {
        2417 goog.defineClass.applyProperties_(cls, statics);
        2418 }
        2419 }
        2420
        2421 return cls;
        2422};
        2423
        2424
        2425/**
        2426 * @typedef {
        2427 * !Object|
        2428 * {constructor:!Function}|
        2429 * {constructor:!Function, statics:(Object|function(Function):void)}}
        2430 * @suppress {missingProvide}
        2431 */
        2432goog.defineClass.ClassDescriptor;
        2433
        2434
        2435/**
        2436 * @define {boolean} Whether the instances returned by
        2437 * goog.defineClass should be sealed when possible.
        2438 */
        2439goog.define('goog.defineClass.SEAL_CLASS_INSTANCES', goog.DEBUG);
        2440
        2441
        2442/**
        2443 * If goog.defineClass.SEAL_CLASS_INSTANCES is enabled and Object.seal is
        2444 * defined, this function will wrap the constructor in a function that seals the
        2445 * results of the provided constructor function.
        2446 *
        2447 * @param {!Function} ctr The constructor whose results maybe be sealed.
        2448 * @param {Function} superClass The superclass constructor.
        2449 * @return {!Function} The replacement constructor.
        2450 * @private
        2451 */
        2452goog.defineClass.createSealingConstructor_ = function(ctr, superClass) {
        2453 if (goog.defineClass.SEAL_CLASS_INSTANCES &&
        2454 Object.seal instanceof Function) {
        2455 // Don't seal subclasses of unsealable-tagged legacy classes.
        2456 if (superClass && superClass.prototype &&
        2457 superClass.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_]) {
        2458 return ctr;
        2459 }
        2460 /**
        2461 * @this {Object}
        2462 * @return {?}
        2463 */
        2464 var wrappedCtr = function() {
        2465 // Don't seal an instance of a subclass when it calls the constructor of
        2466 // its super class as there is most likely still setup to do.
        2467 var instance = ctr.apply(this, arguments) || this;
        2468 instance[goog.UID_PROPERTY_] = instance[goog.UID_PROPERTY_];
        2469 if (this.constructor === wrappedCtr) {
        2470 Object.seal(instance);
        2471 }
        2472 return instance;
        2473 };
        2474 return wrappedCtr;
        2475 }
        2476 return ctr;
        2477};
        2478
        2479
        2480// TODO(johnlenz): share these values with the goog.object
        2481/**
        2482 * The names of the fields that are defined on Object.prototype.
        2483 * @type {!Array<string>}
        2484 * @private
        2485 * @const
        2486 */
        2487goog.defineClass.OBJECT_PROTOTYPE_FIELDS_ = [
        2488 'constructor',
        2489 'hasOwnProperty',
        2490 'isPrototypeOf',
        2491 'propertyIsEnumerable',
        2492 'toLocaleString',
        2493 'toString',
        2494 'valueOf'
        2495];
        2496
        2497
        2498// TODO(johnlenz): share this function with the goog.object
        2499/**
        2500 * @param {!Object} target The object to add properties to.
        2501 * @param {!Object} source The object to copy properties from.
        2502 * @private
        2503 */
        2504goog.defineClass.applyProperties_ = function(target, source) {
        2505 // TODO(johnlenz): update this to support ES5 getters/setters
        2506
        2507 var key;
        2508 for (key in source) {
        2509 if (Object.prototype.hasOwnProperty.call(source, key)) {
        2510 target[key] = source[key];
        2511 }
        2512 }
        2513
        2514 // For IE the for-in-loop does not contain any properties that are not
        2515 // enumerable on the prototype object (for example isPrototypeOf from
        2516 // Object.prototype) and it will also not include 'replace' on objects that
        2517 // extend String and change 'replace' (not that it is common for anyone to
        2518 // extend anything except Object).
        2519 for (var i = 0; i < goog.defineClass.OBJECT_PROTOTYPE_FIELDS_.length; i++) {
        2520 key = goog.defineClass.OBJECT_PROTOTYPE_FIELDS_[i];
        2521 if (Object.prototype.hasOwnProperty.call(source, key)) {
        2522 target[key] = source[key];
        2523 }
        2524 }
        2525};
        2526
        2527
        2528/**
        2529 * Sealing classes breaks the older idiom of assigning properties on the
        2530 * prototype rather than in the constructor. As such, goog.defineClass
        2531 * must not seal subclasses of these old-style classes until they are fixed.
        2532 * Until then, this marks a class as "broken", instructing defineClass
        2533 * not to seal subclasses.
        2534 * @param {!Function} ctr The legacy constructor to tag as unsealable.
        2535 */
        2536goog.tagUnsealableClass = function(ctr) {
        2537 if (!COMPILED && goog.defineClass.SEAL_CLASS_INSTANCES) {
        2538 ctr.prototype[goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_] = true;
        2539 }
        2540};
        2541
        2542
        2543/**
        2544 * Name for unsealable tag property.
        2545 * @const @private {string}
        2546 */
        2547goog.UNSEALABLE_CONSTRUCTOR_PROPERTY_ = 'goog_defineClass_legacy_unsealable';
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/debug.js.src.html b/docs/source/lib/goog/debug/debug.js.src.html new file mode 100644 index 0000000..11ee87e --- /dev/null +++ b/docs/source/lib/goog/debug/debug.js.src.html @@ -0,0 +1 @@ +debug.js

        lib/goog/debug/debug.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Logging and debugging utilities.
        17 *
        18 * @see ../demos/debug.html
        19 */
        20
        21goog.provide('goog.debug');
        22
        23goog.require('goog.array');
        24goog.require('goog.html.SafeHtml');
        25goog.require('goog.html.SafeUrl');
        26goog.require('goog.html.uncheckedconversions');
        27goog.require('goog.string.Const');
        28goog.require('goog.structs.Set');
        29goog.require('goog.userAgent');
        30
        31
        32/** @define {boolean} Whether logging should be enabled. */
        33goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
        34
        35
        36/**
        37 * Catches onerror events fired by windows and similar objects.
        38 * @param {function(Object)} logFunc The function to call with the error
        39 * information.
        40 * @param {boolean=} opt_cancel Whether to stop the error from reaching the
        41 * browser.
        42 * @param {Object=} opt_target Object that fires onerror events.
        43 */
        44goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
        45 var target = opt_target || goog.global;
        46 var oldErrorHandler = target.onerror;
        47 var retVal = !!opt_cancel;
        48
        49 // Chrome interprets onerror return value backwards (http://crbug.com/92062)
        50 // until it was fixed in webkit revision r94061 (Webkit 535.3). This
        51 // workaround still needs to be skipped in Safari after the webkit change
        52 // gets pushed out in Safari.
        53 // See https://bugs.webkit.org/show_bug.cgi?id=67119
        54 if (goog.userAgent.WEBKIT &&
        55 !goog.userAgent.isVersionOrHigher('535.3')) {
        56 retVal = !retVal;
        57 }
        58
        59 /**
        60 * New onerror handler for this target. This onerror handler follows the spec
        61 * according to
        62 * http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
        63 * The spec was changed in August 2013 to support receiving column information
        64 * and an error object for all scripts on the same origin or cross origin
        65 * scripts with the proper headers. See
        66 * https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
        67 *
        68 * @param {string} message The error message. For cross-origin errors, this
        69 * will be scrubbed to just "Script error.". For new browsers that have
        70 * updated to follow the latest spec, errors that come from origins that
        71 * have proper cross origin headers will not be scrubbed.
        72 * @param {string} url The URL of the script that caused the error. The URL
        73 * will be scrubbed to "" for cross origin scripts unless the script has
        74 * proper cross origin headers and the browser has updated to the latest
        75 * spec.
        76 * @param {number} line The line number in the script that the error
        77 * occurred on.
        78 * @param {number=} opt_col The optional column number that the error
        79 * occurred on. Only browsers that have updated to the latest spec will
        80 * include this.
        81 * @param {Error=} opt_error The optional actual error object for this
        82 * error that should include the stack. Only browsers that have updated
        83 * to the latest spec will inlude this parameter.
        84 * @return {boolean} Whether to prevent the error from reaching the browser.
        85 */
        86 target.onerror = function(message, url, line, opt_col, opt_error) {
        87 if (oldErrorHandler) {
        88 oldErrorHandler(message, url, line, opt_col, opt_error);
        89 }
        90 logFunc({
        91 message: message,
        92 fileName: url,
        93 line: line,
        94 col: opt_col,
        95 error: opt_error
        96 });
        97 return retVal;
        98 };
        99};
        100
        101
        102/**
        103 * Creates a string representing an object and all its properties.
        104 * @param {Object|null|undefined} obj Object to expose.
        105 * @param {boolean=} opt_showFn Show the functions as well as the properties,
        106 * default is false.
        107 * @return {string} The string representation of {@code obj}.
        108 */
        109goog.debug.expose = function(obj, opt_showFn) {
        110 if (typeof obj == 'undefined') {
        111 return 'undefined';
        112 }
        113 if (obj == null) {
        114 return 'NULL';
        115 }
        116 var str = [];
        117
        118 for (var x in obj) {
        119 if (!opt_showFn && goog.isFunction(obj[x])) {
        120 continue;
        121 }
        122 var s = x + ' = ';
        123 /** @preserveTry */
        124 try {
        125 s += obj[x];
        126 } catch (e) {
        127 s += '*** ' + e + ' ***';
        128 }
        129 str.push(s);
        130 }
        131 return str.join('\n');
        132};
        133
        134
        135/**
        136 * Creates a string representing a given primitive or object, and for an
        137 * object, all its properties and nested objects. WARNING: If an object is
        138 * given, it and all its nested objects will be modified. To detect reference
        139 * cycles, this method identifies objects using goog.getUid() which mutates the
        140 * object.
        141 * @param {*} obj Object to expose.
        142 * @param {boolean=} opt_showFn Also show properties that are functions (by
        143 * default, functions are omitted).
        144 * @return {string} A string representation of {@code obj}.
        145 */
        146goog.debug.deepExpose = function(obj, opt_showFn) {
        147 var str = [];
        148
        149 var helper = function(obj, space, parentSeen) {
        150 var nestspace = space + ' ';
        151 var seen = new goog.structs.Set(parentSeen);
        152
        153 var indentMultiline = function(str) {
        154 return str.replace(/\n/g, '\n' + space);
        155 };
        156
        157 /** @preserveTry */
        158 try {
        159 if (!goog.isDef(obj)) {
        160 str.push('undefined');
        161 } else if (goog.isNull(obj)) {
        162 str.push('NULL');
        163 } else if (goog.isString(obj)) {
        164 str.push('"' + indentMultiline(obj) + '"');
        165 } else if (goog.isFunction(obj)) {
        166 str.push(indentMultiline(String(obj)));
        167 } else if (goog.isObject(obj)) {
        168 if (seen.contains(obj)) {
        169 str.push('*** reference loop detected ***');
        170 } else {
        171 seen.add(obj);
        172 str.push('{');
        173 for (var x in obj) {
        174 if (!opt_showFn && goog.isFunction(obj[x])) {
        175 continue;
        176 }
        177 str.push('\n');
        178 str.push(nestspace);
        179 str.push(x + ' = ');
        180 helper(obj[x], nestspace, seen);
        181 }
        182 str.push('\n' + space + '}');
        183 }
        184 } else {
        185 str.push(obj);
        186 }
        187 } catch (e) {
        188 str.push('*** ' + e + ' ***');
        189 }
        190 };
        191
        192 helper(obj, '', new goog.structs.Set());
        193 return str.join('');
        194};
        195
        196
        197/**
        198 * Recursively outputs a nested array as a string.
        199 * @param {Array<?>} arr The array.
        200 * @return {string} String representing nested array.
        201 */
        202goog.debug.exposeArray = function(arr) {
        203 var str = [];
        204 for (var i = 0; i < arr.length; i++) {
        205 if (goog.isArray(arr[i])) {
        206 str.push(goog.debug.exposeArray(arr[i]));
        207 } else {
        208 str.push(arr[i]);
        209 }
        210 }
        211 return '[ ' + str.join(', ') + ' ]';
        212};
        213
        214
        215/**
        216 * Exposes an exception that has been caught by a try...catch and outputs the
        217 * error as HTML with a stack trace.
        218 * @param {Object} err Error object or string.
        219 * @param {Function=} opt_fn Optional function to start stack trace from.
        220 * @return {string} Details of exception, as HTML.
        221 */
        222goog.debug.exposeException = function(err, opt_fn) {
        223 var html = goog.debug.exposeExceptionAsHtml(err, opt_fn);
        224 return goog.html.SafeHtml.unwrap(html);
        225};
        226
        227
        228/**
        229 * Exposes an exception that has been caught by a try...catch and outputs the
        230 * error with a stack trace.
        231 * @param {Object} err Error object or string.
        232 * @param {Function=} opt_fn Optional function to start stack trace from.
        233 * @return {!goog.html.SafeHtml} Details of exception.
        234 */
        235goog.debug.exposeExceptionAsHtml = function(err, opt_fn) {
        236 /** @preserveTry */
        237 try {
        238 var e = goog.debug.normalizeErrorObject(err);
        239 // Create the error message
        240 var viewSourceUrl = goog.debug.createViewSourceUrl_(e.fileName);
        241 var error = goog.html.SafeHtml.concat(
        242 goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
        243 'Message: ' + e.message + '\nUrl: '),
        244 goog.html.SafeHtml.create('a',
        245 {href: viewSourceUrl, target: '_new'}, e.fileName),
        246 goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
        247 '\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' +
        248 e.stack + '-> ' + '[end]\n\nJS stack traversal:\n' +
        249 goog.debug.getStacktrace(opt_fn) + '-> '));
        250 return error;
        251 } catch (e2) {
        252 return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
        253 'Exception trying to expose exception! You win, we lose. ' + e2);
        254 }
        255};
        256
        257
        258/**
        259 * @param {?string=} opt_fileName
        260 * @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
        261 * fileName.
        262 * @private
        263 */
        264goog.debug.createViewSourceUrl_ = function(opt_fileName) {
        265 if (!goog.isDefAndNotNull(opt_fileName)) {
        266 opt_fileName = '';
        267 }
        268 if (!/^https?:\/\//i.test(opt_fileName)) {
        269 return goog.html.SafeUrl.fromConstant(
        270 goog.string.Const.from('sanitizedviewsrc'));
        271 }
        272 var sanitizedFileName = goog.html.SafeUrl.sanitize(opt_fileName);
        273 return goog.html.uncheckedconversions.
        274 safeUrlFromStringKnownToSatisfyTypeContract(
        275 goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),
        276 'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));
        277};
        278
        279
        280/**
        281 * Normalizes the error/exception object between browsers.
        282 * @param {Object} err Raw error object.
        283 * @return {!Object} Normalized error object.
        284 */
        285goog.debug.normalizeErrorObject = function(err) {
        286 var href = goog.getObjectByName('window.location.href');
        287 if (goog.isString(err)) {
        288 return {
        289 'message': err,
        290 'name': 'Unknown error',
        291 'lineNumber': 'Not available',
        292 'fileName': href,
        293 'stack': 'Not available'
        294 };
        295 }
        296
        297 var lineNumber, fileName;
        298 var threwError = false;
        299
        300 try {
        301 lineNumber = err.lineNumber || err.line || 'Not available';
        302 } catch (e) {
        303 // Firefox 2 sometimes throws an error when accessing 'lineNumber':
        304 // Message: Permission denied to get property UnnamedClass.lineNumber
        305 lineNumber = 'Not available';
        306 threwError = true;
        307 }
        308
        309 try {
        310 fileName = err.fileName || err.filename || err.sourceURL ||
        311 // $googDebugFname may be set before a call to eval to set the filename
        312 // that the eval is supposed to present.
        313 goog.global['$googDebugFname'] || href;
        314 } catch (e) {
        315 // Firefox 2 may also throw an error when accessing 'filename'.
        316 fileName = 'Not available';
        317 threwError = true;
        318 }
        319
        320 // The IE Error object contains only the name and the message.
        321 // The Safari Error object uses the line and sourceURL fields.
        322 if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
        323 !err.message || !err.name) {
        324 return {
        325 'message': err.message || 'Not available',
        326 'name': err.name || 'UnknownError',
        327 'lineNumber': lineNumber,
        328 'fileName': fileName,
        329 'stack': err.stack || 'Not available'
        330 };
        331 }
        332
        333 // Standards error object
        334 return err;
        335};
        336
        337
        338/**
        339 * Converts an object to an Error if it's a String,
        340 * adds a stacktrace if there isn't one,
        341 * and optionally adds an extra message.
        342 * @param {Error|string} err the original thrown object or string.
        343 * @param {string=} opt_message optional additional message to add to the
        344 * error.
        345 * @return {!Error} If err is a string, it is used to create a new Error,
        346 * which is enhanced and returned. Otherwise err itself is enhanced
        347 * and returned.
        348 */
        349goog.debug.enhanceError = function(err, opt_message) {
        350 var error;
        351 if (typeof err == 'string') {
        352 error = Error(err);
        353 if (Error.captureStackTrace) {
        354 // Trim this function off the call stack, if we can.
        355 Error.captureStackTrace(error, goog.debug.enhanceError);
        356 }
        357 } else {
        358 error = err;
        359 }
        360
        361 if (!error.stack) {
        362 error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
        363 }
        364 if (opt_message) {
        365 // find the first unoccupied 'messageX' property
        366 var x = 0;
        367 while (error['message' + x]) {
        368 ++x;
        369 }
        370 error['message' + x] = String(opt_message);
        371 }
        372 return error;
        373};
        374
        375
        376/**
        377 * Gets the current stack trace. Simple and iterative - doesn't worry about
        378 * catching circular references or getting the args.
        379 * @param {number=} opt_depth Optional maximum depth to trace back to.
        380 * @return {string} A string with the function names of all functions in the
        381 * stack, separated by \n.
        382 * @suppress {es5Strict}
        383 */
        384goog.debug.getStacktraceSimple = function(opt_depth) {
        385 if (goog.STRICT_MODE_COMPATIBLE) {
        386 var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
        387 if (stack) {
        388 return stack;
        389 }
        390 // NOTE: browsers that have strict mode support also have native "stack"
        391 // properties. Fall-through for legacy browser support.
        392 }
        393
        394 var sb = [];
        395 var fn = arguments.callee.caller;
        396 var depth = 0;
        397
        398 while (fn && (!opt_depth || depth < opt_depth)) {
        399 sb.push(goog.debug.getFunctionName(fn));
        400 sb.push('()\n');
        401 /** @preserveTry */
        402 try {
        403 fn = fn.caller;
        404 } catch (e) {
        405 sb.push('[exception trying to get caller]\n');
        406 break;
        407 }
        408 depth++;
        409 if (depth >= goog.debug.MAX_STACK_DEPTH) {
        410 sb.push('[...long stack...]');
        411 break;
        412 }
        413 }
        414 if (opt_depth && depth >= opt_depth) {
        415 sb.push('[...reached max depth limit...]');
        416 } else {
        417 sb.push('[end]');
        418 }
        419
        420 return sb.join('');
        421};
        422
        423
        424/**
        425 * Max length of stack to try and output
        426 * @type {number}
        427 */
        428goog.debug.MAX_STACK_DEPTH = 50;
        429
        430
        431/**
        432 * @param {Function} fn The function to start getting the trace from.
        433 * @return {?string}
        434 * @private
        435 */
        436goog.debug.getNativeStackTrace_ = function(fn) {
        437 var tempErr = new Error();
        438 if (Error.captureStackTrace) {
        439 Error.captureStackTrace(tempErr, fn);
        440 return String(tempErr.stack);
        441 } else {
        442 // IE10, only adds stack traces when an exception is thrown.
        443 try {
        444 throw tempErr;
        445 } catch (e) {
        446 tempErr = e;
        447 }
        448 var stack = tempErr.stack;
        449 if (stack) {
        450 return String(stack);
        451 }
        452 }
        453 return null;
        454};
        455
        456
        457/**
        458 * Gets the current stack trace, either starting from the caller or starting
        459 * from a specified function that's currently on the call stack.
        460 * @param {Function=} opt_fn Optional function to start getting the trace from.
        461 * If not provided, defaults to the function that called this.
        462 * @return {string} Stack trace.
        463 * @suppress {es5Strict}
        464 */
        465goog.debug.getStacktrace = function(opt_fn) {
        466 var stack;
        467 if (goog.STRICT_MODE_COMPATIBLE) {
        468 // Try to get the stack trace from the environment if it is available.
        469 var contextFn = opt_fn || goog.debug.getStacktrace;
        470 stack = goog.debug.getNativeStackTrace_(contextFn);
        471 }
        472 if (!stack) {
        473 // NOTE: browsers that have strict mode support also have native "stack"
        474 // properties. This function will throw in strict mode.
        475 stack = goog.debug.getStacktraceHelper_(
        476 opt_fn || arguments.callee.caller, []);
        477 }
        478 return stack;
        479};
        480
        481
        482/**
        483 * Private helper for getStacktrace().
        484 * @param {Function} fn Function to start getting the trace from.
        485 * @param {Array<!Function>} visited List of functions visited so far.
        486 * @return {string} Stack trace starting from function fn.
        487 * @suppress {es5Strict}
        488 * @private
        489 */
        490goog.debug.getStacktraceHelper_ = function(fn, visited) {
        491 var sb = [];
        492
        493 // Circular reference, certain functions like bind seem to cause a recursive
        494 // loop so we need to catch circular references
        495 if (goog.array.contains(visited, fn)) {
        496 sb.push('[...circular reference...]');
        497
        498 // Traverse the call stack until function not found or max depth is reached
        499 } else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
        500 sb.push(goog.debug.getFunctionName(fn) + '(');
        501 var args = fn.arguments;
        502 // Args may be null for some special functions such as host objects or eval.
        503 for (var i = 0; args && i < args.length; i++) {
        504 if (i > 0) {
        505 sb.push(', ');
        506 }
        507 var argDesc;
        508 var arg = args[i];
        509 switch (typeof arg) {
        510 case 'object':
        511 argDesc = arg ? 'object' : 'null';
        512 break;
        513
        514 case 'string':
        515 argDesc = arg;
        516 break;
        517
        518 case 'number':
        519 argDesc = String(arg);
        520 break;
        521
        522 case 'boolean':
        523 argDesc = arg ? 'true' : 'false';
        524 break;
        525
        526 case 'function':
        527 argDesc = goog.debug.getFunctionName(arg);
        528 argDesc = argDesc ? argDesc : '[fn]';
        529 break;
        530
        531 case 'undefined':
        532 default:
        533 argDesc = typeof arg;
        534 break;
        535 }
        536
        537 if (argDesc.length > 40) {
        538 argDesc = argDesc.substr(0, 40) + '...';
        539 }
        540 sb.push(argDesc);
        541 }
        542 visited.push(fn);
        543 sb.push(')\n');
        544 /** @preserveTry */
        545 try {
        546 sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
        547 } catch (e) {
        548 sb.push('[exception trying to get caller]\n');
        549 }
        550
        551 } else if (fn) {
        552 sb.push('[...long stack...]');
        553 } else {
        554 sb.push('[end]');
        555 }
        556 return sb.join('');
        557};
        558
        559
        560/**
        561 * Set a custom function name resolver.
        562 * @param {function(Function): string} resolver Resolves functions to their
        563 * names.
        564 */
        565goog.debug.setFunctionResolver = function(resolver) {
        566 goog.debug.fnNameResolver_ = resolver;
        567};
        568
        569
        570/**
        571 * Gets a function name
        572 * @param {Function} fn Function to get name of.
        573 * @return {string} Function's name.
        574 */
        575goog.debug.getFunctionName = function(fn) {
        576 if (goog.debug.fnNameCache_[fn]) {
        577 return goog.debug.fnNameCache_[fn];
        578 }
        579 if (goog.debug.fnNameResolver_) {
        580 var name = goog.debug.fnNameResolver_(fn);
        581 if (name) {
        582 goog.debug.fnNameCache_[fn] = name;
        583 return name;
        584 }
        585 }
        586
        587 // Heuristically determine function name based on code.
        588 var functionSource = String(fn);
        589 if (!goog.debug.fnNameCache_[functionSource]) {
        590 var matches = /function ([^\(]+)/.exec(functionSource);
        591 if (matches) {
        592 var method = matches[1];
        593 goog.debug.fnNameCache_[functionSource] = method;
        594 } else {
        595 goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
        596 }
        597 }
        598
        599 return goog.debug.fnNameCache_[functionSource];
        600};
        601
        602
        603/**
        604 * Makes whitespace visible by replacing it with printable characters.
        605 * This is useful in finding diffrences between the expected and the actual
        606 * output strings of a testcase.
        607 * @param {string} string whose whitespace needs to be made visible.
        608 * @return {string} string whose whitespace is made visible.
        609 */
        610goog.debug.makeWhitespaceVisible = function(string) {
        611 return string.replace(/ /g, '[_]')
        612 .replace(/\f/g, '[f]')
        613 .replace(/\n/g, '[n]\n')
        614 .replace(/\r/g, '[r]')
        615 .replace(/\t/g, '[t]');
        616};
        617
        618
        619/**
        620 * Returns the type of a value. If a constructor is passed, and a suitable
        621 * string cannot be found, 'unknown type name' will be returned.
        622 *
        623 * <p>Forked rather than moved from {@link goog.asserts.getType_}
        624 * to avoid adding a dependency to goog.asserts.
        625 * @param {*} value A constructor, object, or primitive.
        626 * @return {string} The best display name for the value, or 'unknown type name'.
        627 */
        628goog.debug.runtimeType = function(value) {
        629 if (value instanceof Function) {
        630 return value.displayName || value.name || 'unknown type name';
        631 } else if (value instanceof Object) {
        632 return value.constructor.displayName || value.constructor.name ||
        633 Object.prototype.toString.call(value);
        634 } else {
        635 return value === null ? 'null' : typeof value;
        636 }
        637};
        638
        639
        640/**
        641 * Hash map for storing function names that have already been looked up.
        642 * @type {Object}
        643 * @private
        644 */
        645goog.debug.fnNameCache_ = {};
        646
        647
        648/**
        649 * Resolves functions to their names. Resolved function names will be cached.
        650 * @type {function(Function):string}
        651 * @private
        652 */
        653goog.debug.fnNameResolver_;
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/entrypointregistry.js.src.html b/docs/source/lib/goog/debug/entrypointregistry.js.src.html new file mode 100644 index 0000000..9bbf8bb --- /dev/null +++ b/docs/source/lib/goog/debug/entrypointregistry.js.src.html @@ -0,0 +1 @@ +entrypointregistry.js

        lib/goog/debug/entrypointregistry.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A global registry for entry points into a program,
        17 * so that they can be instrumented. Each module should register their
        18 * entry points with this registry. Designed to be compiled out
        19 * if no instrumentation is requested.
        20 *
        21 * Entry points may be registered before or after a call to
        22 * goog.debug.entryPointRegistry.monitorAll. If an entry point is registered
        23 * later, the existing monitor will instrument the new entry point.
        24 *
        25 * @author nicksantos@google.com (Nick Santos)
        26 */
        27
        28goog.provide('goog.debug.EntryPointMonitor');
        29goog.provide('goog.debug.entryPointRegistry');
        30
        31goog.require('goog.asserts');
        32
        33
        34
        35/**
        36 * @interface
        37 */
        38goog.debug.EntryPointMonitor = function() {};
        39
        40
        41/**
        42 * Instruments a function.
        43 *
        44 * @param {!Function} fn A function to instrument.
        45 * @return {!Function} The instrumented function.
        46 */
        47goog.debug.EntryPointMonitor.prototype.wrap;
        48
        49
        50/**
        51 * Try to remove an instrumentation wrapper created by this monitor.
        52 * If the function passed to unwrap is not a wrapper created by this
        53 * monitor, then we will do nothing.
        54 *
        55 * Notice that some wrappers may not be unwrappable. For example, if other
        56 * monitors have applied their own wrappers, then it will be impossible to
        57 * unwrap them because their wrappers will have captured our wrapper.
        58 *
        59 * So it is important that entry points are unwrapped in the reverse
        60 * order that they were wrapped.
        61 *
        62 * @param {!Function} fn A function to unwrap.
        63 * @return {!Function} The unwrapped function, or {@code fn} if it was not
        64 * a wrapped function created by this monitor.
        65 */
        66goog.debug.EntryPointMonitor.prototype.unwrap;
        67
        68
        69/**
        70 * An array of entry point callbacks.
        71 * @type {!Array<function(!Function)>}
        72 * @private
        73 */
        74goog.debug.entryPointRegistry.refList_ = [];
        75
        76
        77/**
        78 * Monitors that should wrap all the entry points.
        79 * @type {!Array<!goog.debug.EntryPointMonitor>}
        80 * @private
        81 */
        82goog.debug.entryPointRegistry.monitors_ = [];
        83
        84
        85/**
        86 * Whether goog.debug.entryPointRegistry.monitorAll has ever been called.
        87 * Checking this allows the compiler to optimize out the registrations.
        88 * @type {boolean}
        89 * @private
        90 */
        91goog.debug.entryPointRegistry.monitorsMayExist_ = false;
        92
        93
        94/**
        95 * Register an entry point with this module.
        96 *
        97 * The entry point will be instrumented when a monitor is passed to
        98 * goog.debug.entryPointRegistry.monitorAll. If this has already occurred, the
        99 * entry point is instrumented immediately.
        100 *
        101 * @param {function(!Function)} callback A callback function which is called
        102 * with a transforming function to instrument the entry point. The callback
        103 * is responsible for wrapping the relevant entry point with the
        104 * transforming function.
        105 */
        106goog.debug.entryPointRegistry.register = function(callback) {
        107 // Don't use push(), so that this can be compiled out.
        108 goog.debug.entryPointRegistry.refList_[
        109 goog.debug.entryPointRegistry.refList_.length] = callback;
        110 // If no one calls monitorAll, this can be compiled out.
        111 if (goog.debug.entryPointRegistry.monitorsMayExist_) {
        112 var monitors = goog.debug.entryPointRegistry.monitors_;
        113 for (var i = 0; i < monitors.length; i++) {
        114 callback(goog.bind(monitors[i].wrap, monitors[i]));
        115 }
        116 }
        117};
        118
        119
        120/**
        121 * Configures a monitor to wrap all entry points.
        122 *
        123 * Entry points that have already been registered are immediately wrapped by
        124 * the monitor. When an entry point is registered in the future, it will also
        125 * be wrapped by the monitor when it is registered.
        126 *
        127 * @param {!goog.debug.EntryPointMonitor} monitor An entry point monitor.
        128 */
        129goog.debug.entryPointRegistry.monitorAll = function(monitor) {
        130 goog.debug.entryPointRegistry.monitorsMayExist_ = true;
        131 var transformer = goog.bind(monitor.wrap, monitor);
        132 for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
        133 goog.debug.entryPointRegistry.refList_[i](transformer);
        134 }
        135 goog.debug.entryPointRegistry.monitors_.push(monitor);
        136};
        137
        138
        139/**
        140 * Try to unmonitor all the entry points that have already been registered. If
        141 * an entry point is registered in the future, it will not be wrapped by the
        142 * monitor when it is registered. Note that this may fail if the entry points
        143 * have additional wrapping.
        144 *
        145 * @param {!goog.debug.EntryPointMonitor} monitor The last monitor to wrap
        146 * the entry points.
        147 * @throws {Error} If the monitor is not the most recently configured monitor.
        148 */
        149goog.debug.entryPointRegistry.unmonitorAllIfPossible = function(monitor) {
        150 var monitors = goog.debug.entryPointRegistry.monitors_;
        151 goog.asserts.assert(monitor == monitors[monitors.length - 1],
        152 'Only the most recent monitor can be unwrapped.');
        153 var transformer = goog.bind(monitor.unwrap, monitor);
        154 for (var i = 0; i < goog.debug.entryPointRegistry.refList_.length; i++) {
        155 goog.debug.entryPointRegistry.refList_[i](transformer);
        156 }
        157 monitors.length--;
        158};
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/error.js.src.html b/docs/source/lib/goog/debug/error.js.src.html index 7dec952..0b4c27b 100644 --- a/docs/source/lib/goog/debug/error.js.src.html +++ b/docs/source/lib/goog/debug/error.js.src.html @@ -1 +1 @@ -error.js

        lib/goog/debug/error.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides a base class for custom Error objects such that the
        17 * stack is correctly maintained.
        18 *
        19 * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
        20 * sufficient.
        21 *
        22 */
        23
        24goog.provide('goog.debug.Error');
        25
        26
        27
        28/**
        29 * Base class for custom error objects.
        30 * @param {*=} opt_msg The message associated with the error.
        31 * @constructor
        32 * @extends {Error}
        33 */
        34goog.debug.Error = function(opt_msg) {
        35
        36 // Ensure there is a stack trace.
        37 if (Error.captureStackTrace) {
        38 Error.captureStackTrace(this, goog.debug.Error);
        39 } else {
        40 this.stack = new Error().stack || '';
        41 }
        42
        43 if (opt_msg) {
        44 this.message = String(opt_msg);
        45 }
        46};
        47goog.inherits(goog.debug.Error, Error);
        48
        49
        50/** @override */
        51goog.debug.Error.prototype.name = 'CustomError';
        \ No newline at end of file +error.js

        lib/goog/debug/error.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides a base class for custom Error objects such that the
        17 * stack is correctly maintained.
        18 *
        19 * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is
        20 * sufficient.
        21 *
        22 */
        23
        24goog.provide('goog.debug.Error');
        25
        26
        27
        28/**
        29 * Base class for custom error objects.
        30 * @param {*=} opt_msg The message associated with the error.
        31 * @constructor
        32 * @extends {Error}
        33 */
        34goog.debug.Error = function(opt_msg) {
        35
        36 // Attempt to ensure there is a stack trace.
        37 if (Error.captureStackTrace) {
        38 Error.captureStackTrace(this, goog.debug.Error);
        39 } else {
        40 var stack = new Error().stack;
        41 if (stack) {
        42 this.stack = stack;
        43 }
        44 }
        45
        46 if (opt_msg) {
        47 this.message = String(opt_msg);
        48 }
        49
        50 /**
        51 * Whether to report this error to the server. Setting this to false will
        52 * cause the error reporter to not report the error back to the server,
        53 * which can be useful if the client knows that the error has already been
        54 * logged on the server.
        55 * @type {boolean}
        56 */
        57 this.reportErrorToServer = true;
        58};
        59goog.inherits(goog.debug.Error, Error);
        60
        61
        62/** @override */
        63goog.debug.Error.prototype.name = 'CustomError';
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/logbuffer.js.src.html b/docs/source/lib/goog/debug/logbuffer.js.src.html new file mode 100644 index 0000000..0508996 --- /dev/null +++ b/docs/source/lib/goog/debug/logbuffer.js.src.html @@ -0,0 +1 @@ +logbuffer.js

        lib/goog/debug/logbuffer.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A buffer for log records. The purpose of this is to improve
        17 * logging performance by re-using old objects when the buffer becomes full and
        18 * to eliminate the need for each app to implement their own log buffer. The
        19 * disadvantage to doing this is that log handlers cannot maintain references to
        20 * log records and expect that they are not overwriten at a later point.
        21 *
        22 * @author agrieve@google.com (Andrew Grieve)
        23 */
        24
        25goog.provide('goog.debug.LogBuffer');
        26
        27goog.require('goog.asserts');
        28goog.require('goog.debug.LogRecord');
        29
        30
        31
        32/**
        33 * Creates the log buffer.
        34 * @constructor
        35 * @final
        36 */
        37goog.debug.LogBuffer = function() {
        38 goog.asserts.assert(goog.debug.LogBuffer.isBufferingEnabled(),
        39 'Cannot use goog.debug.LogBuffer without defining ' +
        40 'goog.debug.LogBuffer.CAPACITY.');
        41 this.clear();
        42};
        43
        44
        45/**
        46 * A static method that always returns the same instance of LogBuffer.
        47 * @return {!goog.debug.LogBuffer} The LogBuffer singleton instance.
        48 */
        49goog.debug.LogBuffer.getInstance = function() {
        50 if (!goog.debug.LogBuffer.instance_) {
        51 // This function is written with the return statement after the assignment
        52 // to avoid the jscompiler StripCode bug described in http://b/2608064.
        53 // After that bug is fixed this can be refactored.
        54 goog.debug.LogBuffer.instance_ = new goog.debug.LogBuffer();
        55 }
        56 return goog.debug.LogBuffer.instance_;
        57};
        58
        59
        60/**
        61 * @define {number} The number of log records to buffer. 0 means disable
        62 * buffering.
        63 */
        64goog.define('goog.debug.LogBuffer.CAPACITY', 0);
        65
        66
        67/**
        68 * The array to store the records.
        69 * @type {!Array<!goog.debug.LogRecord|undefined>}
        70 * @private
        71 */
        72goog.debug.LogBuffer.prototype.buffer_;
        73
        74
        75/**
        76 * The index of the most recently added record or -1 if there are no records.
        77 * @type {number}
        78 * @private
        79 */
        80goog.debug.LogBuffer.prototype.curIndex_;
        81
        82
        83/**
        84 * Whether the buffer is at capacity.
        85 * @type {boolean}
        86 * @private
        87 */
        88goog.debug.LogBuffer.prototype.isFull_;
        89
        90
        91/**
        92 * Adds a log record to the buffer, possibly overwriting the oldest record.
        93 * @param {goog.debug.Logger.Level} level One of the level identifiers.
        94 * @param {string} msg The string message.
        95 * @param {string} loggerName The name of the source logger.
        96 * @return {!goog.debug.LogRecord} The log record.
        97 */
        98goog.debug.LogBuffer.prototype.addRecord = function(level, msg, loggerName) {
        99 var curIndex = (this.curIndex_ + 1) % goog.debug.LogBuffer.CAPACITY;
        100 this.curIndex_ = curIndex;
        101 if (this.isFull_) {
        102 var ret = this.buffer_[curIndex];
        103 ret.reset(level, msg, loggerName);
        104 return ret;
        105 }
        106 this.isFull_ = curIndex == goog.debug.LogBuffer.CAPACITY - 1;
        107 return this.buffer_[curIndex] =
        108 new goog.debug.LogRecord(level, msg, loggerName);
        109};
        110
        111
        112/**
        113 * @return {boolean} Whether the log buffer is enabled.
        114 */
        115goog.debug.LogBuffer.isBufferingEnabled = function() {
        116 return goog.debug.LogBuffer.CAPACITY > 0;
        117};
        118
        119
        120/**
        121 * Removes all buffered log records.
        122 */
        123goog.debug.LogBuffer.prototype.clear = function() {
        124 this.buffer_ = new Array(goog.debug.LogBuffer.CAPACITY);
        125 this.curIndex_ = -1;
        126 this.isFull_ = false;
        127};
        128
        129
        130/**
        131 * Calls the given function for each buffered log record, starting with the
        132 * oldest one.
        133 * @param {function(!goog.debug.LogRecord)} func The function to call.
        134 */
        135goog.debug.LogBuffer.prototype.forEachRecord = function(func) {
        136 var buffer = this.buffer_;
        137 // Corner case: no records.
        138 if (!buffer[0]) {
        139 return;
        140 }
        141 var curIndex = this.curIndex_;
        142 var i = this.isFull_ ? curIndex : -1;
        143 do {
        144 i = (i + 1) % goog.debug.LogBuffer.CAPACITY;
        145 func(/** @type {!goog.debug.LogRecord} */ (buffer[i]));
        146 } while (i != curIndex);
        147};
        148
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/logger.js.src.html b/docs/source/lib/goog/debug/logger.js.src.html new file mode 100644 index 0000000..031db67 --- /dev/null +++ b/docs/source/lib/goog/debug/logger.js.src.html @@ -0,0 +1 @@ +logger.js

        lib/goog/debug/logger.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Definition of the Logger class. Please minimize dependencies
        17 * this file has on other closure classes as any dependency it takes won't be
        18 * able to use the logging infrastructure.
        19 *
        20 * @see ../demos/debug.html
        21 */
        22
        23goog.provide('goog.debug.LogManager');
        24goog.provide('goog.debug.Loggable');
        25goog.provide('goog.debug.Logger');
        26goog.provide('goog.debug.Logger.Level');
        27
        28goog.require('goog.array');
        29goog.require('goog.asserts');
        30goog.require('goog.debug');
        31goog.require('goog.debug.LogBuffer');
        32goog.require('goog.debug.LogRecord');
        33
        34
        35/**
        36 * A message value that can be handled by a Logger.
        37 *
        38 * Functions are treated like callbacks, but are only called when the event's
        39 * log level is enabled. This is useful for logging messages that are expensive
        40 * to construct.
        41 *
        42 * @typedef {string|function(): string}
        43 */
        44goog.debug.Loggable;
        45
        46
        47
        48/**
        49 * The Logger is an object used for logging debug messages. Loggers are
        50 * normally named, using a hierarchical dot-separated namespace. Logger names
        51 * can be arbitrary strings, but they should normally be based on the package
        52 * name or class name of the logged component, such as goog.net.BrowserChannel.
        53 *
        54 * The Logger object is loosely based on the java class
        55 * java.util.logging.Logger. It supports different levels of filtering for
        56 * different loggers.
        57 *
        58 * The logger object should never be instantiated by application code. It
        59 * should always use the goog.debug.Logger.getLogger function.
        60 *
        61 * @constructor
        62 * @param {string} name The name of the Logger.
        63 * @final
        64 */
        65goog.debug.Logger = function(name) {
        66 /**
        67 * Name of the Logger. Generally a dot-separated namespace
        68 * @private {string}
        69 */
        70 this.name_ = name;
        71
        72 /**
        73 * Parent Logger.
        74 * @private {goog.debug.Logger}
        75 */
        76 this.parent_ = null;
        77
        78 /**
        79 * Level that this logger only filters above. Null indicates it should
        80 * inherit from the parent.
        81 * @private {goog.debug.Logger.Level}
        82 */
        83 this.level_ = null;
        84
        85 /**
        86 * Map of children loggers. The keys are the leaf names of the children and
        87 * the values are the child loggers.
        88 * @private {Object}
        89 */
        90 this.children_ = null;
        91
        92 /**
        93 * Handlers that are listening to this logger.
        94 * @private {Array<Function>}
        95 */
        96 this.handlers_ = null;
        97};
        98
        99
        100/** @const */
        101goog.debug.Logger.ROOT_LOGGER_NAME = '';
        102
        103
        104/**
        105 * @define {boolean} Toggles whether loggers other than the root logger can have
        106 * log handlers attached to them and whether they can have their log level
        107 * set. Logging is a bit faster when this is set to false.
        108 */
        109goog.define('goog.debug.Logger.ENABLE_HIERARCHY', true);
        110
        111
        112if (!goog.debug.Logger.ENABLE_HIERARCHY) {
        113 /**
        114 * @type {!Array<Function>}
        115 * @private
        116 */
        117 goog.debug.Logger.rootHandlers_ = [];
        118
        119
        120 /**
        121 * @type {goog.debug.Logger.Level}
        122 * @private
        123 */
        124 goog.debug.Logger.rootLevel_;
        125}
        126
        127
        128
        129/**
        130 * The Level class defines a set of standard logging levels that
        131 * can be used to control logging output. The logging Level objects
        132 * are ordered and are specified by ordered integers. Enabling logging
        133 * at a given level also enables logging at all higher levels.
        134 * <p>
        135 * Clients should normally use the predefined Level constants such
        136 * as Level.SEVERE.
        137 * <p>
        138 * The levels in descending order are:
        139 * <ul>
        140 * <li>SEVERE (highest value)
        141 * <li>WARNING
        142 * <li>INFO
        143 * <li>CONFIG
        144 * <li>FINE
        145 * <li>FINER
        146 * <li>FINEST (lowest value)
        147 * </ul>
        148 * In addition there is a level OFF that can be used to turn
        149 * off logging, and a level ALL that can be used to enable
        150 * logging of all messages.
        151 *
        152 * @param {string} name The name of the level.
        153 * @param {number} value The numeric value of the level.
        154 * @constructor
        155 * @final
        156 */
        157goog.debug.Logger.Level = function(name, value) {
        158 /**
        159 * The name of the level
        160 * @type {string}
        161 */
        162 this.name = name;
        163
        164 /**
        165 * The numeric value of the level
        166 * @type {number}
        167 */
        168 this.value = value;
        169};
        170
        171
        172/**
        173 * @return {string} String representation of the logger level.
        174 * @override
        175 */
        176goog.debug.Logger.Level.prototype.toString = function() {
        177 return this.name;
        178};
        179
        180
        181/**
        182 * OFF is a special level that can be used to turn off logging.
        183 * This level is initialized to <CODE>Infinity</CODE>.
        184 * @type {!goog.debug.Logger.Level}
        185 */
        186goog.debug.Logger.Level.OFF =
        187 new goog.debug.Logger.Level('OFF', Infinity);
        188
        189
        190/**
        191 * SHOUT is a message level for extra debugging loudness.
        192 * This level is initialized to <CODE>1200</CODE>.
        193 * @type {!goog.debug.Logger.Level}
        194 */
        195goog.debug.Logger.Level.SHOUT = new goog.debug.Logger.Level('SHOUT', 1200);
        196
        197
        198/**
        199 * SEVERE is a message level indicating a serious failure.
        200 * This level is initialized to <CODE>1000</CODE>.
        201 * @type {!goog.debug.Logger.Level}
        202 */
        203goog.debug.Logger.Level.SEVERE = new goog.debug.Logger.Level('SEVERE', 1000);
        204
        205
        206/**
        207 * WARNING is a message level indicating a potential problem.
        208 * This level is initialized to <CODE>900</CODE>.
        209 * @type {!goog.debug.Logger.Level}
        210 */
        211goog.debug.Logger.Level.WARNING = new goog.debug.Logger.Level('WARNING', 900);
        212
        213
        214/**
        215 * INFO is a message level for informational messages.
        216 * This level is initialized to <CODE>800</CODE>.
        217 * @type {!goog.debug.Logger.Level}
        218 */
        219goog.debug.Logger.Level.INFO = new goog.debug.Logger.Level('INFO', 800);
        220
        221
        222/**
        223 * CONFIG is a message level for static configuration messages.
        224 * This level is initialized to <CODE>700</CODE>.
        225 * @type {!goog.debug.Logger.Level}
        226 */
        227goog.debug.Logger.Level.CONFIG = new goog.debug.Logger.Level('CONFIG', 700);
        228
        229
        230/**
        231 * FINE is a message level providing tracing information.
        232 * This level is initialized to <CODE>500</CODE>.
        233 * @type {!goog.debug.Logger.Level}
        234 */
        235goog.debug.Logger.Level.FINE = new goog.debug.Logger.Level('FINE', 500);
        236
        237
        238/**
        239 * FINER indicates a fairly detailed tracing message.
        240 * This level is initialized to <CODE>400</CODE>.
        241 * @type {!goog.debug.Logger.Level}
        242 */
        243goog.debug.Logger.Level.FINER = new goog.debug.Logger.Level('FINER', 400);
        244
        245/**
        246 * FINEST indicates a highly detailed tracing message.
        247 * This level is initialized to <CODE>300</CODE>.
        248 * @type {!goog.debug.Logger.Level}
        249 */
        250
        251goog.debug.Logger.Level.FINEST = new goog.debug.Logger.Level('FINEST', 300);
        252
        253
        254/**
        255 * ALL indicates that all messages should be logged.
        256 * This level is initialized to <CODE>0</CODE>.
        257 * @type {!goog.debug.Logger.Level}
        258 */
        259goog.debug.Logger.Level.ALL = new goog.debug.Logger.Level('ALL', 0);
        260
        261
        262/**
        263 * The predefined levels.
        264 * @type {!Array<!goog.debug.Logger.Level>}
        265 * @final
        266 */
        267goog.debug.Logger.Level.PREDEFINED_LEVELS = [
        268 goog.debug.Logger.Level.OFF,
        269 goog.debug.Logger.Level.SHOUT,
        270 goog.debug.Logger.Level.SEVERE,
        271 goog.debug.Logger.Level.WARNING,
        272 goog.debug.Logger.Level.INFO,
        273 goog.debug.Logger.Level.CONFIG,
        274 goog.debug.Logger.Level.FINE,
        275 goog.debug.Logger.Level.FINER,
        276 goog.debug.Logger.Level.FINEST,
        277 goog.debug.Logger.Level.ALL];
        278
        279
        280/**
        281 * A lookup map used to find the level object based on the name or value of
        282 * the level object.
        283 * @type {Object}
        284 * @private
        285 */
        286goog.debug.Logger.Level.predefinedLevelsCache_ = null;
        287
        288
        289/**
        290 * Creates the predefined levels cache and populates it.
        291 * @private
        292 */
        293goog.debug.Logger.Level.createPredefinedLevelsCache_ = function() {
        294 goog.debug.Logger.Level.predefinedLevelsCache_ = {};
        295 for (var i = 0, level; level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
        296 i++) {
        297 goog.debug.Logger.Level.predefinedLevelsCache_[level.value] = level;
        298 goog.debug.Logger.Level.predefinedLevelsCache_[level.name] = level;
        299 }
        300};
        301
        302
        303/**
        304 * Gets the predefined level with the given name.
        305 * @param {string} name The name of the level.
        306 * @return {goog.debug.Logger.Level} The level, or null if none found.
        307 */
        308goog.debug.Logger.Level.getPredefinedLevel = function(name) {
        309 if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
        310 goog.debug.Logger.Level.createPredefinedLevelsCache_();
        311 }
        312
        313 return goog.debug.Logger.Level.predefinedLevelsCache_[name] || null;
        314};
        315
        316
        317/**
        318 * Gets the highest predefined level <= #value.
        319 * @param {number} value Level value.
        320 * @return {goog.debug.Logger.Level} The level, or null if none found.
        321 */
        322goog.debug.Logger.Level.getPredefinedLevelByValue = function(value) {
        323 if (!goog.debug.Logger.Level.predefinedLevelsCache_) {
        324 goog.debug.Logger.Level.createPredefinedLevelsCache_();
        325 }
        326
        327 if (value in goog.debug.Logger.Level.predefinedLevelsCache_) {
        328 return goog.debug.Logger.Level.predefinedLevelsCache_[value];
        329 }
        330
        331 for (var i = 0; i < goog.debug.Logger.Level.PREDEFINED_LEVELS.length; ++i) {
        332 var level = goog.debug.Logger.Level.PREDEFINED_LEVELS[i];
        333 if (level.value <= value) {
        334 return level;
        335 }
        336 }
        337 return null;
        338};
        339
        340
        341/**
        342 * Finds or creates a logger for a named subsystem. If a logger has already been
        343 * created with the given name it is returned. Otherwise a new logger is
        344 * created. If a new logger is created its log level will be configured based
        345 * on the LogManager configuration and it will configured to also send logging
        346 * output to its parent's handlers. It will be registered in the LogManager
        347 * global namespace.
        348 *
        349 * @param {string} name A name for the logger. This should be a dot-separated
        350 * name and should normally be based on the package name or class name of the
        351 * subsystem, such as goog.net.BrowserChannel.
        352 * @return {!goog.debug.Logger} The named logger.
        353 * @deprecated use goog.log instead. http://go/goog-debug-logger-deprecated
        354 */
        355goog.debug.Logger.getLogger = function(name) {
        356 return goog.debug.LogManager.getLogger(name);
        357};
        358
        359
        360/**
        361 * Logs a message to profiling tools, if available.
        362 * {@see https://developers.google.com/web-toolkit/speedtracer/logging-api}
        363 * {@see http://msdn.microsoft.com/en-us/library/dd433074(VS.85).aspx}
        364 * @param {string} msg The message to log.
        365 */
        366goog.debug.Logger.logToProfilers = function(msg) {
        367 // Using goog.global, as loggers might be used in window-less contexts.
        368 if (goog.global['console']) {
        369 if (goog.global['console']['timeStamp']) {
        370 // Logs a message to Firebug, Web Inspector, SpeedTracer, etc.
        371 goog.global['console']['timeStamp'](msg);
        372 } else if (goog.global['console']['markTimeline']) {
        373 // TODO(user): markTimeline is deprecated. Drop this else clause entirely
        374 // after Chrome M14 hits stable.
        375 goog.global['console']['markTimeline'](msg);
        376 }
        377 }
        378
        379 if (goog.global['msWriteProfilerMark']) {
        380 // Logs a message to the Microsoft profiler
        381 goog.global['msWriteProfilerMark'](msg);
        382 }
        383};
        384
        385
        386/**
        387 * Gets the name of this logger.
        388 * @return {string} The name of this logger.
        389 */
        390goog.debug.Logger.prototype.getName = function() {
        391 return this.name_;
        392};
        393
        394
        395/**
        396 * Adds a handler to the logger. This doesn't use the event system because
        397 * we want to be able to add logging to the event system.
        398 * @param {Function} handler Handler function to add.
        399 */
        400goog.debug.Logger.prototype.addHandler = function(handler) {
        401 if (goog.debug.LOGGING_ENABLED) {
        402 if (goog.debug.Logger.ENABLE_HIERARCHY) {
        403 if (!this.handlers_) {
        404 this.handlers_ = [];
        405 }
        406 this.handlers_.push(handler);
        407 } else {
        408 goog.asserts.assert(!this.name_,
        409 'Cannot call addHandler on a non-root logger when ' +
        410 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
        411 goog.debug.Logger.rootHandlers_.push(handler);
        412 }
        413 }
        414};
        415
        416
        417/**
        418 * Removes a handler from the logger. This doesn't use the event system because
        419 * we want to be able to add logging to the event system.
        420 * @param {Function} handler Handler function to remove.
        421 * @return {boolean} Whether the handler was removed.
        422 */
        423goog.debug.Logger.prototype.removeHandler = function(handler) {
        424 if (goog.debug.LOGGING_ENABLED) {
        425 var handlers = goog.debug.Logger.ENABLE_HIERARCHY ? this.handlers_ :
        426 goog.debug.Logger.rootHandlers_;
        427 return !!handlers && goog.array.remove(handlers, handler);
        428 } else {
        429 return false;
        430 }
        431};
        432
        433
        434/**
        435 * Returns the parent of this logger.
        436 * @return {goog.debug.Logger} The parent logger or null if this is the root.
        437 */
        438goog.debug.Logger.prototype.getParent = function() {
        439 return this.parent_;
        440};
        441
        442
        443/**
        444 * Returns the children of this logger as a map of the child name to the logger.
        445 * @return {!Object} The map where the keys are the child leaf names and the
        446 * values are the Logger objects.
        447 */
        448goog.debug.Logger.prototype.getChildren = function() {
        449 if (!this.children_) {
        450 this.children_ = {};
        451 }
        452 return this.children_;
        453};
        454
        455
        456/**
        457 * Set the log level specifying which message levels will be logged by this
        458 * logger. Message levels lower than this value will be discarded.
        459 * The level value Level.OFF can be used to turn off logging. If the new level
        460 * is null, it means that this node should inherit its level from its nearest
        461 * ancestor with a specific (non-null) level value.
        462 *
        463 * @param {goog.debug.Logger.Level} level The new level.
        464 */
        465goog.debug.Logger.prototype.setLevel = function(level) {
        466 if (goog.debug.LOGGING_ENABLED) {
        467 if (goog.debug.Logger.ENABLE_HIERARCHY) {
        468 this.level_ = level;
        469 } else {
        470 goog.asserts.assert(!this.name_,
        471 'Cannot call setLevel() on a non-root logger when ' +
        472 'goog.debug.Logger.ENABLE_HIERARCHY is false.');
        473 goog.debug.Logger.rootLevel_ = level;
        474 }
        475 }
        476};
        477
        478
        479/**
        480 * Gets the log level specifying which message levels will be logged by this
        481 * logger. Message levels lower than this value will be discarded.
        482 * The level value Level.OFF can be used to turn off logging. If the level
        483 * is null, it means that this node should inherit its level from its nearest
        484 * ancestor with a specific (non-null) level value.
        485 *
        486 * @return {goog.debug.Logger.Level} The level.
        487 */
        488goog.debug.Logger.prototype.getLevel = function() {
        489 return goog.debug.LOGGING_ENABLED ?
        490 this.level_ : goog.debug.Logger.Level.OFF;
        491};
        492
        493
        494/**
        495 * Returns the effective level of the logger based on its ancestors' levels.
        496 * @return {goog.debug.Logger.Level} The level.
        497 */
        498goog.debug.Logger.prototype.getEffectiveLevel = function() {
        499 if (!goog.debug.LOGGING_ENABLED) {
        500 return goog.debug.Logger.Level.OFF;
        501 }
        502
        503 if (!goog.debug.Logger.ENABLE_HIERARCHY) {
        504 return goog.debug.Logger.rootLevel_;
        505 }
        506 if (this.level_) {
        507 return this.level_;
        508 }
        509 if (this.parent_) {
        510 return this.parent_.getEffectiveLevel();
        511 }
        512 goog.asserts.fail('Root logger has no level set.');
        513 return null;
        514};
        515
        516
        517/**
        518 * Checks if a message of the given level would actually be logged by this
        519 * logger. This check is based on the Loggers effective level, which may be
        520 * inherited from its parent.
        521 * @param {goog.debug.Logger.Level} level The level to check.
        522 * @return {boolean} Whether the message would be logged.
        523 */
        524goog.debug.Logger.prototype.isLoggable = function(level) {
        525 return goog.debug.LOGGING_ENABLED &&
        526 level.value >= this.getEffectiveLevel().value;
        527};
        528
        529
        530/**
        531 * Logs a message. If the logger is currently enabled for the
        532 * given message level then the given message is forwarded to all the
        533 * registered output Handler objects.
        534 * @param {goog.debug.Logger.Level} level One of the level identifiers.
        535 * @param {goog.debug.Loggable} msg The message to log.
        536 * @param {Error|Object=} opt_exception An exception associated with the
        537 * message.
        538 */
        539goog.debug.Logger.prototype.log = function(level, msg, opt_exception) {
        540 // java caches the effective level, not sure it's necessary here
        541 if (goog.debug.LOGGING_ENABLED && this.isLoggable(level)) {
        542 // Message callbacks can be useful when a log message is expensive to build.
        543 if (goog.isFunction(msg)) {
        544 msg = msg();
        545 }
        546
        547 this.doLogRecord_(this.getLogRecord(level, msg, opt_exception));
        548 }
        549};
        550
        551
        552/**
        553 * Creates a new log record and adds the exception (if present) to it.
        554 * @param {goog.debug.Logger.Level} level One of the level identifiers.
        555 * @param {string} msg The string message.
        556 * @param {Error|Object=} opt_exception An exception associated with the
        557 * message.
        558 * @return {!goog.debug.LogRecord} A log record.
        559 * @suppress {es5Strict}
        560 */
        561goog.debug.Logger.prototype.getLogRecord = function(
        562 level, msg, opt_exception) {
        563 if (goog.debug.LogBuffer.isBufferingEnabled()) {
        564 var logRecord =
        565 goog.debug.LogBuffer.getInstance().addRecord(level, msg, this.name_);
        566 } else {
        567 logRecord = new goog.debug.LogRecord(level, String(msg), this.name_);
        568 }
        569 if (opt_exception) {
        570 logRecord.setException(opt_exception);
        571 }
        572 return logRecord;
        573};
        574
        575
        576/**
        577 * Logs a message at the Logger.Level.SHOUT level.
        578 * If the logger is currently enabled for the given message level then the
        579 * given message is forwarded to all the registered output Handler objects.
        580 * @param {goog.debug.Loggable} msg The message to log.
        581 * @param {Error=} opt_exception An exception associated with the message.
        582 */
        583goog.debug.Logger.prototype.shout = function(msg, opt_exception) {
        584 if (goog.debug.LOGGING_ENABLED) {
        585 this.log(goog.debug.Logger.Level.SHOUT, msg, opt_exception);
        586 }
        587};
        588
        589
        590/**
        591 * Logs a message at the Logger.Level.SEVERE level.
        592 * If the logger is currently enabled for the given message level then the
        593 * given message is forwarded to all the registered output Handler objects.
        594 * @param {goog.debug.Loggable} msg The message to log.
        595 * @param {Error=} opt_exception An exception associated with the message.
        596 */
        597goog.debug.Logger.prototype.severe = function(msg, opt_exception) {
        598 if (goog.debug.LOGGING_ENABLED) {
        599 this.log(goog.debug.Logger.Level.SEVERE, msg, opt_exception);
        600 }
        601};
        602
        603
        604/**
        605 * Logs a message at the Logger.Level.WARNING level.
        606 * If the logger is currently enabled for the given message level then the
        607 * given message is forwarded to all the registered output Handler objects.
        608 * @param {goog.debug.Loggable} msg The message to log.
        609 * @param {Error=} opt_exception An exception associated with the message.
        610 */
        611goog.debug.Logger.prototype.warning = function(msg, opt_exception) {
        612 if (goog.debug.LOGGING_ENABLED) {
        613 this.log(goog.debug.Logger.Level.WARNING, msg, opt_exception);
        614 }
        615};
        616
        617
        618/**
        619 * Logs a message at the Logger.Level.INFO level.
        620 * If the logger is currently enabled for the given message level then the
        621 * given message is forwarded to all the registered output Handler objects.
        622 * @param {goog.debug.Loggable} msg The message to log.
        623 * @param {Error=} opt_exception An exception associated with the message.
        624 */
        625goog.debug.Logger.prototype.info = function(msg, opt_exception) {
        626 if (goog.debug.LOGGING_ENABLED) {
        627 this.log(goog.debug.Logger.Level.INFO, msg, opt_exception);
        628 }
        629};
        630
        631
        632/**
        633 * Logs a message at the Logger.Level.CONFIG level.
        634 * If the logger is currently enabled for the given message level then the
        635 * given message is forwarded to all the registered output Handler objects.
        636 * @param {goog.debug.Loggable} msg The message to log.
        637 * @param {Error=} opt_exception An exception associated with the message.
        638 */
        639goog.debug.Logger.prototype.config = function(msg, opt_exception) {
        640 if (goog.debug.LOGGING_ENABLED) {
        641 this.log(goog.debug.Logger.Level.CONFIG, msg, opt_exception);
        642 }
        643};
        644
        645
        646/**
        647 * Logs a message at the Logger.Level.FINE level.
        648 * If the logger is currently enabled for the given message level then the
        649 * given message is forwarded to all the registered output Handler objects.
        650 * @param {goog.debug.Loggable} msg The message to log.
        651 * @param {Error=} opt_exception An exception associated with the message.
        652 */
        653goog.debug.Logger.prototype.fine = function(msg, opt_exception) {
        654 if (goog.debug.LOGGING_ENABLED) {
        655 this.log(goog.debug.Logger.Level.FINE, msg, opt_exception);
        656 }
        657};
        658
        659
        660/**
        661 * Logs a message at the Logger.Level.FINER level.
        662 * If the logger is currently enabled for the given message level then the
        663 * given message is forwarded to all the registered output Handler objects.
        664 * @param {goog.debug.Loggable} msg The message to log.
        665 * @param {Error=} opt_exception An exception associated with the message.
        666 */
        667goog.debug.Logger.prototype.finer = function(msg, opt_exception) {
        668 if (goog.debug.LOGGING_ENABLED) {
        669 this.log(goog.debug.Logger.Level.FINER, msg, opt_exception);
        670 }
        671};
        672
        673
        674/**
        675 * Logs a message at the Logger.Level.FINEST level.
        676 * If the logger is currently enabled for the given message level then the
        677 * given message is forwarded to all the registered output Handler objects.
        678 * @param {goog.debug.Loggable} msg The message to log.
        679 * @param {Error=} opt_exception An exception associated with the message.
        680 */
        681goog.debug.Logger.prototype.finest = function(msg, opt_exception) {
        682 if (goog.debug.LOGGING_ENABLED) {
        683 this.log(goog.debug.Logger.Level.FINEST, msg, opt_exception);
        684 }
        685};
        686
        687
        688/**
        689 * Logs a LogRecord. If the logger is currently enabled for the
        690 * given message level then the given message is forwarded to all the
        691 * registered output Handler objects.
        692 * @param {goog.debug.LogRecord} logRecord A log record to log.
        693 */
        694goog.debug.Logger.prototype.logRecord = function(logRecord) {
        695 if (goog.debug.LOGGING_ENABLED && this.isLoggable(logRecord.getLevel())) {
        696 this.doLogRecord_(logRecord);
        697 }
        698};
        699
        700
        701/**
        702 * Logs a LogRecord.
        703 * @param {goog.debug.LogRecord} logRecord A log record to log.
        704 * @private
        705 */
        706goog.debug.Logger.prototype.doLogRecord_ = function(logRecord) {
        707 goog.debug.Logger.logToProfilers('log:' + logRecord.getMessage());
        708 if (goog.debug.Logger.ENABLE_HIERARCHY) {
        709 var target = this;
        710 while (target) {
        711 target.callPublish_(logRecord);
        712 target = target.getParent();
        713 }
        714 } else {
        715 for (var i = 0, handler; handler = goog.debug.Logger.rootHandlers_[i++]; ) {
        716 handler(logRecord);
        717 }
        718 }
        719};
        720
        721
        722/**
        723 * Calls the handlers for publish.
        724 * @param {goog.debug.LogRecord} logRecord The log record to publish.
        725 * @private
        726 */
        727goog.debug.Logger.prototype.callPublish_ = function(logRecord) {
        728 if (this.handlers_) {
        729 for (var i = 0, handler; handler = this.handlers_[i]; i++) {
        730 handler(logRecord);
        731 }
        732 }
        733};
        734
        735
        736/**
        737 * Sets the parent of this logger. This is used for setting up the logger tree.
        738 * @param {goog.debug.Logger} parent The parent logger.
        739 * @private
        740 */
        741goog.debug.Logger.prototype.setParent_ = function(parent) {
        742 this.parent_ = parent;
        743};
        744
        745
        746/**
        747 * Adds a child to this logger. This is used for setting up the logger tree.
        748 * @param {string} name The leaf name of the child.
        749 * @param {goog.debug.Logger} logger The child logger.
        750 * @private
        751 */
        752goog.debug.Logger.prototype.addChild_ = function(name, logger) {
        753 this.getChildren()[name] = logger;
        754};
        755
        756
        757/**
        758 * There is a single global LogManager object that is used to maintain a set of
        759 * shared state about Loggers and log services. This is loosely based on the
        760 * java class java.util.logging.LogManager.
        761 * @const
        762 */
        763goog.debug.LogManager = {};
        764
        765
        766/**
        767 * Map of logger names to logger objects.
        768 *
        769 * @type {!Object<string, !goog.debug.Logger>}
        770 * @private
        771 */
        772goog.debug.LogManager.loggers_ = {};
        773
        774
        775/**
        776 * The root logger which is the root of the logger tree.
        777 * @type {goog.debug.Logger}
        778 * @private
        779 */
        780goog.debug.LogManager.rootLogger_ = null;
        781
        782
        783/**
        784 * Initializes the LogManager if not already initialized.
        785 */
        786goog.debug.LogManager.initialize = function() {
        787 if (!goog.debug.LogManager.rootLogger_) {
        788 goog.debug.LogManager.rootLogger_ = new goog.debug.Logger(
        789 goog.debug.Logger.ROOT_LOGGER_NAME);
        790 goog.debug.LogManager.loggers_[goog.debug.Logger.ROOT_LOGGER_NAME] =
        791 goog.debug.LogManager.rootLogger_;
        792 goog.debug.LogManager.rootLogger_.setLevel(goog.debug.Logger.Level.CONFIG);
        793 }
        794};
        795
        796
        797/**
        798 * Returns all the loggers.
        799 * @return {!Object<string, !goog.debug.Logger>} Map of logger names to logger
        800 * objects.
        801 */
        802goog.debug.LogManager.getLoggers = function() {
        803 return goog.debug.LogManager.loggers_;
        804};
        805
        806
        807/**
        808 * Returns the root of the logger tree namespace, the logger with the empty
        809 * string as its name.
        810 *
        811 * @return {!goog.debug.Logger} The root logger.
        812 */
        813goog.debug.LogManager.getRoot = function() {
        814 goog.debug.LogManager.initialize();
        815 return /** @type {!goog.debug.Logger} */ (goog.debug.LogManager.rootLogger_);
        816};
        817
        818
        819/**
        820 * Finds a named logger.
        821 *
        822 * @param {string} name A name for the logger. This should be a dot-separated
        823 * name and should normally be based on the package name or class name of the
        824 * subsystem, such as goog.net.BrowserChannel.
        825 * @return {!goog.debug.Logger} The named logger.
        826 */
        827goog.debug.LogManager.getLogger = function(name) {
        828 goog.debug.LogManager.initialize();
        829 var ret = goog.debug.LogManager.loggers_[name];
        830 return ret || goog.debug.LogManager.createLogger_(name);
        831};
        832
        833
        834/**
        835 * Creates a function that can be passed to goog.debug.catchErrors. The function
        836 * will log all reported errors using the given logger.
        837 * @param {goog.debug.Logger=} opt_logger The logger to log the errors to.
        838 * Defaults to the root logger.
        839 * @return {function(Object)} The created function.
        840 */
        841goog.debug.LogManager.createFunctionForCatchErrors = function(opt_logger) {
        842 return function(info) {
        843 var logger = opt_logger || goog.debug.LogManager.getRoot();
        844 logger.severe('Error: ' + info.message + ' (' + info.fileName +
        845 ' @ Line: ' + info.line + ')');
        846 };
        847};
        848
        849
        850/**
        851 * Creates the named logger. Will also create the parents of the named logger
        852 * if they don't yet exist.
        853 * @param {string} name The name of the logger.
        854 * @return {!goog.debug.Logger} The named logger.
        855 * @private
        856 */
        857goog.debug.LogManager.createLogger_ = function(name) {
        858 // find parent logger
        859 var logger = new goog.debug.Logger(name);
        860 if (goog.debug.Logger.ENABLE_HIERARCHY) {
        861 var lastDotIndex = name.lastIndexOf('.');
        862 var parentName = name.substr(0, lastDotIndex);
        863 var leafName = name.substr(lastDotIndex + 1);
        864 var parentLogger = goog.debug.LogManager.getLogger(parentName);
        865
        866 // tell the parent about the child and the child about the parent
        867 parentLogger.addChild_(leafName, logger);
        868 logger.setParent_(parentLogger);
        869 }
        870
        871 goog.debug.LogManager.loggers_[name] = logger;
        872 return logger;
        873};
        \ No newline at end of file diff --git a/docs/source/lib/goog/debug/logrecord.js.src.html b/docs/source/lib/goog/debug/logrecord.js.src.html new file mode 100644 index 0000000..fc8deb0 --- /dev/null +++ b/docs/source/lib/goog/debug/logrecord.js.src.html @@ -0,0 +1 @@ +logrecord.js

        lib/goog/debug/logrecord.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Definition of the LogRecord class. Please minimize
        17 * dependencies this file has on other closure classes as any dependency it
        18 * takes won't be able to use the logging infrastructure.
        19 *
        20 */
        21
        22goog.provide('goog.debug.LogRecord');
        23
        24
        25
        26/**
        27 * LogRecord objects are used to pass logging requests between
        28 * the logging framework and individual log Handlers.
        29 * @constructor
        30 * @param {goog.debug.Logger.Level} level One of the level identifiers.
        31 * @param {string} msg The string message.
        32 * @param {string} loggerName The name of the source logger.
        33 * @param {number=} opt_time Time this log record was created if other than now.
        34 * If 0, we use #goog.now.
        35 * @param {number=} opt_sequenceNumber Sequence number of this log record. This
        36 * should only be passed in when restoring a log record from persistence.
        37 */
        38goog.debug.LogRecord = function(level, msg, loggerName,
        39 opt_time, opt_sequenceNumber) {
        40 this.reset(level, msg, loggerName, opt_time, opt_sequenceNumber);
        41};
        42
        43
        44/**
        45 * Time the LogRecord was created.
        46 * @type {number}
        47 * @private
        48 */
        49goog.debug.LogRecord.prototype.time_;
        50
        51
        52/**
        53 * Level of the LogRecord
        54 * @type {goog.debug.Logger.Level}
        55 * @private
        56 */
        57goog.debug.LogRecord.prototype.level_;
        58
        59
        60/**
        61 * Message associated with the record
        62 * @type {string}
        63 * @private
        64 */
        65goog.debug.LogRecord.prototype.msg_;
        66
        67
        68/**
        69 * Name of the logger that created the record.
        70 * @type {string}
        71 * @private
        72 */
        73goog.debug.LogRecord.prototype.loggerName_;
        74
        75
        76/**
        77 * Sequence number for the LogRecord. Each record has a unique sequence number
        78 * that is greater than all log records created before it.
        79 * @type {number}
        80 * @private
        81 */
        82goog.debug.LogRecord.prototype.sequenceNumber_ = 0;
        83
        84
        85/**
        86 * Exception associated with the record
        87 * @type {Object}
        88 * @private
        89 */
        90goog.debug.LogRecord.prototype.exception_ = null;
        91
        92
        93/**
        94 * @define {boolean} Whether to enable log sequence numbers.
        95 */
        96goog.define('goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS', true);
        97
        98
        99/**
        100 * A sequence counter for assigning increasing sequence numbers to LogRecord
        101 * objects.
        102 * @type {number}
        103 * @private
        104 */
        105goog.debug.LogRecord.nextSequenceNumber_ = 0;
        106
        107
        108/**
        109 * Sets all fields of the log record.
        110 * @param {goog.debug.Logger.Level} level One of the level identifiers.
        111 * @param {string} msg The string message.
        112 * @param {string} loggerName The name of the source logger.
        113 * @param {number=} opt_time Time this log record was created if other than now.
        114 * If 0, we use #goog.now.
        115 * @param {number=} opt_sequenceNumber Sequence number of this log record. This
        116 * should only be passed in when restoring a log record from persistence.
        117 */
        118goog.debug.LogRecord.prototype.reset = function(level, msg, loggerName,
        119 opt_time, opt_sequenceNumber) {
        120 if (goog.debug.LogRecord.ENABLE_SEQUENCE_NUMBERS) {
        121 this.sequenceNumber_ = typeof opt_sequenceNumber == 'number' ?
        122 opt_sequenceNumber : goog.debug.LogRecord.nextSequenceNumber_++;
        123 }
        124
        125 this.time_ = opt_time || goog.now();
        126 this.level_ = level;
        127 this.msg_ = msg;
        128 this.loggerName_ = loggerName;
        129 delete this.exception_;
        130};
        131
        132
        133/**
        134 * Get the source Logger's name.
        135 *
        136 * @return {string} source logger name (may be null).
        137 */
        138goog.debug.LogRecord.prototype.getLoggerName = function() {
        139 return this.loggerName_;
        140};
        141
        142
        143/**
        144 * Get the exception that is part of the log record.
        145 *
        146 * @return {Object} the exception.
        147 */
        148goog.debug.LogRecord.prototype.getException = function() {
        149 return this.exception_;
        150};
        151
        152
        153/**
        154 * Set the exception that is part of the log record.
        155 *
        156 * @param {Object} exception the exception.
        157 */
        158goog.debug.LogRecord.prototype.setException = function(exception) {
        159 this.exception_ = exception;
        160};
        161
        162
        163/**
        164 * Get the source Logger's name.
        165 *
        166 * @param {string} loggerName source logger name (may be null).
        167 */
        168goog.debug.LogRecord.prototype.setLoggerName = function(loggerName) {
        169 this.loggerName_ = loggerName;
        170};
        171
        172
        173/**
        174 * Get the logging message level, for example Level.SEVERE.
        175 * @return {goog.debug.Logger.Level} the logging message level.
        176 */
        177goog.debug.LogRecord.prototype.getLevel = function() {
        178 return this.level_;
        179};
        180
        181
        182/**
        183 * Set the logging message level, for example Level.SEVERE.
        184 * @param {goog.debug.Logger.Level} level the logging message level.
        185 */
        186goog.debug.LogRecord.prototype.setLevel = function(level) {
        187 this.level_ = level;
        188};
        189
        190
        191/**
        192 * Get the "raw" log message, before localization or formatting.
        193 *
        194 * @return {string} the raw message string.
        195 */
        196goog.debug.LogRecord.prototype.getMessage = function() {
        197 return this.msg_;
        198};
        199
        200
        201/**
        202 * Set the "raw" log message, before localization or formatting.
        203 *
        204 * @param {string} msg the raw message string.
        205 */
        206goog.debug.LogRecord.prototype.setMessage = function(msg) {
        207 this.msg_ = msg;
        208};
        209
        210
        211/**
        212 * Get event time in milliseconds since 1970.
        213 *
        214 * @return {number} event time in millis since 1970.
        215 */
        216goog.debug.LogRecord.prototype.getMillis = function() {
        217 return this.time_;
        218};
        219
        220
        221/**
        222 * Set event time in milliseconds since 1970.
        223 *
        224 * @param {number} time event time in millis since 1970.
        225 */
        226goog.debug.LogRecord.prototype.setMillis = function(time) {
        227 this.time_ = time;
        228};
        229
        230
        231/**
        232 * Get the sequence number.
        233 * <p>
        234 * Sequence numbers are normally assigned in the LogRecord
        235 * constructor, which assigns unique sequence numbers to
        236 * each new LogRecord in increasing order.
        237 * @return {number} the sequence number.
        238 */
        239goog.debug.LogRecord.prototype.getSequenceNumber = function() {
        240 return this.sequenceNumber_;
        241};
        242
        \ No newline at end of file diff --git a/docs/source/lib/goog/deps.js.src.html b/docs/source/lib/goog/deps.js.src.html index c3c1eac..13506a0 100644 --- a/docs/source/lib/goog/deps.js.src.html +++ b/docs/source/lib/goog/deps.js.src.html @@ -1 +1 @@ -deps.js

        lib/goog/deps.js

        1// This file has been auto-generated; do not edit by hand
        2goog.addDependency("../atoms/error.js", ["bot.Error","bot.ErrorCode"], []);
        3goog.addDependency("../atoms/response.js", ["bot.response","bot.response.ResponseObject"], ["bot.Error","bot.ErrorCode"]);
        4goog.addDependency("../atoms/json.js", ["bot.json"], ["bot.userAgent","goog.json","goog.userAgent"]);
        5goog.addDependency("../atoms/userAgent.js", ["bot.userAgent"], ["goog.string","goog.userAgent","goog.userAgent.product","goog.userAgent.product.isVersion"]);
        6goog.addDependency("string/string.js", ["goog.string","goog.string.Unicode"], []);
        7goog.addDependency("useragent/useragent.js", ["goog.userAgent"], ["goog.string"]);
        8goog.addDependency("useragent/product.js", ["goog.userAgent.product"], ["goog.userAgent"]);
        9goog.addDependency("useragent/product_isversion.js", ["goog.userAgent.product.isVersion"], ["goog.userAgent.product"]);
        10goog.addDependency("json/json.js", ["goog.json","goog.json.Serializer"], []);
        11goog.addDependency("../webdriver/capabilities.js", ["webdriver.Browser","webdriver.Capabilities","webdriver.Capability"], []);
        12goog.addDependency("../webdriver/logging.js", ["webdriver.logging","webdriver.logging.Preferences"], ["goog.object"]);
        13goog.addDependency("object/object.js", ["goog.object"], []);
        14goog.addDependency("../webdriver/actionsequence.js", ["webdriver.ActionSequence"], ["goog.array","webdriver.Button","webdriver.Command","webdriver.CommandName","webdriver.Key"]);
        15goog.addDependency("array/array.js", ["goog.array","goog.array.ArrayLike"], ["goog.asserts"]);
        16goog.addDependency("asserts/asserts.js", ["goog.asserts","goog.asserts.AssertionError"], ["goog.debug.Error","goog.string"]);
        17goog.addDependency("debug/error.js", ["goog.debug.Error"], []);
        18goog.addDependency("../webdriver/button.js", ["webdriver.Button"], []);
        19goog.addDependency("../webdriver/command.js", ["webdriver.Command","webdriver.CommandExecutor","webdriver.CommandName"], []);
        20goog.addDependency("../webdriver/key.js", ["webdriver.Key"], []);
        21goog.addDependency("../webdriver/stacktrace.js", ["webdriver.stacktrace","webdriver.stacktrace.Snapshot"], ["goog.array","goog.string","goog.userAgent"]);
        22goog.addDependency("../webdriver/locators.js", ["webdriver.By","webdriver.Locator","webdriver.Locator.Strategy"], ["goog.array","goog.object","goog.string"]);
        23goog.addDependency("../webdriver/promise.js", ["webdriver.promise","webdriver.promise.ControlFlow","webdriver.promise.ControlFlow.Timer","webdriver.promise.Deferred","webdriver.promise.Promise"], ["goog.array","goog.debug.Error","goog.object","webdriver.EventEmitter","webdriver.stacktrace.Snapshot"]);
        24goog.addDependency("../webdriver/events.js", ["webdriver.EventEmitter"], []);
        25goog.addDependency("../webdriver/process.js", ["webdriver.process"], ["goog.Uri","goog.array","goog.json"]);
        26goog.addDependency("uri/uri.js", ["goog.Uri","goog.Uri.QueryData"], ["goog.array","goog.string","goog.structs","goog.structs.Map","goog.uri.utils","goog.uri.utils.ComponentIndex","goog.uri.utils.StandardQueryParam"]);
        27goog.addDependency("structs/structs.js", ["goog.structs"], ["goog.array","goog.object"]);
        28goog.addDependency("structs/map.js", ["goog.structs.Map"], ["goog.iter.Iterator","goog.iter.StopIteration","goog.object"]);
        29goog.addDependency("iter/iter.js", ["goog.iter","goog.iter.Iterator","goog.iter.StopIteration"], ["goog.array","goog.asserts"]);
        30goog.addDependency("uri/utils.js", ["goog.uri.utils","goog.uri.utils.ComponentIndex","goog.uri.utils.QueryArray","goog.uri.utils.QueryValue","goog.uri.utils.StandardQueryParam"], ["goog.asserts","goog.string","goog.userAgent"]);
        31goog.addDependency("../webdriver/webdriver.js", ["webdriver.Alert","webdriver.UnhandledAlertError","webdriver.WebDriver","webdriver.WebElement"], ["bot.Error","bot.ErrorCode","bot.response","goog.array","goog.object","webdriver.ActionSequence","webdriver.Command","webdriver.CommandName","webdriver.Key","webdriver.Locator","webdriver.Session","webdriver.logging","webdriver.promise"]);
        32goog.addDependency("../webdriver/session.js", ["webdriver.Session"], ["webdriver.Capabilities"]);
        33goog.addDependency("../webdriver/builder.js", ["webdriver.Builder"], ["goog.userAgent","webdriver.AbstractBuilder","webdriver.FirefoxDomExecutor","webdriver.WebDriver","webdriver.http.CorsClient","webdriver.http.Executor","webdriver.http.XhrClient","webdriver.process"]);
        34goog.addDependency("../webdriver/abstractbuilder.js", ["webdriver.AbstractBuilder"], ["webdriver.Capabilities","webdriver.process"]);
        35goog.addDependency("../webdriver/firefoxdomexecutor.js", ["webdriver.FirefoxDomExecutor"], ["bot.response","goog.json","goog.userAgent.product","webdriver.Command","webdriver.CommandName"]);
        36goog.addDependency("../webdriver/http/corsclient.js", ["webdriver.http.CorsClient"], ["goog.json","webdriver.http.Response"]);
        37goog.addDependency("../webdriver/http/http.js", ["webdriver.http.Client","webdriver.http.Executor","webdriver.http.Request","webdriver.http.Response"], ["bot.ErrorCode","goog.array","goog.json","webdriver.CommandName","webdriver.promise.Deferred"]);
        38goog.addDependency("../webdriver/http/xhrclient.js", ["webdriver.http.XhrClient"], ["goog.json","goog.net.XmlHttp","webdriver.http.Response"]);
        39goog.addDependency("net/xmlhttp.js", ["goog.net.DefaultXmlHttpFactory","goog.net.XmlHttp","goog.net.XmlHttp.OptionType","goog.net.XmlHttp.ReadyState"], ["goog.net.WrapperXmlHttpFactory","goog.net.XmlHttpFactory"]);
        40goog.addDependency("net/wrapperxmlhttpfactory.js", ["goog.net.WrapperXmlHttpFactory"], ["goog.net.XmlHttpFactory"]);
        41goog.addDependency("net/xmlhttpfactory.js", ["goog.net.XmlHttpFactory"], []);
        42goog.addDependency("../webdriver/testing/asserts.js", ["webdriver.testing.Assertion","webdriver.testing.ContainsMatcher","webdriver.testing.NegatedAssertion","webdriver.testing.assert","webdriver.testing.asserts"], ["goog.array","goog.labs.testing.CloseToMatcher","goog.labs.testing.EndsWithMatcher","goog.labs.testing.EqualToMatcher","goog.labs.testing.EqualsMatcher","goog.labs.testing.GreaterThanEqualToMatcher","goog.labs.testing.GreaterThanMatcher","goog.labs.testing.LessThanEqualToMatcher","goog.labs.testing.LessThanMatcher","goog.labs.testing.InstanceOfMatcher","goog.labs.testing.IsNotMatcher","goog.labs.testing.IsNullMatcher","goog.labs.testing.IsNullOrUndefinedMatcher","goog.labs.testing.IsUndefinedMatcher","goog.labs.testing.Matcher","goog.labs.testing.ObjectEqualsMatcher","goog.labs.testing.RegexMatcher","goog.labs.testing.StartsWithMatcher","goog.labs.testing.assertThat","goog.string","webdriver.promise"]);
        43goog.addDependency("labs/testing/numbermatcher.js", ["goog.labs.testing.CloseToMatcher","goog.labs.testing.EqualToMatcher","goog.labs.testing.GreaterThanEqualToMatcher","goog.labs.testing.GreaterThanMatcher","goog.labs.testing.LessThanEqualToMatcher","goog.labs.testing.LessThanMatcher"], ["goog.asserts","goog.labs.testing.Matcher"]);
        44goog.addDependency("labs/testing/matcher.js", ["goog.labs.testing.Matcher"], []);
        45goog.addDependency("labs/testing/stringmatcher.js", ["goog.labs.testing.ContainsStringMatcher","goog.labs.testing.EndsWithMatcher","goog.labs.testing.EqualToIgnoringCaseMatcher","goog.labs.testing.EqualToIgnoringWhitespaceMatcher","goog.labs.testing.EqualsMatcher","goog.labs.testing.RegexMatcher","goog.labs.testing.StartsWithMatcher","goog.labs.testing.StringContainsInOrderMatcher"], ["goog.asserts","goog.labs.testing.Matcher","goog.string"]);
        46goog.addDependency("labs/testing/objectmatcher.js", ["goog.labs.testing.HasPropertyMatcher","goog.labs.testing.InstanceOfMatcher","goog.labs.testing.IsNullMatcher","goog.labs.testing.IsNullOrUndefinedMatcher","goog.labs.testing.IsUndefinedMatcher","goog.labs.testing.ObjectEqualsMatcher"], ["goog.labs.testing.Matcher","goog.string"]);
        47goog.addDependency("labs/testing/logicmatcher.js", ["goog.labs.testing.AllOfMatcher","goog.labs.testing.AnyOfMatcher","goog.labs.testing.IsNotMatcher"], ["goog.array","goog.labs.testing.Matcher"]);
        48goog.addDependency("labs/testing/assertthat.js", ["goog.labs.testing.MatcherError","goog.labs.testing.assertThat"], ["goog.asserts","goog.debug.Error","goog.labs.testing.Matcher"]);
        \ No newline at end of file +deps.js

        lib/goog/deps.js

        1// This file has been auto-generated; do not edit by hand
        2goog.addDependency("../webdriver/test/events_test.js", [], ["goog.testing.jsunit","webdriver.EventEmitter"], false);
        3goog.addDependency("testing/jsunit.js", ["goog.testing.jsunit"], ["goog.dom.TagName","goog.testing.TestCase","goog.testing.TestRunner"], false);
        4goog.addDependency("dom/tagname.js", ["goog.dom.TagName"], [], false);
        5goog.addDependency("testing/testcase.js", ["goog.testing.TestCase","goog.testing.TestCase.Error","goog.testing.TestCase.Order","goog.testing.TestCase.Result","goog.testing.TestCase.Test"], ["goog.Promise","goog.Thenable","goog.asserts","goog.dom.TagName","goog.object","goog.testing.asserts","goog.testing.stacktrace"], false);
        6goog.addDependency("promise/promise.js", ["goog.Promise"], ["goog.Thenable","goog.asserts","goog.async.FreeList","goog.async.run","goog.async.throwException","goog.debug.Error","goog.promise.Resolver"], false);
        7goog.addDependency("promise/thenable.js", ["goog.Thenable"], [], false);
        8goog.addDependency("asserts/asserts.js", ["goog.asserts","goog.asserts.AssertionError"], ["goog.debug.Error","goog.dom.NodeType","goog.string"], false);
        9goog.addDependency("debug/error.js", ["goog.debug.Error"], [], false);
        10goog.addDependency("dom/nodetype.js", ["goog.dom.NodeType"], [], false);
        11goog.addDependency("string/string.js", ["goog.string","goog.string.Unicode"], [], false);
        12goog.addDependency("async/freelist.js", ["goog.async.FreeList"], [], false);
        13goog.addDependency("async/run.js", ["goog.async.run"], ["goog.async.WorkQueue","goog.async.nextTick","goog.async.throwException","goog.testing.watchers"], false);
        14goog.addDependency("async/workqueue.js", ["goog.async.WorkItem","goog.async.WorkQueue"], ["goog.asserts","goog.async.FreeList"], false);
        15goog.addDependency("async/nexttick.js", ["goog.async.nextTick","goog.async.throwException"], ["goog.debug.entryPointRegistry","goog.dom.TagName","goog.functions","goog.labs.userAgent.browser","goog.labs.userAgent.engine"], false);
        16goog.addDependency("debug/entrypointregistry.js", ["goog.debug.EntryPointMonitor","goog.debug.entryPointRegistry"], ["goog.asserts"], false);
        17goog.addDependency("functions/functions.js", ["goog.functions"], [], false);
        18goog.addDependency("labs/useragent/browser.js", ["goog.labs.userAgent.browser"], ["goog.array","goog.labs.userAgent.util","goog.object","goog.string"], false);
        19goog.addDependency("array/array.js", ["goog.array","goog.array.ArrayLike"], ["goog.asserts"], false);
        20goog.addDependency("labs/useragent/util.js", ["goog.labs.userAgent.util"], ["goog.string"], false);
        21goog.addDependency("object/object.js", ["goog.object"], [], false);
        22goog.addDependency("labs/useragent/engine.js", ["goog.labs.userAgent.engine"], ["goog.array","goog.labs.userAgent.util","goog.string"], false);
        23goog.addDependency("testing/watchers.js", ["goog.testing.watchers"], [], false);
        24goog.addDependency("promise/resolver.js", ["goog.promise.Resolver"], [], false);
        25goog.addDependency("testing/asserts.js", ["goog.testing.JsUnitException","goog.testing.asserts","goog.testing.asserts.ArrayLike"], ["goog.testing.stacktrace"], false);
        26goog.addDependency("testing/stacktrace.js", ["goog.testing.stacktrace","goog.testing.stacktrace.Frame"], [], false);
        27goog.addDependency("testing/testrunner.js", ["goog.testing.TestRunner"], ["goog.dom.TagName","goog.testing.TestCase"], false);
        28goog.addDependency("../webdriver/events.js", ["webdriver.EventEmitter"], [], false);
        29goog.addDependency("../webdriver/test/promise_test.js", [], ["goog.testing.MockClock","goog.testing.jsunit","goog.userAgent","webdriver.promise","webdriver.stacktrace","webdriver.test.testutil"], false);
        30goog.addDependency("testing/mockclock.js", ["goog.testing.MockClock"], ["goog.Disposable","goog.async.run","goog.testing.PropertyReplacer","goog.testing.events","goog.testing.events.Event","goog.testing.watchers"], false);
        31goog.addDependency("disposable/disposable.js", ["goog.Disposable","goog.dispose","goog.disposeAll"], ["goog.disposable.IDisposable"], false);
        32goog.addDependency("disposable/idisposable.js", ["goog.disposable.IDisposable"], [], false);
        33goog.addDependency("testing/propertyreplacer.js", ["goog.testing.PropertyReplacer"], ["goog.testing.ObjectPropertyString","goog.userAgent"], false);
        34goog.addDependency("testing/objectpropertystring.js", ["goog.testing.ObjectPropertyString"], [], false);
        35goog.addDependency("useragent/useragent.js", ["goog.userAgent"], ["goog.labs.userAgent.browser","goog.labs.userAgent.engine","goog.labs.userAgent.platform","goog.labs.userAgent.util","goog.string"], false);
        36goog.addDependency("labs/useragent/platform.js", ["goog.labs.userAgent.platform"], ["goog.labs.userAgent.util","goog.string"], false);
        37goog.addDependency("testing/events/events.js", ["goog.testing.events","goog.testing.events.Event"], ["goog.Disposable","goog.asserts","goog.dom.NodeType","goog.events","goog.events.BrowserEvent","goog.events.BrowserFeature","goog.events.EventTarget","goog.events.EventType","goog.events.KeyCodes","goog.object","goog.style","goog.userAgent"], false);
        38goog.addDependency("events/events.js", ["goog.events","goog.events.CaptureSimulationMode","goog.events.Key","goog.events.ListenableType"], ["goog.asserts","goog.debug.entryPointRegistry","goog.events.BrowserEvent","goog.events.BrowserFeature","goog.events.Listenable","goog.events.ListenerMap"], false);
        39goog.addDependency("events/browserevent.js", ["goog.events.BrowserEvent","goog.events.BrowserEvent.MouseButton"], ["goog.events.BrowserFeature","goog.events.Event","goog.events.EventType","goog.reflect","goog.userAgent"], false);
        40goog.addDependency("events/browserfeature.js", ["goog.events.BrowserFeature"], ["goog.userAgent"], false);
        41goog.addDependency("events/event.js", ["goog.events.Event","goog.events.EventLike"], ["goog.Disposable","goog.events.EventId"], false);
        42goog.addDependency("events/eventid.js", ["goog.events.EventId"], [], false);
        43goog.addDependency("events/eventtype.js", ["goog.events.EventType"], ["goog.userAgent"], false);
        44goog.addDependency("reflect/reflect.js", ["goog.reflect"], [], false);
        45goog.addDependency("events/listenable.js", ["goog.events.Listenable","goog.events.ListenableKey"], ["goog.events.EventId"], false);
        46goog.addDependency("events/listenermap.js", ["goog.events.ListenerMap"], ["goog.array","goog.events.Listener","goog.object"], false);
        47goog.addDependency("events/listener.js", ["goog.events.Listener"], ["goog.events.ListenableKey"], false);
        48goog.addDependency("events/eventtarget.js", ["goog.events.EventTarget"], ["goog.Disposable","goog.asserts","goog.events","goog.events.Event","goog.events.Listenable","goog.events.ListenerMap","goog.object"], false);
        49goog.addDependency("events/keycodes.js", ["goog.events.KeyCodes"], ["goog.userAgent"], false);
        50goog.addDependency("style/style.js", ["goog.style"], ["goog.array","goog.asserts","goog.dom","goog.dom.NodeType","goog.dom.TagName","goog.dom.vendor","goog.math.Box","goog.math.Coordinate","goog.math.Rect","goog.math.Size","goog.object","goog.string","goog.userAgent"], false);
        51goog.addDependency("dom/dom.js", ["goog.dom","goog.dom.Appendable","goog.dom.DomHelper"], ["goog.array","goog.asserts","goog.dom.BrowserFeature","goog.dom.NodeType","goog.dom.TagName","goog.dom.safe","goog.html.SafeHtml","goog.math.Coordinate","goog.math.Size","goog.object","goog.string","goog.string.Unicode","goog.userAgent"], false);
        52goog.addDependency("dom/browserfeature.js", ["goog.dom.BrowserFeature"], ["goog.userAgent"], false);
        53goog.addDependency("dom/safe.js", ["goog.dom.safe","goog.dom.safe.InsertAdjacentHtmlPosition"], ["goog.asserts","goog.html.SafeHtml","goog.html.SafeUrl","goog.html.TrustedResourceUrl","goog.string","goog.string.Const"], false);
        54goog.addDependency("html/safehtml.js", ["goog.html.SafeHtml"], ["goog.array","goog.asserts","goog.dom.TagName","goog.dom.tags","goog.html.SafeStyle","goog.html.SafeStyleSheet","goog.html.SafeUrl","goog.html.TrustedResourceUrl","goog.i18n.bidi.Dir","goog.i18n.bidi.DirectionalString","goog.object","goog.string","goog.string.Const","goog.string.TypedString"], false);
        55goog.addDependency("dom/tags.js", ["goog.dom.tags"], ["goog.object"], false);
        56goog.addDependency("html/safestyle.js", ["goog.html.SafeStyle"], ["goog.array","goog.asserts","goog.string","goog.string.Const","goog.string.TypedString"], false);
        57goog.addDependency("string/const.js", ["goog.string.Const"], ["goog.asserts","goog.string.TypedString"], false);
        58goog.addDependency("string/typedstring.js", ["goog.string.TypedString"], [], false);
        59goog.addDependency("html/safestylesheet.js", ["goog.html.SafeStyleSheet"], ["goog.array","goog.asserts","goog.string","goog.string.Const","goog.string.TypedString"], false);
        60goog.addDependency("html/safeurl.js", ["goog.html.SafeUrl"], ["goog.asserts","goog.fs.url","goog.i18n.bidi.Dir","goog.i18n.bidi.DirectionalString","goog.string.Const","goog.string.TypedString"], false);
        61goog.addDependency("fs/url.js", ["goog.fs.url"], [], false);
        62goog.addDependency("i18n/bidi.js", ["goog.i18n.bidi","goog.i18n.bidi.Dir","goog.i18n.bidi.DirectionalString","goog.i18n.bidi.Format"], [], false);
        63goog.addDependency("html/trustedresourceurl.js", ["goog.html.TrustedResourceUrl"], ["goog.asserts","goog.i18n.bidi.Dir","goog.i18n.bidi.DirectionalString","goog.string.Const","goog.string.TypedString"], false);
        64goog.addDependency("math/coordinate.js", ["goog.math.Coordinate"], ["goog.math"], false);
        65goog.addDependency("math/math.js", ["goog.math"], ["goog.array","goog.asserts"], false);
        66goog.addDependency("math/size.js", ["goog.math.Size"], [], false);
        67goog.addDependency("dom/vendor.js", ["goog.dom.vendor"], ["goog.string","goog.userAgent"], false);
        68goog.addDependency("math/box.js", ["goog.math.Box"], ["goog.math.Coordinate"], false);
        69goog.addDependency("math/rect.js", ["goog.math.Rect"], ["goog.math.Box","goog.math.Coordinate","goog.math.Size"], false);
        70goog.addDependency("../webdriver/promise.js", ["webdriver.promise"], ["goog.array","goog.asserts","goog.async.run","goog.async.throwException","goog.debug.Error","goog.object","webdriver.EventEmitter","webdriver.stacktrace"], true);
        71goog.addDependency("../webdriver/stacktrace.js", ["webdriver.stacktrace","webdriver.stacktrace.Snapshot"], ["goog.array","goog.string","goog.userAgent"], false);
        72goog.addDependency("../webdriver/test/testutil.js", ["webdriver.test.testutil","webdriver.test.testutil.StubError"], ["goog.array","goog.debug.Error","goog.string","goog.testing.recordFunction","webdriver.stacktrace"], false);
        73goog.addDependency("testing/recordfunction.js", ["goog.testing.FunctionCall","goog.testing.recordConstructor","goog.testing.recordFunction"], ["goog.testing.asserts"], false);
        74goog.addDependency("../webdriver/test/logging_test.js", [], ["goog.debug.LogRecord","goog.debug.Logger","goog.testing.jsunit","webdriver.logging"], false);
        75goog.addDependency("debug/logrecord.js", ["goog.debug.LogRecord"], [], false);
        76goog.addDependency("debug/logger.js", ["goog.debug.LogManager","goog.debug.Loggable","goog.debug.Logger","goog.debug.Logger.Level"], ["goog.array","goog.asserts","goog.debug","goog.debug.LogBuffer","goog.debug.LogRecord"], false);
        77goog.addDependency("debug/debug.js", ["goog.debug"], ["goog.array","goog.html.SafeHtml","goog.html.SafeUrl","goog.html.uncheckedconversions","goog.string.Const","goog.structs.Set","goog.userAgent"], false);
        78goog.addDependency("html/uncheckedconversions.js", ["goog.html.uncheckedconversions"], ["goog.asserts","goog.html.SafeHtml","goog.html.SafeScript","goog.html.SafeStyle","goog.html.SafeStyleSheet","goog.html.SafeUrl","goog.html.TrustedResourceUrl","goog.string","goog.string.Const"], false);
        79goog.addDependency("html/safescript.js", ["goog.html.SafeScript"], ["goog.asserts","goog.string.Const","goog.string.TypedString"], false);
        80goog.addDependency("structs/set.js", ["goog.structs.Set"], ["goog.structs","goog.structs.Collection","goog.structs.Map"], false);
        81goog.addDependency("structs/structs.js", ["goog.structs"], ["goog.array","goog.object"], false);
        82goog.addDependency("structs/collection.js", ["goog.structs.Collection"], [], false);
        83goog.addDependency("structs/map.js", ["goog.structs.Map"], ["goog.iter.Iterator","goog.iter.StopIteration","goog.object"], false);
        84goog.addDependency("iter/iter.js", ["goog.iter","goog.iter.Iterable","goog.iter.Iterator","goog.iter.StopIteration"], ["goog.array","goog.asserts","goog.functions","goog.math"], false);
        85goog.addDependency("debug/logbuffer.js", ["goog.debug.LogBuffer"], ["goog.asserts","goog.debug.LogRecord"], false);
        86goog.addDependency("../webdriver/logging.js", ["webdriver.logging"], ["goog.debug.LogManager","goog.debug.LogRecord","goog.debug.Logger","goog.object"], true);
        87goog.addDependency("../webdriver/test/test_bootstrap.js", [], [], false);
        88goog.addDependency("../webdriver/test/promise_error_test.js", [], ["goog.Promise","goog.async.run","goog.testing.jsunit","goog.userAgent","goog.userAgent.product","webdriver.promise","webdriver.test.testutil"], false);
        89goog.addDependency("useragent/product.js", ["goog.userAgent.product"], ["goog.labs.userAgent.browser","goog.labs.userAgent.platform","goog.userAgent"], false);
        90goog.addDependency("../webdriver/test/promise_flow_test.js", [], ["goog.array","goog.string","goog.testing.jsunit","goog.userAgent","webdriver.promise","webdriver.stacktrace.Snapshot","webdriver.stacktrace","webdriver.test.testutil"], false);
        91goog.addDependency("../webdriver/test/locators_test.js", [], ["goog.testing.jsunit","webdriver.By","webdriver.Locator","webdriver.Locator.Strategy","webdriver.test.testutil"], false);
        92goog.addDependency("../webdriver/locators.js", ["webdriver.By","webdriver.Locator","webdriver.Locator.Strategy"], ["goog.array","goog.object","goog.string"], false);
        93goog.addDependency("../webdriver/test/webdriver_test.js", [], ["bot.ErrorCode","goog.Promise","goog.functions","goog.testing.PropertyReplacer","goog.testing.MockControl","goog.testing.jsunit","goog.userAgent","webdriver.Capabilities","webdriver.Command","webdriver.CommandExecutor","webdriver.CommandName","webdriver.FileDetector","webdriver.WebDriver","webdriver.Serializable","webdriver.Session","webdriver.logging","webdriver.promise","webdriver.test.testutil"], false);
        94goog.addDependency("../atoms/error.js", ["bot.Error","bot.ErrorCode"], [], false);
        95goog.addDependency("testing/mockcontrol.js", ["goog.testing.MockControl"], ["goog.array","goog.testing","goog.testing.LooseMock","goog.testing.StrictMock"], false);
        96goog.addDependency("testing/functionmock.js", ["goog.testing","goog.testing.FunctionMock","goog.testing.GlobalFunctionMock","goog.testing.MethodMock"], ["goog.object","goog.testing.LooseMock","goog.testing.Mock","goog.testing.PropertyReplacer","goog.testing.StrictMock"], false);
        97goog.addDependency("testing/loosemock.js", ["goog.testing.LooseExpectationCollection","goog.testing.LooseMock"], ["goog.array","goog.structs.Map","goog.testing.Mock"], false);
        98goog.addDependency("testing/mock.js", ["goog.testing.Mock","goog.testing.MockExpectation"], ["goog.array","goog.object","goog.testing.JsUnitException","goog.testing.MockInterface","goog.testing.mockmatchers"], false);
        99goog.addDependency("testing/mockinterface.js", ["goog.testing.MockInterface"], [], false);
        100goog.addDependency("testing/mockmatchers.js", ["goog.testing.mockmatchers","goog.testing.mockmatchers.ArgumentMatcher","goog.testing.mockmatchers.IgnoreArgument","goog.testing.mockmatchers.InstanceOf","goog.testing.mockmatchers.ObjectEquals","goog.testing.mockmatchers.RegexpMatch","goog.testing.mockmatchers.SaveArgument","goog.testing.mockmatchers.TypeOf"], ["goog.array","goog.dom","goog.testing.asserts"], false);
        101goog.addDependency("testing/strictmock.js", ["goog.testing.StrictMock"], ["goog.array","goog.testing.Mock"], false);
        102goog.addDependency("../webdriver/capabilities.js", ["webdriver.Browser","webdriver.Capabilities","webdriver.Capability","webdriver.ProxyConfig"], ["webdriver.Serializable","webdriver.logging"], false);
        103goog.addDependency("../webdriver/serializable.js", ["webdriver.Serializable"], [], false);
        104goog.addDependency("../webdriver/command.js", ["webdriver.Command","webdriver.CommandExecutor","webdriver.CommandName"], [], false);
        105goog.addDependency("../webdriver/webdriver.js", ["webdriver.Alert","webdriver.AlertPromise","webdriver.FileDetector","webdriver.UnhandledAlertError","webdriver.WebDriver","webdriver.WebElement","webdriver.WebElementPromise"], ["bot.Error","bot.ErrorCode","bot.response","goog.array","goog.object","webdriver.ActionSequence","webdriver.Command","webdriver.CommandName","webdriver.Key","webdriver.Locator","webdriver.Serializable","webdriver.Session","webdriver.TouchSequence","webdriver.logging","webdriver.promise","webdriver.until"], false);
        106goog.addDependency("../atoms/response.js", ["bot.response","bot.response.ResponseObject"], ["bot.Error","bot.ErrorCode"], false);
        107goog.addDependency("../webdriver/actionsequence.js", ["webdriver.ActionSequence"], ["goog.array","webdriver.Button","webdriver.Command","webdriver.CommandName","webdriver.Key"], false);
        108goog.addDependency("../webdriver/button.js", ["webdriver.Button"], [], false);
        109goog.addDependency("../webdriver/key.js", ["webdriver.Key"], [], false);
        110goog.addDependency("../webdriver/session.js", ["webdriver.Session"], ["webdriver.Capabilities"], false);
        111goog.addDependency("../webdriver/touchsequence.js", ["webdriver.TouchSequence"], ["goog.array","webdriver.Command","webdriver.CommandName"], false);
        112goog.addDependency("../webdriver/until.js", ["webdriver.until"], ["bot.ErrorCode","goog.array","goog.string","webdriver.Locator"], false);
        113goog.addDependency("../webdriver/test/testutil_test.js", [], ["goog.testing.jsunit","webdriver.test.testutil"], false);
        114goog.addDependency("../webdriver/test/builder_test.js", [], ["goog.testing.jsunit","webdriver.Builder"], false);
        115goog.addDependency("../webdriver/builder.js", ["webdriver.Builder"], ["goog.Uri","goog.userAgent","webdriver.Capabilities","webdriver.FirefoxDomExecutor","webdriver.WebDriver","webdriver.http.CorsClient","webdriver.http.Executor","webdriver.http.XhrClient"], false);
        116goog.addDependency("uri/uri.js", ["goog.Uri","goog.Uri.QueryData"], ["goog.array","goog.string","goog.structs","goog.structs.Map","goog.uri.utils","goog.uri.utils.ComponentIndex","goog.uri.utils.StandardQueryParam"], false);
        117goog.addDependency("uri/utils.js", ["goog.uri.utils","goog.uri.utils.ComponentIndex","goog.uri.utils.QueryArray","goog.uri.utils.QueryValue","goog.uri.utils.StandardQueryParam"], ["goog.asserts","goog.string","goog.userAgent"], false);
        118goog.addDependency("../webdriver/firefoxdomexecutor.js", ["webdriver.FirefoxDomExecutor"], ["bot.response","goog.userAgent.product","webdriver.Command","webdriver.CommandExecutor","webdriver.CommandName"], false);
        119goog.addDependency("../webdriver/http/corsclient.js", ["webdriver.http.CorsClient"], ["webdriver.http.Client","webdriver.http.Response"], false);
        120goog.addDependency("../webdriver/http/http.js", ["webdriver.http.Client","webdriver.http.Executor","webdriver.http.Request","webdriver.http.Response"], ["bot.ErrorCode","goog.array","webdriver.CommandExecutor","webdriver.CommandName","webdriver.logging","webdriver.promise"], false);
        121goog.addDependency("../webdriver/http/xhrclient.js", ["webdriver.http.XhrClient"], ["goog.net.XmlHttp","webdriver.http.Client","webdriver.http.Response"], false);
        122goog.addDependency("net/xmlhttp.js", ["goog.net.DefaultXmlHttpFactory","goog.net.XmlHttp","goog.net.XmlHttp.OptionType","goog.net.XmlHttp.ReadyState","goog.net.XmlHttpDefines"], ["goog.asserts","goog.net.WrapperXmlHttpFactory","goog.net.XmlHttpFactory"], false);
        123goog.addDependency("net/wrapperxmlhttpfactory.js", ["goog.net.WrapperXmlHttpFactory"], ["goog.net.XhrLike","goog.net.XmlHttpFactory"], false);
        124goog.addDependency("net/xhrlike.js", ["goog.net.XhrLike"], [], false);
        125goog.addDependency("net/xmlhttpfactory.js", ["goog.net.XmlHttpFactory"], ["goog.net.XhrLike"], false);
        126goog.addDependency("../webdriver/test/capabilities_test.js", [], ["goog.testing.jsunit","webdriver.Capabilities"], false);
        127goog.addDependency("../webdriver/test/stacktrace_test.js", [], ["bot.Error","bot.ErrorCode","goog.string","goog.testing.JsUnitException","goog.testing.PropertyReplacer","goog.testing.StrictMock","goog.testing.jsunit","goog.testing.stacktrace","webdriver.stacktrace","webdriver.test.testutil"], false);
        128goog.addDependency("../webdriver/test/http/http_test.js", [], ["bot.ErrorCode","goog.Uri","goog.testing.MockControl","goog.testing.jsunit","goog.userAgent","webdriver.Command","webdriver.http.Client","webdriver.http.Executor","webdriver.promise","webdriver.test.testutil"], false);
        129goog.addDependency("../webdriver/test/http/corsclient_test.js", [], ["goog.testing.MockControl","goog.testing.PropertyReplacer","goog.testing.jsunit","goog.userAgent","webdriver.http.CorsClient","webdriver.http.Request","webdriver.test.testutil"], false);
        130goog.addDependency("../webdriver/test/http/xhrclient_test.js", [], ["goog.testing.MockControl","goog.testing.PropertyReplacer","goog.testing.jsunit","goog.userAgent","webdriver.http.Request","webdriver.http.XhrClient","webdriver.promise","webdriver.test.testutil"], false);
        131goog.addDependency("../webdriver/test/testing/client_test.js", [], ["goog.testing.MockControl","goog.testing.PropertyReplacer","goog.testing.jsunit","goog.userAgent","webdriver.testing.Client"], false);
        132goog.addDependency("../webdriver/testing/client.js", ["webdriver.testing.Client"], ["goog.json","goog.net.XmlHttp"], false);
        133goog.addDependency("json/json.js", ["goog.json","goog.json.Replacer","goog.json.Reviver","goog.json.Serializer"], [], false);
        134goog.addDependency("../webdriver/test/testing/testcase_test.js", [], ["goog.Promise","goog.testing.MockControl","goog.testing.PropertyReplacer","goog.testing.mockmatchers","goog.testing.jsunit","goog.testing.recordFunction","goog.userAgent","webdriver.test.testutil","webdriver.testing.TestCase"], false);
        135goog.addDependency("../webdriver/testing/testcase.js", ["webdriver.testing.TestCase"], ["goog.testing.TestCase","webdriver.promise","webdriver.testing.asserts"], false);
        136goog.addDependency("../webdriver/testing/asserts.js", ["webdriver.testing.Assertion","webdriver.testing.ContainsMatcher","webdriver.testing.NegatedAssertion","webdriver.testing.assert","webdriver.testing.asserts"], ["goog.array","goog.labs.testing.CloseToMatcher","goog.labs.testing.EndsWithMatcher","goog.labs.testing.EqualToMatcher","goog.labs.testing.EqualsMatcher","goog.labs.testing.GreaterThanEqualToMatcher","goog.labs.testing.GreaterThanMatcher","goog.labs.testing.LessThanEqualToMatcher","goog.labs.testing.LessThanMatcher","goog.labs.testing.InstanceOfMatcher","goog.labs.testing.IsNotMatcher","goog.labs.testing.IsNullMatcher","goog.labs.testing.IsNullOrUndefinedMatcher","goog.labs.testing.IsUndefinedMatcher","goog.labs.testing.Matcher","goog.labs.testing.ObjectEqualsMatcher","goog.labs.testing.RegexMatcher","goog.labs.testing.StartsWithMatcher","goog.labs.testing.assertThat","goog.string","webdriver.promise"], false);
        137goog.addDependency("labs/testing/numbermatcher.js", ["goog.labs.testing.CloseToMatcher","goog.labs.testing.EqualToMatcher","goog.labs.testing.GreaterThanEqualToMatcher","goog.labs.testing.GreaterThanMatcher","goog.labs.testing.LessThanEqualToMatcher","goog.labs.testing.LessThanMatcher"], ["goog.asserts","goog.labs.testing.Matcher"], false);
        138goog.addDependency("labs/testing/matcher.js", ["goog.labs.testing.Matcher"], [], false);
        139goog.addDependency("labs/testing/stringmatcher.js", ["goog.labs.testing.ContainsStringMatcher","goog.labs.testing.EndsWithMatcher","goog.labs.testing.EqualToIgnoringWhitespaceMatcher","goog.labs.testing.EqualsMatcher","goog.labs.testing.RegexMatcher","goog.labs.testing.StartsWithMatcher","goog.labs.testing.StringContainsInOrderMatcher"], ["goog.asserts","goog.labs.testing.Matcher","goog.string"], false);
        140goog.addDependency("labs/testing/objectmatcher.js", ["goog.labs.testing.HasPropertyMatcher","goog.labs.testing.InstanceOfMatcher","goog.labs.testing.IsNullMatcher","goog.labs.testing.IsNullOrUndefinedMatcher","goog.labs.testing.IsUndefinedMatcher","goog.labs.testing.ObjectEqualsMatcher"], ["goog.labs.testing.Matcher"], false);
        141goog.addDependency("labs/testing/logicmatcher.js", ["goog.labs.testing.AllOfMatcher","goog.labs.testing.AnyOfMatcher","goog.labs.testing.IsNotMatcher"], ["goog.array","goog.labs.testing.Matcher"], false);
        142goog.addDependency("labs/testing/assertthat.js", ["goog.labs.testing.MatcherError","goog.labs.testing.assertThat"], ["goog.debug.Error"], false);
        143goog.addDependency("../webdriver/test/testing/asserts_test.js", [], ["goog.testing.jsunit","goog.userAgent","webdriver.test.testutil","webdriver.testing.assert","webdriver.testing.asserts"], false);
        144goog.addDependency("../atoms/json.js", ["bot.json"], ["bot.userAgent","goog.json","goog.userAgent"], false);
        145goog.addDependency("../atoms/userAgent.js", ["bot.userAgent"], ["goog.string","goog.userAgent","goog.userAgent.product","goog.userAgent.product.isVersion"], false);
        146goog.addDependency("useragent/product_isversion.js", ["goog.userAgent.product.isVersion"], ["goog.labs.userAgent.platform","goog.string","goog.userAgent","goog.userAgent.product"], false);
        147goog.addDependency("../webdriver/testing/window.js", ["webdriver.testing.Window"], ["goog.string","webdriver.promise"], false);
        148goog.addDependency("../webdriver/testing/jsunit.js", ["webdriver.testing.jsunit","webdriver.testing.jsunit.TestRunner"], ["goog.testing.TestRunner","webdriver.testing.Client","webdriver.testing.TestCase"], false);
        149goog.addDependency("../webdriver/test/webdriver_generator_test.js", ["webdriver.test.WebDriver.generator.test"], ["goog.testing.jsunit","webdriver.Session","webdriver.WebDriver"], false);
        150goog.addDependency("../webdriver/test/until_test.js", ["webdriver.test.until_test"], ["bot.Error","bot.ErrorCode","bot.response","goog.array","goog.string","goog.testing.jsunit","goog.userAgent","webdriver.By","webdriver.CommandName","webdriver.WebDriver","webdriver.WebElement","webdriver.WebElementPromise","webdriver.until"], false);
        151goog.addDependency("../webdriver/test/promise_generator_test.js", ["webdriver.test.promise.generator.test"], ["goog.testing.jsunit","webdriver.promise"], false);
        \ No newline at end of file diff --git a/docs/source/lib/goog/disposable/disposable.js.src.html b/docs/source/lib/goog/disposable/disposable.js.src.html new file mode 100644 index 0000000..60129c2 --- /dev/null +++ b/docs/source/lib/goog/disposable/disposable.js.src.html @@ -0,0 +1 @@ +disposable.js

        lib/goog/disposable/disposable.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Implements the disposable interface. The dispose method is used
        17 * to clean up references and resources.
        18 * @author arv@google.com (Erik Arvidsson)
        19 */
        20
        21
        22goog.provide('goog.Disposable');
        23/** @suppress {extraProvide} */
        24goog.provide('goog.dispose');
        25/** @suppress {extraProvide} */
        26goog.provide('goog.disposeAll');
        27
        28goog.require('goog.disposable.IDisposable');
        29
        30
        31
        32/**
        33 * Class that provides the basic implementation for disposable objects. If your
        34 * class holds one or more references to COM objects, DOM nodes, or other
        35 * disposable objects, it should extend this class or implement the disposable
        36 * interface (defined in goog.disposable.IDisposable).
        37 * @constructor
        38 * @implements {goog.disposable.IDisposable}
        39 */
        40goog.Disposable = function() {
        41 if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
        42 if (goog.Disposable.INCLUDE_STACK_ON_CREATION) {
        43 this.creationStack = new Error().stack;
        44 }
        45 goog.Disposable.instances_[goog.getUid(this)] = this;
        46 }
        47 // Support sealing
        48 this.disposed_ = this.disposed_;
        49 this.onDisposeCallbacks_ = this.onDisposeCallbacks_;
        50};
        51
        52
        53/**
        54 * @enum {number} Different monitoring modes for Disposable.
        55 */
        56goog.Disposable.MonitoringMode = {
        57 /**
        58 * No monitoring.
        59 */
        60 OFF: 0,
        61 /**
        62 * Creating and disposing the goog.Disposable instances is monitored. All
        63 * disposable objects need to call the {@code goog.Disposable} base
        64 * constructor. The PERMANENT mode must be switched on before creating any
        65 * goog.Disposable instances.
        66 */
        67 PERMANENT: 1,
        68 /**
        69 * INTERACTIVE mode can be switched on and off on the fly without producing
        70 * errors. It also doesn't warn if the disposable objects don't call the
        71 * {@code goog.Disposable} base constructor.
        72 */
        73 INTERACTIVE: 2
        74};
        75
        76
        77/**
        78 * @define {number} The monitoring mode of the goog.Disposable
        79 * instances. Default is OFF. Switching on the monitoring is only
        80 * recommended for debugging because it has a significant impact on
        81 * performance and memory usage. If switched off, the monitoring code
        82 * compiles down to 0 bytes.
        83 */
        84goog.define('goog.Disposable.MONITORING_MODE', 0);
        85
        86
        87/**
        88 * @define {boolean} Whether to attach creation stack to each created disposable
        89 * instance; This is only relevant for when MonitoringMode != OFF.
        90 */
        91goog.define('goog.Disposable.INCLUDE_STACK_ON_CREATION', true);
        92
        93
        94/**
        95 * Maps the unique ID of every undisposed {@code goog.Disposable} object to
        96 * the object itself.
        97 * @type {!Object<number, !goog.Disposable>}
        98 * @private
        99 */
        100goog.Disposable.instances_ = {};
        101
        102
        103/**
        104 * @return {!Array<!goog.Disposable>} All {@code goog.Disposable} objects that
        105 * haven't been disposed of.
        106 */
        107goog.Disposable.getUndisposedObjects = function() {
        108 var ret = [];
        109 for (var id in goog.Disposable.instances_) {
        110 if (goog.Disposable.instances_.hasOwnProperty(id)) {
        111 ret.push(goog.Disposable.instances_[Number(id)]);
        112 }
        113 }
        114 return ret;
        115};
        116
        117
        118/**
        119 * Clears the registry of undisposed objects but doesn't dispose of them.
        120 */
        121goog.Disposable.clearUndisposedObjects = function() {
        122 goog.Disposable.instances_ = {};
        123};
        124
        125
        126/**
        127 * Whether the object has been disposed of.
        128 * @type {boolean}
        129 * @private
        130 */
        131goog.Disposable.prototype.disposed_ = false;
        132
        133
        134/**
        135 * Callbacks to invoke when this object is disposed.
        136 * @type {Array<!Function>}
        137 * @private
        138 */
        139goog.Disposable.prototype.onDisposeCallbacks_;
        140
        141
        142/**
        143 * If monitoring the goog.Disposable instances is enabled, stores the creation
        144 * stack trace of the Disposable instance.
        145 * @const {string}
        146 */
        147goog.Disposable.prototype.creationStack;
        148
        149
        150/**
        151 * @return {boolean} Whether the object has been disposed of.
        152 * @override
        153 */
        154goog.Disposable.prototype.isDisposed = function() {
        155 return this.disposed_;
        156};
        157
        158
        159/**
        160 * @return {boolean} Whether the object has been disposed of.
        161 * @deprecated Use {@link #isDisposed} instead.
        162 */
        163goog.Disposable.prototype.getDisposed = goog.Disposable.prototype.isDisposed;
        164
        165
        166/**
        167 * Disposes of the object. If the object hasn't already been disposed of, calls
        168 * {@link #disposeInternal}. Classes that extend {@code goog.Disposable} should
        169 * override {@link #disposeInternal} in order to delete references to COM
        170 * objects, DOM nodes, and other disposable objects. Reentrant.
        171 *
        172 * @return {void} Nothing.
        173 * @override
        174 */
        175goog.Disposable.prototype.dispose = function() {
        176 if (!this.disposed_) {
        177 // Set disposed_ to true first, in case during the chain of disposal this
        178 // gets disposed recursively.
        179 this.disposed_ = true;
        180 this.disposeInternal();
        181 if (goog.Disposable.MONITORING_MODE != goog.Disposable.MonitoringMode.OFF) {
        182 var uid = goog.getUid(this);
        183 if (goog.Disposable.MONITORING_MODE ==
        184 goog.Disposable.MonitoringMode.PERMANENT &&
        185 !goog.Disposable.instances_.hasOwnProperty(uid)) {
        186 throw Error(this + ' did not call the goog.Disposable base ' +
        187 'constructor or was disposed of after a clearUndisposedObjects ' +
        188 'call');
        189 }
        190 delete goog.Disposable.instances_[uid];
        191 }
        192 }
        193};
        194
        195
        196/**
        197 * Associates a disposable object with this object so that they will be disposed
        198 * together.
        199 * @param {goog.disposable.IDisposable} disposable that will be disposed when
        200 * this object is disposed.
        201 */
        202goog.Disposable.prototype.registerDisposable = function(disposable) {
        203 this.addOnDisposeCallback(goog.partial(goog.dispose, disposable));
        204};
        205
        206
        207/**
        208 * Invokes a callback function when this object is disposed. Callbacks are
        209 * invoked in the order in which they were added. If a callback is added to
        210 * an already disposed Disposable, it will be called immediately.
        211 * @param {function(this:T):?} callback The callback function.
        212 * @param {T=} opt_scope An optional scope to call the callback in.
        213 * @template T
        214 */
        215goog.Disposable.prototype.addOnDisposeCallback = function(callback, opt_scope) {
        216 if (this.disposed_) {
        217 callback.call(opt_scope);
        218 return;
        219 }
        220 if (!this.onDisposeCallbacks_) {
        221 this.onDisposeCallbacks_ = [];
        222 }
        223
        224 this.onDisposeCallbacks_.push(
        225 goog.isDef(opt_scope) ? goog.bind(callback, opt_scope) : callback);
        226};
        227
        228
        229/**
        230 * Deletes or nulls out any references to COM objects, DOM nodes, or other
        231 * disposable objects. Classes that extend {@code goog.Disposable} should
        232 * override this method.
        233 * Not reentrant. To avoid calling it twice, it must only be called from the
        234 * subclass' {@code disposeInternal} method. Everywhere else the public
        235 * {@code dispose} method must be used.
        236 * For example:
        237 * <pre>
        238 * mypackage.MyClass = function() {
        239 * mypackage.MyClass.base(this, 'constructor');
        240 * // Constructor logic specific to MyClass.
        241 * ...
        242 * };
        243 * goog.inherits(mypackage.MyClass, goog.Disposable);
        244 *
        245 * mypackage.MyClass.prototype.disposeInternal = function() {
        246 * // Dispose logic specific to MyClass.
        247 * ...
        248 * // Call superclass's disposeInternal at the end of the subclass's, like
        249 * // in C++, to avoid hard-to-catch issues.
        250 * mypackage.MyClass.base(this, 'disposeInternal');
        251 * };
        252 * </pre>
        253 * @protected
        254 */
        255goog.Disposable.prototype.disposeInternal = function() {
        256 if (this.onDisposeCallbacks_) {
        257 while (this.onDisposeCallbacks_.length) {
        258 this.onDisposeCallbacks_.shift()();
        259 }
        260 }
        261};
        262
        263
        264/**
        265 * Returns True if we can verify the object is disposed.
        266 * Calls {@code isDisposed} on the argument if it supports it. If obj
        267 * is not an object with an isDisposed() method, return false.
        268 * @param {*} obj The object to investigate.
        269 * @return {boolean} True if we can verify the object is disposed.
        270 */
        271goog.Disposable.isDisposed = function(obj) {
        272 if (obj && typeof obj.isDisposed == 'function') {
        273 return obj.isDisposed();
        274 }
        275 return false;
        276};
        277
        278
        279/**
        280 * Calls {@code dispose} on the argument if it supports it. If obj is not an
        281 * object with a dispose() method, this is a no-op.
        282 * @param {*} obj The object to dispose of.
        283 */
        284goog.dispose = function(obj) {
        285 if (obj && typeof obj.dispose == 'function') {
        286 obj.dispose();
        287 }
        288};
        289
        290
        291/**
        292 * Calls {@code dispose} on each member of the list that supports it. (If the
        293 * member is an ArrayLike, then {@code goog.disposeAll()} will be called
        294 * recursively on each of its members.) If the member is not an object with a
        295 * {@code dispose()} method, then it is ignored.
        296 * @param {...*} var_args The list.
        297 */
        298goog.disposeAll = function(var_args) {
        299 for (var i = 0, len = arguments.length; i < len; ++i) {
        300 var disposable = arguments[i];
        301 if (goog.isArrayLike(disposable)) {
        302 goog.disposeAll.apply(null, disposable);
        303 } else {
        304 goog.dispose(disposable);
        305 }
        306 }
        307};
        \ No newline at end of file diff --git a/docs/source/lib/goog/disposable/idisposable.js.src.html b/docs/source/lib/goog/disposable/idisposable.js.src.html new file mode 100644 index 0000000..a3d4819 --- /dev/null +++ b/docs/source/lib/goog/disposable/idisposable.js.src.html @@ -0,0 +1 @@ +idisposable.js

        lib/goog/disposable/idisposable.js

        1// Copyright 2011 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Definition of the disposable interface. A disposable object
        17 * has a dispose method to to clean up references and resources.
        18 * @author nnaze@google.com (Nathan Naze)
        19 */
        20
        21
        22goog.provide('goog.disposable.IDisposable');
        23
        24
        25
        26/**
        27 * Interface for a disposable object. If a instance requires cleanup
        28 * (references COM objects, DOM notes, or other disposable objects), it should
        29 * implement this interface (it may subclass goog.Disposable).
        30 * @interface
        31 */
        32goog.disposable.IDisposable = function() {};
        33
        34
        35/**
        36 * Disposes of the object and its resources.
        37 * @return {void} Nothing.
        38 */
        39goog.disposable.IDisposable.prototype.dispose = goog.abstractMethod;
        40
        41
        42/**
        43 * @return {boolean} Whether the object has been disposed of.
        44 */
        45goog.disposable.IDisposable.prototype.isDisposed = goog.abstractMethod;
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/browserfeature.js.src.html b/docs/source/lib/goog/dom/browserfeature.js.src.html new file mode 100644 index 0000000..0c53d6b --- /dev/null +++ b/docs/source/lib/goog/dom/browserfeature.js.src.html @@ -0,0 +1 @@ +browserfeature.js

        lib/goog/dom/browserfeature.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Browser capability checks for the dom package.
        17 *
        18 */
        19
        20
        21goog.provide('goog.dom.BrowserFeature');
        22
        23goog.require('goog.userAgent');
        24
        25
        26/**
        27 * Enum of browser capabilities.
        28 * @enum {boolean}
        29 */
        30goog.dom.BrowserFeature = {
        31 /**
        32 * Whether attributes 'name' and 'type' can be added to an element after it's
        33 * created. False in Internet Explorer prior to version 9.
        34 */
        35 CAN_ADD_NAME_OR_TYPE_ATTRIBUTES: !goog.userAgent.IE ||
        36 goog.userAgent.isDocumentModeOrHigher(9),
        37
        38 /**
        39 * Whether we can use element.children to access an element's Element
        40 * children. Available since Gecko 1.9.1, IE 9. (IE<9 also includes comment
        41 * nodes in the collection.)
        42 */
        43 CAN_USE_CHILDREN_ATTRIBUTE: !goog.userAgent.GECKO && !goog.userAgent.IE ||
        44 goog.userAgent.IE && goog.userAgent.isDocumentModeOrHigher(9) ||
        45 goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9.1'),
        46
        47 /**
        48 * Opera, Safari 3, and Internet Explorer 9 all support innerText but they
        49 * include text nodes in script and style tags. Not document-mode-dependent.
        50 */
        51 CAN_USE_INNER_TEXT: (
        52 goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')),
        53
        54 /**
        55 * MSIE, Opera, and Safari>=4 support element.parentElement to access an
        56 * element's parent if it is an Element.
        57 */
        58 CAN_USE_PARENT_ELEMENT_PROPERTY: goog.userAgent.IE || goog.userAgent.OPERA ||
        59 goog.userAgent.WEBKIT,
        60
        61 /**
        62 * Whether NoScope elements need a scoped element written before them in
        63 * innerHTML.
        64 * MSDN: http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx#1
        65 */
        66 INNER_HTML_NEEDS_SCOPED_ELEMENT: goog.userAgent.IE,
        67
        68 /**
        69 * Whether we use legacy IE range API.
        70 */
        71 LEGACY_IE_RANGES: goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)
        72};
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/dom.js.src.html b/docs/source/lib/goog/dom/dom.js.src.html new file mode 100644 index 0000000..30765ac --- /dev/null +++ b/docs/source/lib/goog/dom/dom.js.src.html @@ -0,0 +1 @@ +dom.js

        lib/goog/dom/dom.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for manipulating the browser's Document Object Model
        17 * Inspiration taken *heavily* from mochikit (http://mochikit.com/).
        18 *
        19 * You can use {@link goog.dom.DomHelper} to create new dom helpers that refer
        20 * to a different document object. This is useful if you are working with
        21 * frames or multiple windows.
        22 *
        23 * @author arv@google.com (Erik Arvidsson)
        24 */
        25
        26
        27// TODO(arv): Rename/refactor getTextContent and getRawTextContent. The problem
        28// is that getTextContent should mimic the DOM3 textContent. We should add a
        29// getInnerText (or getText) which tries to return the visible text, innerText.
        30
        31
        32goog.provide('goog.dom');
        33goog.provide('goog.dom.Appendable');
        34goog.provide('goog.dom.DomHelper');
        35
        36goog.require('goog.array');
        37goog.require('goog.asserts');
        38goog.require('goog.dom.BrowserFeature');
        39goog.require('goog.dom.NodeType');
        40goog.require('goog.dom.TagName');
        41goog.require('goog.dom.safe');
        42goog.require('goog.html.SafeHtml');
        43goog.require('goog.math.Coordinate');
        44goog.require('goog.math.Size');
        45goog.require('goog.object');
        46goog.require('goog.string');
        47goog.require('goog.string.Unicode');
        48goog.require('goog.userAgent');
        49
        50
        51/**
        52 * @define {boolean} Whether we know at compile time that the browser is in
        53 * quirks mode.
        54 */
        55goog.define('goog.dom.ASSUME_QUIRKS_MODE', false);
        56
        57
        58/**
        59 * @define {boolean} Whether we know at compile time that the browser is in
        60 * standards compliance mode.
        61 */
        62goog.define('goog.dom.ASSUME_STANDARDS_MODE', false);
        63
        64
        65/**
        66 * Whether we know the compatibility mode at compile time.
        67 * @type {boolean}
        68 * @private
        69 */
        70goog.dom.COMPAT_MODE_KNOWN_ =
        71 goog.dom.ASSUME_QUIRKS_MODE || goog.dom.ASSUME_STANDARDS_MODE;
        72
        73
        74/**
        75 * Gets the DomHelper object for the document where the element resides.
        76 * @param {(Node|Window)=} opt_element If present, gets the DomHelper for this
        77 * element.
        78 * @return {!goog.dom.DomHelper} The DomHelper.
        79 */
        80goog.dom.getDomHelper = function(opt_element) {
        81 return opt_element ?
        82 new goog.dom.DomHelper(goog.dom.getOwnerDocument(opt_element)) :
        83 (goog.dom.defaultDomHelper_ ||
        84 (goog.dom.defaultDomHelper_ = new goog.dom.DomHelper()));
        85};
        86
        87
        88/**
        89 * Cached default DOM helper.
        90 * @type {goog.dom.DomHelper}
        91 * @private
        92 */
        93goog.dom.defaultDomHelper_;
        94
        95
        96/**
        97 * Gets the document object being used by the dom library.
        98 * @return {!Document} Document object.
        99 */
        100goog.dom.getDocument = function() {
        101 return document;
        102};
        103
        104
        105/**
        106 * Gets an element from the current document by element id.
        107 *
        108 * If an Element is passed in, it is returned.
        109 *
        110 * @param {string|Element} element Element ID or a DOM node.
        111 * @return {Element} The element with the given ID, or the node passed in.
        112 */
        113goog.dom.getElement = function(element) {
        114 return goog.dom.getElementHelper_(document, element);
        115};
        116
        117
        118/**
        119 * Gets an element by id from the given document (if present).
        120 * If an element is given, it is returned.
        121 * @param {!Document} doc
        122 * @param {string|Element} element Element ID or a DOM node.
        123 * @return {Element} The resulting element.
        124 * @private
        125 */
        126goog.dom.getElementHelper_ = function(doc, element) {
        127 return goog.isString(element) ?
        128 doc.getElementById(element) :
        129 element;
        130};
        131
        132
        133/**
        134 * Gets an element by id, asserting that the element is found.
        135 *
        136 * This is used when an element is expected to exist, and should fail with
        137 * an assertion error if it does not (if assertions are enabled).
        138 *
        139 * @param {string} id Element ID.
        140 * @return {!Element} The element with the given ID, if it exists.
        141 */
        142goog.dom.getRequiredElement = function(id) {
        143 return goog.dom.getRequiredElementHelper_(document, id);
        144};
        145
        146
        147/**
        148 * Helper function for getRequiredElementHelper functions, both static and
        149 * on DomHelper. Asserts the element with the given id exists.
        150 * @param {!Document} doc
        151 * @param {string} id
        152 * @return {!Element} The element with the given ID, if it exists.
        153 * @private
        154 */
        155goog.dom.getRequiredElementHelper_ = function(doc, id) {
        156 // To prevent users passing in Elements as is permitted in getElement().
        157 goog.asserts.assertString(id);
        158 var element = goog.dom.getElementHelper_(doc, id);
        159 element = goog.asserts.assertElement(element,
        160 'No element found with id: ' + id);
        161 return element;
        162};
        163
        164
        165/**
        166 * Alias for getElement.
        167 * @param {string|Element} element Element ID or a DOM node.
        168 * @return {Element} The element with the given ID, or the node passed in.
        169 * @deprecated Use {@link goog.dom.getElement} instead.
        170 */
        171goog.dom.$ = goog.dom.getElement;
        172
        173
        174/**
        175 * Looks up elements by both tag and class name, using browser native functions
        176 * ({@code querySelectorAll}, {@code getElementsByTagName} or
        177 * {@code getElementsByClassName}) where possible. This function
        178 * is a useful, if limited, way of collecting a list of DOM elements
        179 * with certain characteristics. {@code goog.dom.query} offers a
        180 * more powerful and general solution which allows matching on CSS3
        181 * selector expressions, but at increased cost in code size. If all you
        182 * need is particular tags belonging to a single class, this function
        183 * is fast and sleek.
        184 *
        185 * Note that tag names are case sensitive in the SVG namespace, and this
        186 * function converts opt_tag to uppercase for comparisons. For queries in the
        187 * SVG namespace you should use querySelector or querySelectorAll instead.
        188 * https://bugzilla.mozilla.org/show_bug.cgi?id=963870
        189 * https://bugs.webkit.org/show_bug.cgi?id=83438
        190 *
        191 * @see {goog.dom.query}
        192 *
        193 * @param {?string=} opt_tag Element tag name.
        194 * @param {?string=} opt_class Optional class name.
        195 * @param {(Document|Element)=} opt_el Optional element to look in.
        196 * @return { {length: number} } Array-like list of elements (only a length
        197 * property and numerical indices are guaranteed to exist).
        198 */
        199goog.dom.getElementsByTagNameAndClass = function(opt_tag, opt_class, opt_el) {
        200 return goog.dom.getElementsByTagNameAndClass_(document, opt_tag, opt_class,
        201 opt_el);
        202};
        203
        204
        205/**
        206 * Returns a static, array-like list of the elements with the provided
        207 * className.
        208 * @see {goog.dom.query}
        209 * @param {string} className the name of the class to look for.
        210 * @param {(Document|Element)=} opt_el Optional element to look in.
        211 * @return { {length: number} } The items found with the class name provided.
        212 */
        213goog.dom.getElementsByClass = function(className, opt_el) {
        214 var parent = opt_el || document;
        215 if (goog.dom.canUseQuerySelector_(parent)) {
        216 return parent.querySelectorAll('.' + className);
        217 }
        218 return goog.dom.getElementsByTagNameAndClass_(
        219 document, '*', className, opt_el);
        220};
        221
        222
        223/**
        224 * Returns the first element with the provided className.
        225 * @see {goog.dom.query}
        226 * @param {string} className the name of the class to look for.
        227 * @param {Element|Document=} opt_el Optional element to look in.
        228 * @return {Element} The first item with the class name provided.
        229 */
        230goog.dom.getElementByClass = function(className, opt_el) {
        231 var parent = opt_el || document;
        232 var retVal = null;
        233 if (parent.getElementsByClassName) {
        234 retVal = parent.getElementsByClassName(className)[0];
        235 } else if (goog.dom.canUseQuerySelector_(parent)) {
        236 retVal = parent.querySelector('.' + className);
        237 } else {
        238 retVal = goog.dom.getElementsByTagNameAndClass_(
        239 document, '*', className, opt_el)[0];
        240 }
        241 return retVal || null;
        242};
        243
        244
        245/**
        246 * Ensures an element with the given className exists, and then returns the
        247 * first element with the provided className.
        248 * @see {goog.dom.query}
        249 * @param {string} className the name of the class to look for.
        250 * @param {!Element|!Document=} opt_root Optional element or document to look
        251 * in.
        252 * @return {!Element} The first item with the class name provided.
        253 * @throws {goog.asserts.AssertionError} Thrown if no element is found.
        254 */
        255goog.dom.getRequiredElementByClass = function(className, opt_root) {
        256 var retValue = goog.dom.getElementByClass(className, opt_root);
        257 return goog.asserts.assert(retValue,
        258 'No element found with className: ' + className);
        259};
        260
        261
        262/**
        263 * Prefer the standardized (http://www.w3.org/TR/selectors-api/), native and
        264 * fast W3C Selectors API.
        265 * @param {!(Element|Document)} parent The parent document object.
        266 * @return {boolean} whether or not we can use parent.querySelector* APIs.
        267 * @private
        268 */
        269goog.dom.canUseQuerySelector_ = function(parent) {
        270 return !!(parent.querySelectorAll && parent.querySelector);
        271};
        272
        273
        274/**
        275 * Helper for {@code getElementsByTagNameAndClass}.
        276 * @param {!Document} doc The document to get the elements in.
        277 * @param {?string=} opt_tag Element tag name.
        278 * @param {?string=} opt_class Optional class name.
        279 * @param {(Document|Element)=} opt_el Optional element to look in.
        280 * @return { {length: number} } Array-like list of elements (only a length
        281 * property and numerical indices are guaranteed to exist).
        282 * @private
        283 */
        284goog.dom.getElementsByTagNameAndClass_ = function(doc, opt_tag, opt_class,
        285 opt_el) {
        286 var parent = opt_el || doc;
        287 var tagName = (opt_tag && opt_tag != '*') ? opt_tag.toUpperCase() : '';
        288
        289 if (goog.dom.canUseQuerySelector_(parent) &&
        290 (tagName || opt_class)) {
        291 var query = tagName + (opt_class ? '.' + opt_class : '');
        292 return parent.querySelectorAll(query);
        293 }
        294
        295 // Use the native getElementsByClassName if available, under the assumption
        296 // that even when the tag name is specified, there will be fewer elements to
        297 // filter through when going by class than by tag name
        298 if (opt_class && parent.getElementsByClassName) {
        299 var els = parent.getElementsByClassName(opt_class);
        300
        301 if (tagName) {
        302 var arrayLike = {};
        303 var len = 0;
        304
        305 // Filter for specific tags if requested.
        306 for (var i = 0, el; el = els[i]; i++) {
        307 if (tagName == el.nodeName) {
        308 arrayLike[len++] = el;
        309 }
        310 }
        311 arrayLike.length = len;
        312
        313 return arrayLike;
        314 } else {
        315 return els;
        316 }
        317 }
        318
        319 var els = parent.getElementsByTagName(tagName || '*');
        320
        321 if (opt_class) {
        322 var arrayLike = {};
        323 var len = 0;
        324 for (var i = 0, el; el = els[i]; i++) {
        325 var className = el.className;
        326 // Check if className has a split function since SVG className does not.
        327 if (typeof className.split == 'function' &&
        328 goog.array.contains(className.split(/\s+/), opt_class)) {
        329 arrayLike[len++] = el;
        330 }
        331 }
        332 arrayLike.length = len;
        333 return arrayLike;
        334 } else {
        335 return els;
        336 }
        337};
        338
        339
        340/**
        341 * Alias for {@code getElementsByTagNameAndClass}.
        342 * @param {?string=} opt_tag Element tag name.
        343 * @param {?string=} opt_class Optional class name.
        344 * @param {Element=} opt_el Optional element to look in.
        345 * @return { {length: number} } Array-like list of elements (only a length
        346 * property and numerical indices are guaranteed to exist).
        347 * @deprecated Use {@link goog.dom.getElementsByTagNameAndClass} instead.
        348 */
        349goog.dom.$$ = goog.dom.getElementsByTagNameAndClass;
        350
        351
        352/**
        353 * Sets multiple properties on a node.
        354 * @param {Element} element DOM node to set properties on.
        355 * @param {Object} properties Hash of property:value pairs.
        356 */
        357goog.dom.setProperties = function(element, properties) {
        358 goog.object.forEach(properties, function(val, key) {
        359 if (key == 'style') {
        360 element.style.cssText = val;
        361 } else if (key == 'class') {
        362 element.className = val;
        363 } else if (key == 'for') {
        364 element.htmlFor = val;
        365 } else if (key in goog.dom.DIRECT_ATTRIBUTE_MAP_) {
        366 element.setAttribute(goog.dom.DIRECT_ATTRIBUTE_MAP_[key], val);
        367 } else if (goog.string.startsWith(key, 'aria-') ||
        368 goog.string.startsWith(key, 'data-')) {
        369 element.setAttribute(key, val);
        370 } else {
        371 element[key] = val;
        372 }
        373 });
        374};
        375
        376
        377/**
        378 * Map of attributes that should be set using
        379 * element.setAttribute(key, val) instead of element[key] = val. Used
        380 * by goog.dom.setProperties.
        381 *
        382 * @private {!Object<string, string>}
        383 * @const
        384 */
        385goog.dom.DIRECT_ATTRIBUTE_MAP_ = {
        386 'cellpadding': 'cellPadding',
        387 'cellspacing': 'cellSpacing',
        388 'colspan': 'colSpan',
        389 'frameborder': 'frameBorder',
        390 'height': 'height',
        391 'maxlength': 'maxLength',
        392 'role': 'role',
        393 'rowspan': 'rowSpan',
        394 'type': 'type',
        395 'usemap': 'useMap',
        396 'valign': 'vAlign',
        397 'width': 'width'
        398};
        399
        400
        401/**
        402 * Gets the dimensions of the viewport.
        403 *
        404 * Gecko Standards mode:
        405 * docEl.clientWidth Width of viewport excluding scrollbar.
        406 * win.innerWidth Width of viewport including scrollbar.
        407 * body.clientWidth Width of body element.
        408 *
        409 * docEl.clientHeight Height of viewport excluding scrollbar.
        410 * win.innerHeight Height of viewport including scrollbar.
        411 * body.clientHeight Height of document.
        412 *
        413 * Gecko Backwards compatible mode:
        414 * docEl.clientWidth Width of viewport excluding scrollbar.
        415 * win.innerWidth Width of viewport including scrollbar.
        416 * body.clientWidth Width of viewport excluding scrollbar.
        417 *
        418 * docEl.clientHeight Height of document.
        419 * win.innerHeight Height of viewport including scrollbar.
        420 * body.clientHeight Height of viewport excluding scrollbar.
        421 *
        422 * IE6/7 Standards mode:
        423 * docEl.clientWidth Width of viewport excluding scrollbar.
        424 * win.innerWidth Undefined.
        425 * body.clientWidth Width of body element.
        426 *
        427 * docEl.clientHeight Height of viewport excluding scrollbar.
        428 * win.innerHeight Undefined.
        429 * body.clientHeight Height of document element.
        430 *
        431 * IE5 + IE6/7 Backwards compatible mode:
        432 * docEl.clientWidth 0.
        433 * win.innerWidth Undefined.
        434 * body.clientWidth Width of viewport excluding scrollbar.
        435 *
        436 * docEl.clientHeight 0.
        437 * win.innerHeight Undefined.
        438 * body.clientHeight Height of viewport excluding scrollbar.
        439 *
        440 * Opera 9 Standards and backwards compatible mode:
        441 * docEl.clientWidth Width of viewport excluding scrollbar.
        442 * win.innerWidth Width of viewport including scrollbar.
        443 * body.clientWidth Width of viewport excluding scrollbar.
        444 *
        445 * docEl.clientHeight Height of document.
        446 * win.innerHeight Height of viewport including scrollbar.
        447 * body.clientHeight Height of viewport excluding scrollbar.
        448 *
        449 * WebKit:
        450 * Safari 2
        451 * docEl.clientHeight Same as scrollHeight.
        452 * docEl.clientWidth Same as innerWidth.
        453 * win.innerWidth Width of viewport excluding scrollbar.
        454 * win.innerHeight Height of the viewport including scrollbar.
        455 * frame.innerHeight Height of the viewport exluding scrollbar.
        456 *
        457 * Safari 3 (tested in 522)
        458 *
        459 * docEl.clientWidth Width of viewport excluding scrollbar.
        460 * docEl.clientHeight Height of viewport excluding scrollbar in strict mode.
        461 * body.clientHeight Height of viewport excluding scrollbar in quirks mode.
        462 *
        463 * @param {Window=} opt_window Optional window element to test.
        464 * @return {!goog.math.Size} Object with values 'width' and 'height'.
        465 */
        466goog.dom.getViewportSize = function(opt_window) {
        467 // TODO(arv): This should not take an argument
        468 return goog.dom.getViewportSize_(opt_window || window);
        469};
        470
        471
        472/**
        473 * Helper for {@code getViewportSize}.
        474 * @param {Window} win The window to get the view port size for.
        475 * @return {!goog.math.Size} Object with values 'width' and 'height'.
        476 * @private
        477 */
        478goog.dom.getViewportSize_ = function(win) {
        479 var doc = win.document;
        480 var el = goog.dom.isCss1CompatMode_(doc) ? doc.documentElement : doc.body;
        481 return new goog.math.Size(el.clientWidth, el.clientHeight);
        482};
        483
        484
        485/**
        486 * Calculates the height of the document.
        487 *
        488 * @return {number} The height of the current document.
        489 */
        490goog.dom.getDocumentHeight = function() {
        491 return goog.dom.getDocumentHeight_(window);
        492};
        493
        494
        495/**
        496 * Calculates the height of the document of the given window.
        497 *
        498 * Function code copied from the opensocial gadget api:
        499 * gadgets.window.adjustHeight(opt_height)
        500 *
        501 * @private
        502 * @param {!Window} win The window whose document height to retrieve.
        503 * @return {number} The height of the document of the given window.
        504 */
        505goog.dom.getDocumentHeight_ = function(win) {
        506 // NOTE(eae): This method will return the window size rather than the document
        507 // size in webkit quirks mode.
        508 var doc = win.document;
        509 var height = 0;
        510
        511 if (doc) {
        512 // Calculating inner content height is hard and different between
        513 // browsers rendering in Strict vs. Quirks mode. We use a combination of
        514 // three properties within document.body and document.documentElement:
        515 // - scrollHeight
        516 // - offsetHeight
        517 // - clientHeight
        518 // These values differ significantly between browsers and rendering modes.
        519 // But there are patterns. It just takes a lot of time and persistence
        520 // to figure out.
        521
        522 var body = doc.body;
        523 var docEl = doc.documentElement;
        524 if (!(docEl && body)) {
        525 return 0;
        526 }
        527
        528 // Get the height of the viewport
        529 var vh = goog.dom.getViewportSize_(win).height;
        530 if (goog.dom.isCss1CompatMode_(doc) && docEl.scrollHeight) {
        531 // In Strict mode:
        532 // The inner content height is contained in either:
        533 // document.documentElement.scrollHeight
        534 // document.documentElement.offsetHeight
        535 // Based on studying the values output by different browsers,
        536 // use the value that's NOT equal to the viewport height found above.
        537 height = docEl.scrollHeight != vh ?
        538 docEl.scrollHeight : docEl.offsetHeight;
        539 } else {
        540 // In Quirks mode:
        541 // documentElement.clientHeight is equal to documentElement.offsetHeight
        542 // except in IE. In most browsers, document.documentElement can be used
        543 // to calculate the inner content height.
        544 // However, in other browsers (e.g. IE), document.body must be used
        545 // instead. How do we know which one to use?
        546 // If document.documentElement.clientHeight does NOT equal
        547 // document.documentElement.offsetHeight, then use document.body.
        548 var sh = docEl.scrollHeight;
        549 var oh = docEl.offsetHeight;
        550 if (docEl.clientHeight != oh) {
        551 sh = body.scrollHeight;
        552 oh = body.offsetHeight;
        553 }
        554
        555 // Detect whether the inner content height is bigger or smaller
        556 // than the bounding box (viewport). If bigger, take the larger
        557 // value. If smaller, take the smaller value.
        558 if (sh > vh) {
        559 // Content is larger
        560 height = sh > oh ? sh : oh;
        561 } else {
        562 // Content is smaller
        563 height = sh < oh ? sh : oh;
        564 }
        565 }
        566 }
        567
        568 return height;
        569};
        570
        571
        572/**
        573 * Gets the page scroll distance as a coordinate object.
        574 *
        575 * @param {Window=} opt_window Optional window element to test.
        576 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
        577 * @deprecated Use {@link goog.dom.getDocumentScroll} instead.
        578 */
        579goog.dom.getPageScroll = function(opt_window) {
        580 var win = opt_window || goog.global || window;
        581 return goog.dom.getDomHelper(win.document).getDocumentScroll();
        582};
        583
        584
        585/**
        586 * Gets the document scroll distance as a coordinate object.
        587 *
        588 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
        589 */
        590goog.dom.getDocumentScroll = function() {
        591 return goog.dom.getDocumentScroll_(document);
        592};
        593
        594
        595/**
        596 * Helper for {@code getDocumentScroll}.
        597 *
        598 * @param {!Document} doc The document to get the scroll for.
        599 * @return {!goog.math.Coordinate} Object with values 'x' and 'y'.
        600 * @private
        601 */
        602goog.dom.getDocumentScroll_ = function(doc) {
        603 var el = goog.dom.getDocumentScrollElement_(doc);
        604 var win = goog.dom.getWindow_(doc);
        605 if (goog.userAgent.IE && goog.userAgent.isVersionOrHigher('10') &&
        606 win.pageYOffset != el.scrollTop) {
        607 // The keyboard on IE10 touch devices shifts the page using the pageYOffset
        608 // without modifying scrollTop. For this case, we want the body scroll
        609 // offsets.
        610 return new goog.math.Coordinate(el.scrollLeft, el.scrollTop);
        611 }
        612 return new goog.math.Coordinate(win.pageXOffset || el.scrollLeft,
        613 win.pageYOffset || el.scrollTop);
        614};
        615
        616
        617/**
        618 * Gets the document scroll element.
        619 * @return {!Element} Scrolling element.
        620 */
        621goog.dom.getDocumentScrollElement = function() {
        622 return goog.dom.getDocumentScrollElement_(document);
        623};
        624
        625
        626/**
        627 * Helper for {@code getDocumentScrollElement}.
        628 * @param {!Document} doc The document to get the scroll element for.
        629 * @return {!Element} Scrolling element.
        630 * @private
        631 */
        632goog.dom.getDocumentScrollElement_ = function(doc) {
        633 // Old WebKit needs body.scrollLeft in both quirks mode and strict mode. We
        634 // also default to the documentElement if the document does not have a body
        635 // (e.g. a SVG document).
        636 // Uses http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement to
        637 // avoid trying to guess about browser behavior from the UA string.
        638 if (doc.scrollingElement) {
        639 return doc.scrollingElement;
        640 }
        641 if (!goog.userAgent.WEBKIT && goog.dom.isCss1CompatMode_(doc)) {
        642 return doc.documentElement;
        643 }
        644 return doc.body || doc.documentElement;
        645};
        646
        647
        648/**
        649 * Gets the window object associated with the given document.
        650 *
        651 * @param {Document=} opt_doc Document object to get window for.
        652 * @return {!Window} The window associated with the given document.
        653 */
        654goog.dom.getWindow = function(opt_doc) {
        655 // TODO(arv): This should not take an argument.
        656 return opt_doc ? goog.dom.getWindow_(opt_doc) : window;
        657};
        658
        659
        660/**
        661 * Helper for {@code getWindow}.
        662 *
        663 * @param {!Document} doc Document object to get window for.
        664 * @return {!Window} The window associated with the given document.
        665 * @private
        666 */
        667goog.dom.getWindow_ = function(doc) {
        668 return doc.parentWindow || doc.defaultView;
        669};
        670
        671
        672/**
        673 * Returns a dom node with a set of attributes. This function accepts varargs
        674 * for subsequent nodes to be added. Subsequent nodes will be added to the
        675 * first node as childNodes.
        676 *
        677 * So:
        678 * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
        679 * would return a div with two child paragraphs
        680 *
        681 * @param {string} tagName Tag to create.
        682 * @param {(Object|Array<string>|string)=} opt_attributes If object, then a map
        683 * of name-value pairs for attributes. If a string, then this is the
        684 * className of the new element. If an array, the elements will be joined
        685 * together as the className of the new element.
        686 * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
        687 * strings for text nodes. If one of the var_args is an array or NodeList,i
        688 * its elements will be added as childNodes instead.
        689 * @return {!Element} Reference to a DOM node.
        690 */
        691goog.dom.createDom = function(tagName, opt_attributes, var_args) {
        692 return goog.dom.createDom_(document, arguments);
        693};
        694
        695
        696/**
        697 * Helper for {@code createDom}.
        698 * @param {!Document} doc The document to create the DOM in.
        699 * @param {!Arguments} args Argument object passed from the callers. See
        700 * {@code goog.dom.createDom} for details.
        701 * @return {!Element} Reference to a DOM node.
        702 * @private
        703 */
        704goog.dom.createDom_ = function(doc, args) {
        705 var tagName = args[0];
        706 var attributes = args[1];
        707
        708 // Internet Explorer is dumb: http://msdn.microsoft.com/workshop/author/
        709 // dhtml/reference/properties/name_2.asp
        710 // Also does not allow setting of 'type' attribute on 'input' or 'button'.
        711 if (!goog.dom.BrowserFeature.CAN_ADD_NAME_OR_TYPE_ATTRIBUTES && attributes &&
        712 (attributes.name || attributes.type)) {
        713 var tagNameArr = ['<', tagName];
        714 if (attributes.name) {
        715 tagNameArr.push(' name="', goog.string.htmlEscape(attributes.name),
        716 '"');
        717 }
        718 if (attributes.type) {
        719 tagNameArr.push(' type="', goog.string.htmlEscape(attributes.type),
        720 '"');
        721
        722 // Clone attributes map to remove 'type' without mutating the input.
        723 var clone = {};
        724 goog.object.extend(clone, attributes);
        725
        726 // JSCompiler can't see how goog.object.extend added this property,
        727 // because it was essentially added by reflection.
        728 // So it needs to be quoted.
        729 delete clone['type'];
        730
        731 attributes = clone;
        732 }
        733 tagNameArr.push('>');
        734 tagName = tagNameArr.join('');
        735 }
        736
        737 var element = doc.createElement(tagName);
        738
        739 if (attributes) {
        740 if (goog.isString(attributes)) {
        741 element.className = attributes;
        742 } else if (goog.isArray(attributes)) {
        743 element.className = attributes.join(' ');
        744 } else {
        745 goog.dom.setProperties(element, attributes);
        746 }
        747 }
        748
        749 if (args.length > 2) {
        750 goog.dom.append_(doc, element, args, 2);
        751 }
        752
        753 return element;
        754};
        755
        756
        757/**
        758 * Appends a node with text or other nodes.
        759 * @param {!Document} doc The document to create new nodes in.
        760 * @param {!Node} parent The node to append nodes to.
        761 * @param {!Arguments} args The values to add. See {@code goog.dom.append}.
        762 * @param {number} startIndex The index of the array to start from.
        763 * @private
        764 */
        765goog.dom.append_ = function(doc, parent, args, startIndex) {
        766 function childHandler(child) {
        767 // TODO(user): More coercion, ala MochiKit?
        768 if (child) {
        769 parent.appendChild(goog.isString(child) ?
        770 doc.createTextNode(child) : child);
        771 }
        772 }
        773
        774 for (var i = startIndex; i < args.length; i++) {
        775 var arg = args[i];
        776 // TODO(attila): Fix isArrayLike to return false for a text node.
        777 if (goog.isArrayLike(arg) && !goog.dom.isNodeLike(arg)) {
        778 // If the argument is a node list, not a real array, use a clone,
        779 // because forEach can't be used to mutate a NodeList.
        780 goog.array.forEach(goog.dom.isNodeList(arg) ?
        781 goog.array.toArray(arg) : arg,
        782 childHandler);
        783 } else {
        784 childHandler(arg);
        785 }
        786 }
        787};
        788
        789
        790/**
        791 * Alias for {@code createDom}.
        792 * @param {string} tagName Tag to create.
        793 * @param {(string|Object)=} opt_attributes If object, then a map of name-value
        794 * pairs for attributes. If a string, then this is the className of the new
        795 * element.
        796 * @param {...(Object|string|Array|NodeList)} var_args Further DOM nodes or
        797 * strings for text nodes. If one of the var_args is an array, its
        798 * children will be added as childNodes instead.
        799 * @return {!Element} Reference to a DOM node.
        800 * @deprecated Use {@link goog.dom.createDom} instead.
        801 */
        802goog.dom.$dom = goog.dom.createDom;
        803
        804
        805/**
        806 * Creates a new element.
        807 * @param {string} name Tag name.
        808 * @return {!Element} The new element.
        809 */
        810goog.dom.createElement = function(name) {
        811 return document.createElement(name);
        812};
        813
        814
        815/**
        816 * Creates a new text node.
        817 * @param {number|string} content Content.
        818 * @return {!Text} The new text node.
        819 */
        820goog.dom.createTextNode = function(content) {
        821 return document.createTextNode(String(content));
        822};
        823
        824
        825/**
        826 * Create a table.
        827 * @param {number} rows The number of rows in the table. Must be >= 1.
        828 * @param {number} columns The number of columns in the table. Must be >= 1.
        829 * @param {boolean=} opt_fillWithNbsp If true, fills table entries with
        830 * {@code goog.string.Unicode.NBSP} characters.
        831 * @return {!Element} The created table.
        832 */
        833goog.dom.createTable = function(rows, columns, opt_fillWithNbsp) {
        834 // TODO(user): Return HTMLTableElement, also in prototype function.
        835 // Callers need to be updated to e.g. not assign numbers to table.cellSpacing.
        836 return goog.dom.createTable_(document, rows, columns, !!opt_fillWithNbsp);
        837};
        838
        839
        840/**
        841 * Create a table.
        842 * @param {!Document} doc Document object to use to create the table.
        843 * @param {number} rows The number of rows in the table. Must be >= 1.
        844 * @param {number} columns The number of columns in the table. Must be >= 1.
        845 * @param {boolean} fillWithNbsp If true, fills table entries with
        846 * {@code goog.string.Unicode.NBSP} characters.
        847 * @return {!HTMLTableElement} The created table.
        848 * @private
        849 */
        850goog.dom.createTable_ = function(doc, rows, columns, fillWithNbsp) {
        851 var table = /** @type {!HTMLTableElement} */
        852 (doc.createElement(goog.dom.TagName.TABLE));
        853 var tbody = table.appendChild(doc.createElement(goog.dom.TagName.TBODY));
        854 for (var i = 0; i < rows; i++) {
        855 var tr = doc.createElement(goog.dom.TagName.TR);
        856 for (var j = 0; j < columns; j++) {
        857 var td = doc.createElement(goog.dom.TagName.TD);
        858 // IE <= 9 will create a text node if we set text content to the empty
        859 // string, so we avoid doing it unless necessary. This ensures that the
        860 // same DOM tree is returned on all browsers.
        861 if (fillWithNbsp) {
        862 goog.dom.setTextContent(td, goog.string.Unicode.NBSP);
        863 }
        864 tr.appendChild(td);
        865 }
        866 tbody.appendChild(tr);
        867 }
        868 return table;
        869};
        870
        871
        872/**
        873 * Converts HTML markup into a node.
        874 * @param {!goog.html.SafeHtml} html The HTML markup to convert.
        875 * @return {!Node} The resulting node.
        876 */
        877goog.dom.safeHtmlToNode = function(html) {
        878 return goog.dom.safeHtmlToNode_(document, html);
        879};
        880
        881
        882/**
        883 * Helper for {@code safeHtmlToNode}.
        884 * @param {!Document} doc The document.
        885 * @param {!goog.html.SafeHtml} html The HTML markup to convert.
        886 * @return {!Node} The resulting node.
        887 * @private
        888 */
        889goog.dom.safeHtmlToNode_ = function(doc, html) {
        890 var tempDiv = doc.createElement(goog.dom.TagName.DIV);
        891 if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
        892 goog.dom.safe.setInnerHtml(tempDiv,
        893 goog.html.SafeHtml.concat(goog.html.SafeHtml.create('br'), html));
        894 tempDiv.removeChild(tempDiv.firstChild);
        895 } else {
        896 goog.dom.safe.setInnerHtml(tempDiv, html);
        897 }
        898 return goog.dom.childrenToNode_(doc, tempDiv);
        899};
        900
        901
        902/**
        903 * Converts an HTML string into a document fragment. The string must be
        904 * sanitized in order to avoid cross-site scripting. For example
        905 * {@code goog.dom.htmlToDocumentFragment('&lt;img src=x onerror=alert(0)&gt;')}
        906 * triggers an alert in all browsers, even if the returned document fragment
        907 * is thrown away immediately.
        908 *
        909 * @param {string} htmlString The HTML string to convert.
        910 * @return {!Node} The resulting document fragment.
        911 */
        912goog.dom.htmlToDocumentFragment = function(htmlString) {
        913 return goog.dom.htmlToDocumentFragment_(document, htmlString);
        914};
        915
        916
        917// TODO(jakubvrana): Merge with {@code safeHtmlToNode_}.
        918/**
        919 * Helper for {@code htmlToDocumentFragment}.
        920 *
        921 * @param {!Document} doc The document.
        922 * @param {string} htmlString The HTML string to convert.
        923 * @return {!Node} The resulting document fragment.
        924 * @private
        925 */
        926goog.dom.htmlToDocumentFragment_ = function(doc, htmlString) {
        927 var tempDiv = doc.createElement(goog.dom.TagName.DIV);
        928 if (goog.dom.BrowserFeature.INNER_HTML_NEEDS_SCOPED_ELEMENT) {
        929 tempDiv.innerHTML = '<br>' + htmlString;
        930 tempDiv.removeChild(tempDiv.firstChild);
        931 } else {
        932 tempDiv.innerHTML = htmlString;
        933 }
        934 return goog.dom.childrenToNode_(doc, tempDiv);
        935};
        936
        937
        938/**
        939 * Helper for {@code htmlToDocumentFragment_}.
        940 * @param {!Document} doc The document.
        941 * @param {!Node} tempDiv The input node.
        942 * @return {!Node} The resulting node.
        943 * @private
        944 */
        945goog.dom.childrenToNode_ = function(doc, tempDiv) {
        946 if (tempDiv.childNodes.length == 1) {
        947 return tempDiv.removeChild(tempDiv.firstChild);
        948 } else {
        949 var fragment = doc.createDocumentFragment();
        950 while (tempDiv.firstChild) {
        951 fragment.appendChild(tempDiv.firstChild);
        952 }
        953 return fragment;
        954 }
        955};
        956
        957
        958/**
        959 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
        960 * mode, false otherwise.
        961 * @return {boolean} True if in CSS1-compatible mode.
        962 */
        963goog.dom.isCss1CompatMode = function() {
        964 return goog.dom.isCss1CompatMode_(document);
        965};
        966
        967
        968/**
        969 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
        970 * mode, false otherwise.
        971 * @param {!Document} doc The document to check.
        972 * @return {boolean} True if in CSS1-compatible mode.
        973 * @private
        974 */
        975goog.dom.isCss1CompatMode_ = function(doc) {
        976 if (goog.dom.COMPAT_MODE_KNOWN_) {
        977 return goog.dom.ASSUME_STANDARDS_MODE;
        978 }
        979
        980 return doc.compatMode == 'CSS1Compat';
        981};
        982
        983
        984/**
        985 * Determines if the given node can contain children, intended to be used for
        986 * HTML generation.
        987 *
        988 * IE natively supports node.canHaveChildren but has inconsistent behavior.
        989 * Prior to IE8 the base tag allows children and in IE9 all nodes return true
        990 * for canHaveChildren.
        991 *
        992 * In practice all non-IE browsers allow you to add children to any node, but
        993 * the behavior is inconsistent:
        994 *
        995 * <pre>
        996 * var a = document.createElement(goog.dom.TagName.BR);
        997 * a.appendChild(document.createTextNode('foo'));
        998 * a.appendChild(document.createTextNode('bar'));
        999 * console.log(a.childNodes.length); // 2
        1000 * console.log(a.innerHTML); // Chrome: "", IE9: "foobar", FF3.5: "foobar"
        1001 * </pre>
        1002 *
        1003 * For more information, see:
        1004 * http://dev.w3.org/html5/markup/syntax.html#syntax-elements
        1005 *
        1006 * TODO(user): Rename shouldAllowChildren() ?
        1007 *
        1008 * @param {Node} node The node to check.
        1009 * @return {boolean} Whether the node can contain children.
        1010 */
        1011goog.dom.canHaveChildren = function(node) {
        1012 if (node.nodeType != goog.dom.NodeType.ELEMENT) {
        1013 return false;
        1014 }
        1015 switch (node.tagName) {
        1016 case goog.dom.TagName.APPLET:
        1017 case goog.dom.TagName.AREA:
        1018 case goog.dom.TagName.BASE:
        1019 case goog.dom.TagName.BR:
        1020 case goog.dom.TagName.COL:
        1021 case goog.dom.TagName.COMMAND:
        1022 case goog.dom.TagName.EMBED:
        1023 case goog.dom.TagName.FRAME:
        1024 case goog.dom.TagName.HR:
        1025 case goog.dom.TagName.IMG:
        1026 case goog.dom.TagName.INPUT:
        1027 case goog.dom.TagName.IFRAME:
        1028 case goog.dom.TagName.ISINDEX:
        1029 case goog.dom.TagName.KEYGEN:
        1030 case goog.dom.TagName.LINK:
        1031 case goog.dom.TagName.NOFRAMES:
        1032 case goog.dom.TagName.NOSCRIPT:
        1033 case goog.dom.TagName.META:
        1034 case goog.dom.TagName.OBJECT:
        1035 case goog.dom.TagName.PARAM:
        1036 case goog.dom.TagName.SCRIPT:
        1037 case goog.dom.TagName.SOURCE:
        1038 case goog.dom.TagName.STYLE:
        1039 case goog.dom.TagName.TRACK:
        1040 case goog.dom.TagName.WBR:
        1041 return false;
        1042 }
        1043 return true;
        1044};
        1045
        1046
        1047/**
        1048 * Appends a child to a node.
        1049 * @param {Node} parent Parent.
        1050 * @param {Node} child Child.
        1051 */
        1052goog.dom.appendChild = function(parent, child) {
        1053 parent.appendChild(child);
        1054};
        1055
        1056
        1057/**
        1058 * Appends a node with text or other nodes.
        1059 * @param {!Node} parent The node to append nodes to.
        1060 * @param {...goog.dom.Appendable} var_args The things to append to the node.
        1061 * If this is a Node it is appended as is.
        1062 * If this is a string then a text node is appended.
        1063 * If this is an array like object then fields 0 to length - 1 are appended.
        1064 */
        1065goog.dom.append = function(parent, var_args) {
        1066 goog.dom.append_(goog.dom.getOwnerDocument(parent), parent, arguments, 1);
        1067};
        1068
        1069
        1070/**
        1071 * Removes all the child nodes on a DOM node.
        1072 * @param {Node} node Node to remove children from.
        1073 */
        1074goog.dom.removeChildren = function(node) {
        1075 // Note: Iterations over live collections can be slow, this is the fastest
        1076 // we could find. The double parenthesis are used to prevent JsCompiler and
        1077 // strict warnings.
        1078 var child;
        1079 while ((child = node.firstChild)) {
        1080 node.removeChild(child);
        1081 }
        1082};
        1083
        1084
        1085/**
        1086 * Inserts a new node before an existing reference node (i.e. as the previous
        1087 * sibling). If the reference node has no parent, then does nothing.
        1088 * @param {Node} newNode Node to insert.
        1089 * @param {Node} refNode Reference node to insert before.
        1090 */
        1091goog.dom.insertSiblingBefore = function(newNode, refNode) {
        1092 if (refNode.parentNode) {
        1093 refNode.parentNode.insertBefore(newNode, refNode);
        1094 }
        1095};
        1096
        1097
        1098/**
        1099 * Inserts a new node after an existing reference node (i.e. as the next
        1100 * sibling). If the reference node has no parent, then does nothing.
        1101 * @param {Node} newNode Node to insert.
        1102 * @param {Node} refNode Reference node to insert after.
        1103 */
        1104goog.dom.insertSiblingAfter = function(newNode, refNode) {
        1105 if (refNode.parentNode) {
        1106 refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
        1107 }
        1108};
        1109
        1110
        1111/**
        1112 * Insert a child at a given index. If index is larger than the number of child
        1113 * nodes that the parent currently has, the node is inserted as the last child
        1114 * node.
        1115 * @param {Element} parent The element into which to insert the child.
        1116 * @param {Node} child The element to insert.
        1117 * @param {number} index The index at which to insert the new child node. Must
        1118 * not be negative.
        1119 */
        1120goog.dom.insertChildAt = function(parent, child, index) {
        1121 // Note that if the second argument is null, insertBefore
        1122 // will append the child at the end of the list of children.
        1123 parent.insertBefore(child, parent.childNodes[index] || null);
        1124};
        1125
        1126
        1127/**
        1128 * Removes a node from its parent.
        1129 * @param {Node} node The node to remove.
        1130 * @return {Node} The node removed if removed; else, null.
        1131 */
        1132goog.dom.removeNode = function(node) {
        1133 return node && node.parentNode ? node.parentNode.removeChild(node) : null;
        1134};
        1135
        1136
        1137/**
        1138 * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
        1139 * parent.
        1140 * @param {Node} newNode Node to insert.
        1141 * @param {Node} oldNode Node to replace.
        1142 */
        1143goog.dom.replaceNode = function(newNode, oldNode) {
        1144 var parent = oldNode.parentNode;
        1145 if (parent) {
        1146 parent.replaceChild(newNode, oldNode);
        1147 }
        1148};
        1149
        1150
        1151/**
        1152 * Flattens an element. That is, removes it and replace it with its children.
        1153 * Does nothing if the element is not in the document.
        1154 * @param {Element} element The element to flatten.
        1155 * @return {Element|undefined} The original element, detached from the document
        1156 * tree, sans children; or undefined, if the element was not in the document
        1157 * to begin with.
        1158 */
        1159goog.dom.flattenElement = function(element) {
        1160 var child, parent = element.parentNode;
        1161 if (parent && parent.nodeType != goog.dom.NodeType.DOCUMENT_FRAGMENT) {
        1162 // Use IE DOM method (supported by Opera too) if available
        1163 if (element.removeNode) {
        1164 return /** @type {Element} */ (element.removeNode(false));
        1165 } else {
        1166 // Move all children of the original node up one level.
        1167 while ((child = element.firstChild)) {
        1168 parent.insertBefore(child, element);
        1169 }
        1170
        1171 // Detach the original element.
        1172 return /** @type {Element} */ (goog.dom.removeNode(element));
        1173 }
        1174 }
        1175};
        1176
        1177
        1178/**
        1179 * Returns an array containing just the element children of the given element.
        1180 * @param {Element} element The element whose element children we want.
        1181 * @return {!(Array|NodeList)} An array or array-like list of just the element
        1182 * children of the given element.
        1183 */
        1184goog.dom.getChildren = function(element) {
        1185 // We check if the children attribute is supported for child elements
        1186 // since IE8 misuses the attribute by also including comments.
        1187 if (goog.dom.BrowserFeature.CAN_USE_CHILDREN_ATTRIBUTE &&
        1188 element.children != undefined) {
        1189 return element.children;
        1190 }
        1191 // Fall back to manually filtering the element's child nodes.
        1192 return goog.array.filter(element.childNodes, function(node) {
        1193 return node.nodeType == goog.dom.NodeType.ELEMENT;
        1194 });
        1195};
        1196
        1197
        1198/**
        1199 * Returns the first child node that is an element.
        1200 * @param {Node} node The node to get the first child element of.
        1201 * @return {Element} The first child node of {@code node} that is an element.
        1202 */
        1203goog.dom.getFirstElementChild = function(node) {
        1204 if (node.firstElementChild != undefined) {
        1205 return /** @type {!Element} */(node).firstElementChild;
        1206 }
        1207 return goog.dom.getNextElementNode_(node.firstChild, true);
        1208};
        1209
        1210
        1211/**
        1212 * Returns the last child node that is an element.
        1213 * @param {Node} node The node to get the last child element of.
        1214 * @return {Element} The last child node of {@code node} that is an element.
        1215 */
        1216goog.dom.getLastElementChild = function(node) {
        1217 if (node.lastElementChild != undefined) {
        1218 return /** @type {!Element} */(node).lastElementChild;
        1219 }
        1220 return goog.dom.getNextElementNode_(node.lastChild, false);
        1221};
        1222
        1223
        1224/**
        1225 * Returns the first next sibling that is an element.
        1226 * @param {Node} node The node to get the next sibling element of.
        1227 * @return {Element} The next sibling of {@code node} that is an element.
        1228 */
        1229goog.dom.getNextElementSibling = function(node) {
        1230 if (node.nextElementSibling != undefined) {
        1231 return /** @type {!Element} */(node).nextElementSibling;
        1232 }
        1233 return goog.dom.getNextElementNode_(node.nextSibling, true);
        1234};
        1235
        1236
        1237/**
        1238 * Returns the first previous sibling that is an element.
        1239 * @param {Node} node The node to get the previous sibling element of.
        1240 * @return {Element} The first previous sibling of {@code node} that is
        1241 * an element.
        1242 */
        1243goog.dom.getPreviousElementSibling = function(node) {
        1244 if (node.previousElementSibling != undefined) {
        1245 return /** @type {!Element} */(node).previousElementSibling;
        1246 }
        1247 return goog.dom.getNextElementNode_(node.previousSibling, false);
        1248};
        1249
        1250
        1251/**
        1252 * Returns the first node that is an element in the specified direction,
        1253 * starting with {@code node}.
        1254 * @param {Node} node The node to get the next element from.
        1255 * @param {boolean} forward Whether to look forwards or backwards.
        1256 * @return {Element} The first element.
        1257 * @private
        1258 */
        1259goog.dom.getNextElementNode_ = function(node, forward) {
        1260 while (node && node.nodeType != goog.dom.NodeType.ELEMENT) {
        1261 node = forward ? node.nextSibling : node.previousSibling;
        1262 }
        1263
        1264 return /** @type {Element} */ (node);
        1265};
        1266
        1267
        1268/**
        1269 * Returns the next node in source order from the given node.
        1270 * @param {Node} node The node.
        1271 * @return {Node} The next node in the DOM tree, or null if this was the last
        1272 * node.
        1273 */
        1274goog.dom.getNextNode = function(node) {
        1275 if (!node) {
        1276 return null;
        1277 }
        1278
        1279 if (node.firstChild) {
        1280 return node.firstChild;
        1281 }
        1282
        1283 while (node && !node.nextSibling) {
        1284 node = node.parentNode;
        1285 }
        1286
        1287 return node ? node.nextSibling : null;
        1288};
        1289
        1290
        1291/**
        1292 * Returns the previous node in source order from the given node.
        1293 * @param {Node} node The node.
        1294 * @return {Node} The previous node in the DOM tree, or null if this was the
        1295 * first node.
        1296 */
        1297goog.dom.getPreviousNode = function(node) {
        1298 if (!node) {
        1299 return null;
        1300 }
        1301
        1302 if (!node.previousSibling) {
        1303 return node.parentNode;
        1304 }
        1305
        1306 node = node.previousSibling;
        1307 while (node && node.lastChild) {
        1308 node = node.lastChild;
        1309 }
        1310
        1311 return node;
        1312};
        1313
        1314
        1315/**
        1316 * Whether the object looks like a DOM node.
        1317 * @param {?} obj The object being tested for node likeness.
        1318 * @return {boolean} Whether the object looks like a DOM node.
        1319 */
        1320goog.dom.isNodeLike = function(obj) {
        1321 return goog.isObject(obj) && obj.nodeType > 0;
        1322};
        1323
        1324
        1325/**
        1326 * Whether the object looks like an Element.
        1327 * @param {?} obj The object being tested for Element likeness.
        1328 * @return {boolean} Whether the object looks like an Element.
        1329 */
        1330goog.dom.isElement = function(obj) {
        1331 return goog.isObject(obj) && obj.nodeType == goog.dom.NodeType.ELEMENT;
        1332};
        1333
        1334
        1335/**
        1336 * Returns true if the specified value is a Window object. This includes the
        1337 * global window for HTML pages, and iframe windows.
        1338 * @param {?} obj Variable to test.
        1339 * @return {boolean} Whether the variable is a window.
        1340 */
        1341goog.dom.isWindow = function(obj) {
        1342 return goog.isObject(obj) && obj['window'] == obj;
        1343};
        1344
        1345
        1346/**
        1347 * Returns an element's parent, if it's an Element.
        1348 * @param {Element} element The DOM element.
        1349 * @return {Element} The parent, or null if not an Element.
        1350 */
        1351goog.dom.getParentElement = function(element) {
        1352 var parent;
        1353 if (goog.dom.BrowserFeature.CAN_USE_PARENT_ELEMENT_PROPERTY) {
        1354 var isIe9 = goog.userAgent.IE &&
        1355 goog.userAgent.isVersionOrHigher('9') &&
        1356 !goog.userAgent.isVersionOrHigher('10');
        1357 // SVG elements in IE9 can't use the parentElement property.
        1358 // goog.global['SVGElement'] is not defined in IE9 quirks mode.
        1359 if (!(isIe9 && goog.global['SVGElement'] &&
        1360 element instanceof goog.global['SVGElement'])) {
        1361 parent = element.parentElement;
        1362 if (parent) {
        1363 return parent;
        1364 }
        1365 }
        1366 }
        1367 parent = element.parentNode;
        1368 return goog.dom.isElement(parent) ? /** @type {!Element} */ (parent) : null;
        1369};
        1370
        1371
        1372/**
        1373 * Whether a node contains another node.
        1374 * @param {Node} parent The node that should contain the other node.
        1375 * @param {Node} descendant The node to test presence of.
        1376 * @return {boolean} Whether the parent node contains the descendent node.
        1377 */
        1378goog.dom.contains = function(parent, descendant) {
        1379 // We use browser specific methods for this if available since it is faster
        1380 // that way.
        1381
        1382 // IE DOM
        1383 if (parent.contains && descendant.nodeType == goog.dom.NodeType.ELEMENT) {
        1384 return parent == descendant || parent.contains(descendant);
        1385 }
        1386
        1387 // W3C DOM Level 3
        1388 if (typeof parent.compareDocumentPosition != 'undefined') {
        1389 return parent == descendant ||
        1390 Boolean(parent.compareDocumentPosition(descendant) & 16);
        1391 }
        1392
        1393 // W3C DOM Level 1
        1394 while (descendant && parent != descendant) {
        1395 descendant = descendant.parentNode;
        1396 }
        1397 return descendant == parent;
        1398};
        1399
        1400
        1401/**
        1402 * Compares the document order of two nodes, returning 0 if they are the same
        1403 * node, a negative number if node1 is before node2, and a positive number if
        1404 * node2 is before node1. Note that we compare the order the tags appear in the
        1405 * document so in the tree <b><i>text</i></b> the B node is considered to be
        1406 * before the I node.
        1407 *
        1408 * @param {Node} node1 The first node to compare.
        1409 * @param {Node} node2 The second node to compare.
        1410 * @return {number} 0 if the nodes are the same node, a negative number if node1
        1411 * is before node2, and a positive number if node2 is before node1.
        1412 */
        1413goog.dom.compareNodeOrder = function(node1, node2) {
        1414 // Fall out quickly for equality.
        1415 if (node1 == node2) {
        1416 return 0;
        1417 }
        1418
        1419 // Use compareDocumentPosition where available
        1420 if (node1.compareDocumentPosition) {
        1421 // 4 is the bitmask for FOLLOWS.
        1422 return node1.compareDocumentPosition(node2) & 2 ? 1 : -1;
        1423 }
        1424
        1425 // Special case for document nodes on IE 7 and 8.
        1426 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
        1427 if (node1.nodeType == goog.dom.NodeType.DOCUMENT) {
        1428 return -1;
        1429 }
        1430 if (node2.nodeType == goog.dom.NodeType.DOCUMENT) {
        1431 return 1;
        1432 }
        1433 }
        1434
        1435 // Process in IE using sourceIndex - we check to see if the first node has
        1436 // a source index or if its parent has one.
        1437 if ('sourceIndex' in node1 ||
        1438 (node1.parentNode && 'sourceIndex' in node1.parentNode)) {
        1439 var isElement1 = node1.nodeType == goog.dom.NodeType.ELEMENT;
        1440 var isElement2 = node2.nodeType == goog.dom.NodeType.ELEMENT;
        1441
        1442 if (isElement1 && isElement2) {
        1443 return node1.sourceIndex - node2.sourceIndex;
        1444 } else {
        1445 var parent1 = node1.parentNode;
        1446 var parent2 = node2.parentNode;
        1447
        1448 if (parent1 == parent2) {
        1449 return goog.dom.compareSiblingOrder_(node1, node2);
        1450 }
        1451
        1452 if (!isElement1 && goog.dom.contains(parent1, node2)) {
        1453 return -1 * goog.dom.compareParentsDescendantNodeIe_(node1, node2);
        1454 }
        1455
        1456
        1457 if (!isElement2 && goog.dom.contains(parent2, node1)) {
        1458 return goog.dom.compareParentsDescendantNodeIe_(node2, node1);
        1459 }
        1460
        1461 return (isElement1 ? node1.sourceIndex : parent1.sourceIndex) -
        1462 (isElement2 ? node2.sourceIndex : parent2.sourceIndex);
        1463 }
        1464 }
        1465
        1466 // For Safari, we compare ranges.
        1467 var doc = goog.dom.getOwnerDocument(node1);
        1468
        1469 var range1, range2;
        1470 range1 = doc.createRange();
        1471 range1.selectNode(node1);
        1472 range1.collapse(true);
        1473
        1474 range2 = doc.createRange();
        1475 range2.selectNode(node2);
        1476 range2.collapse(true);
        1477
        1478 return range1.compareBoundaryPoints(goog.global['Range'].START_TO_END,
        1479 range2);
        1480};
        1481
        1482
        1483/**
        1484 * Utility function to compare the position of two nodes, when
        1485 * {@code textNode}'s parent is an ancestor of {@code node}. If this entry
        1486 * condition is not met, this function will attempt to reference a null object.
        1487 * @param {!Node} textNode The textNode to compare.
        1488 * @param {Node} node The node to compare.
        1489 * @return {number} -1 if node is before textNode, +1 otherwise.
        1490 * @private
        1491 */
        1492goog.dom.compareParentsDescendantNodeIe_ = function(textNode, node) {
        1493 var parent = textNode.parentNode;
        1494 if (parent == node) {
        1495 // If textNode is a child of node, then node comes first.
        1496 return -1;
        1497 }
        1498 var sibling = node;
        1499 while (sibling.parentNode != parent) {
        1500 sibling = sibling.parentNode;
        1501 }
        1502 return goog.dom.compareSiblingOrder_(sibling, textNode);
        1503};
        1504
        1505
        1506/**
        1507 * Utility function to compare the position of two nodes known to be non-equal
        1508 * siblings.
        1509 * @param {Node} node1 The first node to compare.
        1510 * @param {!Node} node2 The second node to compare.
        1511 * @return {number} -1 if node1 is before node2, +1 otherwise.
        1512 * @private
        1513 */
        1514goog.dom.compareSiblingOrder_ = function(node1, node2) {
        1515 var s = node2;
        1516 while ((s = s.previousSibling)) {
        1517 if (s == node1) {
        1518 // We just found node1 before node2.
        1519 return -1;
        1520 }
        1521 }
        1522
        1523 // Since we didn't find it, node1 must be after node2.
        1524 return 1;
        1525};
        1526
        1527
        1528/**
        1529 * Find the deepest common ancestor of the given nodes.
        1530 * @param {...Node} var_args The nodes to find a common ancestor of.
        1531 * @return {Node} The common ancestor of the nodes, or null if there is none.
        1532 * null will only be returned if two or more of the nodes are from different
        1533 * documents.
        1534 */
        1535goog.dom.findCommonAncestor = function(var_args) {
        1536 var i, count = arguments.length;
        1537 if (!count) {
        1538 return null;
        1539 } else if (count == 1) {
        1540 return arguments[0];
        1541 }
        1542
        1543 var paths = [];
        1544 var minLength = Infinity;
        1545 for (i = 0; i < count; i++) {
        1546 // Compute the list of ancestors.
        1547 var ancestors = [];
        1548 var node = arguments[i];
        1549 while (node) {
        1550 ancestors.unshift(node);
        1551 node = node.parentNode;
        1552 }
        1553
        1554 // Save the list for comparison.
        1555 paths.push(ancestors);
        1556 minLength = Math.min(minLength, ancestors.length);
        1557 }
        1558 var output = null;
        1559 for (i = 0; i < minLength; i++) {
        1560 var first = paths[0][i];
        1561 for (var j = 1; j < count; j++) {
        1562 if (first != paths[j][i]) {
        1563 return output;
        1564 }
        1565 }
        1566 output = first;
        1567 }
        1568 return output;
        1569};
        1570
        1571
        1572/**
        1573 * Returns the owner document for a node.
        1574 * @param {Node|Window} node The node to get the document for.
        1575 * @return {!Document} The document owning the node.
        1576 */
        1577goog.dom.getOwnerDocument = function(node) {
        1578 // TODO(nnaze): Update param signature to be non-nullable.
        1579 goog.asserts.assert(node, 'Node cannot be null or undefined.');
        1580 return /** @type {!Document} */ (
        1581 node.nodeType == goog.dom.NodeType.DOCUMENT ? node :
        1582 node.ownerDocument || node.document);
        1583};
        1584
        1585
        1586/**
        1587 * Cross-browser function for getting the document element of a frame or iframe.
        1588 * @param {Element} frame Frame element.
        1589 * @return {!Document} The frame content document.
        1590 */
        1591goog.dom.getFrameContentDocument = function(frame) {
        1592 var doc = frame.contentDocument || frame.contentWindow.document;
        1593 return doc;
        1594};
        1595
        1596
        1597/**
        1598 * Cross-browser function for getting the window of a frame or iframe.
        1599 * @param {Element} frame Frame element.
        1600 * @return {Window} The window associated with the given frame.
        1601 */
        1602goog.dom.getFrameContentWindow = function(frame) {
        1603 return frame.contentWindow ||
        1604 goog.dom.getWindow(goog.dom.getFrameContentDocument(frame));
        1605};
        1606
        1607
        1608/**
        1609 * Sets the text content of a node, with cross-browser support.
        1610 * @param {Node} node The node to change the text content of.
        1611 * @param {string|number} text The value that should replace the node's content.
        1612 */
        1613goog.dom.setTextContent = function(node, text) {
        1614 goog.asserts.assert(node != null,
        1615 'goog.dom.setTextContent expects a non-null value for node');
        1616
        1617 if ('textContent' in node) {
        1618 node.textContent = text;
        1619 } else if (node.nodeType == goog.dom.NodeType.TEXT) {
        1620 node.data = text;
        1621 } else if (node.firstChild &&
        1622 node.firstChild.nodeType == goog.dom.NodeType.TEXT) {
        1623 // If the first child is a text node we just change its data and remove the
        1624 // rest of the children.
        1625 while (node.lastChild != node.firstChild) {
        1626 node.removeChild(node.lastChild);
        1627 }
        1628 node.firstChild.data = text;
        1629 } else {
        1630 goog.dom.removeChildren(node);
        1631 var doc = goog.dom.getOwnerDocument(node);
        1632 node.appendChild(doc.createTextNode(String(text)));
        1633 }
        1634};
        1635
        1636
        1637/**
        1638 * Gets the outerHTML of a node, which islike innerHTML, except that it
        1639 * actually contains the HTML of the node itself.
        1640 * @param {Element} element The element to get the HTML of.
        1641 * @return {string} The outerHTML of the given element.
        1642 */
        1643goog.dom.getOuterHtml = function(element) {
        1644 // IE, Opera and WebKit all have outerHTML.
        1645 if ('outerHTML' in element) {
        1646 return element.outerHTML;
        1647 } else {
        1648 var doc = goog.dom.getOwnerDocument(element);
        1649 var div = doc.createElement(goog.dom.TagName.DIV);
        1650 div.appendChild(element.cloneNode(true));
        1651 return div.innerHTML;
        1652 }
        1653};
        1654
        1655
        1656/**
        1657 * Finds the first descendant node that matches the filter function, using
        1658 * a depth first search. This function offers the most general purpose way
        1659 * of finding a matching element. You may also wish to consider
        1660 * {@code goog.dom.query} which can express many matching criteria using
        1661 * CSS selector expressions. These expressions often result in a more
        1662 * compact representation of the desired result.
        1663 * @see goog.dom.query
        1664 *
        1665 * @param {Node} root The root of the tree to search.
        1666 * @param {function(Node) : boolean} p The filter function.
        1667 * @return {Node|undefined} The found node or undefined if none is found.
        1668 */
        1669goog.dom.findNode = function(root, p) {
        1670 var rv = [];
        1671 var found = goog.dom.findNodes_(root, p, rv, true);
        1672 return found ? rv[0] : undefined;
        1673};
        1674
        1675
        1676/**
        1677 * Finds all the descendant nodes that match the filter function, using a
        1678 * a depth first search. This function offers the most general-purpose way
        1679 * of finding a set of matching elements. You may also wish to consider
        1680 * {@code goog.dom.query} which can express many matching criteria using
        1681 * CSS selector expressions. These expressions often result in a more
        1682 * compact representation of the desired result.
        1683
        1684 * @param {Node} root The root of the tree to search.
        1685 * @param {function(Node) : boolean} p The filter function.
        1686 * @return {!Array<!Node>} The found nodes or an empty array if none are found.
        1687 */
        1688goog.dom.findNodes = function(root, p) {
        1689 var rv = [];
        1690 goog.dom.findNodes_(root, p, rv, false);
        1691 return rv;
        1692};
        1693
        1694
        1695/**
        1696 * Finds the first or all the descendant nodes that match the filter function,
        1697 * using a depth first search.
        1698 * @param {Node} root The root of the tree to search.
        1699 * @param {function(Node) : boolean} p The filter function.
        1700 * @param {!Array<!Node>} rv The found nodes are added to this array.
        1701 * @param {boolean} findOne If true we exit after the first found node.
        1702 * @return {boolean} Whether the search is complete or not. True in case findOne
        1703 * is true and the node is found. False otherwise.
        1704 * @private
        1705 */
        1706goog.dom.findNodes_ = function(root, p, rv, findOne) {
        1707 if (root != null) {
        1708 var child = root.firstChild;
        1709 while (child) {
        1710 if (p(child)) {
        1711 rv.push(child);
        1712 if (findOne) {
        1713 return true;
        1714 }
        1715 }
        1716 if (goog.dom.findNodes_(child, p, rv, findOne)) {
        1717 return true;
        1718 }
        1719 child = child.nextSibling;
        1720 }
        1721 }
        1722 return false;
        1723};
        1724
        1725
        1726/**
        1727 * Map of tags whose content to ignore when calculating text length.
        1728 * @private {!Object<string, number>}
        1729 * @const
        1730 */
        1731goog.dom.TAGS_TO_IGNORE_ = {
        1732 'SCRIPT': 1,
        1733 'STYLE': 1,
        1734 'HEAD': 1,
        1735 'IFRAME': 1,
        1736 'OBJECT': 1
        1737};
        1738
        1739
        1740/**
        1741 * Map of tags which have predefined values with regard to whitespace.
        1742 * @private {!Object<string, string>}
        1743 * @const
        1744 */
        1745goog.dom.PREDEFINED_TAG_VALUES_ = {'IMG': ' ', 'BR': '\n'};
        1746
        1747
        1748/**
        1749 * Returns true if the element has a tab index that allows it to receive
        1750 * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
        1751 * natively support keyboard focus, even if they have no tab index.
        1752 * @param {!Element} element Element to check.
        1753 * @return {boolean} Whether the element has a tab index that allows keyboard
        1754 * focus.
        1755 * @see http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
        1756 */
        1757goog.dom.isFocusableTabIndex = function(element) {
        1758 return goog.dom.hasSpecifiedTabIndex_(element) &&
        1759 goog.dom.isTabIndexFocusable_(element);
        1760};
        1761
        1762
        1763/**
        1764 * Enables or disables keyboard focus support on the element via its tab index.
        1765 * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
        1766 * (or elements that natively support keyboard focus, like form elements) can
        1767 * receive keyboard focus. See http://go/tabindex for more info.
        1768 * @param {Element} element Element whose tab index is to be changed.
        1769 * @param {boolean} enable Whether to set or remove a tab index on the element
        1770 * that supports keyboard focus.
        1771 */
        1772goog.dom.setFocusableTabIndex = function(element, enable) {
        1773 if (enable) {
        1774 element.tabIndex = 0;
        1775 } else {
        1776 // Set tabIndex to -1 first, then remove it. This is a workaround for
        1777 // Safari (confirmed in version 4 on Windows). When removing the attribute
        1778 // without setting it to -1 first, the element remains keyboard focusable
        1779 // despite not having a tabIndex attribute anymore.
        1780 element.tabIndex = -1;
        1781 element.removeAttribute('tabIndex'); // Must be camelCase!
        1782 }
        1783};
        1784
        1785
        1786/**
        1787 * Returns true if the element can be focused, i.e. it has a tab index that
        1788 * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
        1789 * that natively supports keyboard focus.
        1790 * @param {!Element} element Element to check.
        1791 * @return {boolean} Whether the element allows keyboard focus.
        1792 */
        1793goog.dom.isFocusable = function(element) {
        1794 var focusable;
        1795 // Some elements can have unspecified tab index and still receive focus.
        1796 if (goog.dom.nativelySupportsFocus_(element)) {
        1797 // Make sure the element is not disabled ...
        1798 focusable = !element.disabled &&
        1799 // ... and if a tab index is specified, it allows focus.
        1800 (!goog.dom.hasSpecifiedTabIndex_(element) ||
        1801 goog.dom.isTabIndexFocusable_(element));
        1802 } else {
        1803 focusable = goog.dom.isFocusableTabIndex(element);
        1804 }
        1805
        1806 // IE requires elements to be visible in order to focus them.
        1807 return focusable && goog.userAgent.IE ?
        1808 goog.dom.hasNonZeroBoundingRect_(element) : focusable;
        1809};
        1810
        1811
        1812/**
        1813 * Returns true if the element has a specified tab index.
        1814 * @param {!Element} element Element to check.
        1815 * @return {boolean} Whether the element has a specified tab index.
        1816 * @private
        1817 */
        1818goog.dom.hasSpecifiedTabIndex_ = function(element) {
        1819 // IE returns 0 for an unset tabIndex, so we must use getAttributeNode(),
        1820 // which returns an object with a 'specified' property if tabIndex is
        1821 // specified. This works on other browsers, too.
        1822 var attrNode = element.getAttributeNode('tabindex'); // Must be lowercase!
        1823 return goog.isDefAndNotNull(attrNode) && attrNode.specified;
        1824};
        1825
        1826
        1827/**
        1828 * Returns true if the element's tab index allows the element to be focused.
        1829 * @param {!Element} element Element to check.
        1830 * @return {boolean} Whether the element's tab index allows focus.
        1831 * @private
        1832 */
        1833goog.dom.isTabIndexFocusable_ = function(element) {
        1834 var index = element.tabIndex;
        1835 // NOTE: IE9 puts tabIndex in 16-bit int, e.g. -2 is 65534.
        1836 return goog.isNumber(index) && index >= 0 && index < 32768;
        1837};
        1838
        1839
        1840/**
        1841 * Returns true if the element is focusable even when tabIndex is not set.
        1842 * @param {!Element} element Element to check.
        1843 * @return {boolean} Whether the element natively supports focus.
        1844 * @private
        1845 */
        1846goog.dom.nativelySupportsFocus_ = function(element) {
        1847 return element.tagName == goog.dom.TagName.A ||
        1848 element.tagName == goog.dom.TagName.INPUT ||
        1849 element.tagName == goog.dom.TagName.TEXTAREA ||
        1850 element.tagName == goog.dom.TagName.SELECT ||
        1851 element.tagName == goog.dom.TagName.BUTTON;
        1852};
        1853
        1854
        1855/**
        1856 * Returns true if the element has a bounding rectangle that would be visible
        1857 * (i.e. its width and height are greater than zero).
        1858 * @param {!Element} element Element to check.
        1859 * @return {boolean} Whether the element has a non-zero bounding rectangle.
        1860 * @private
        1861 */
        1862goog.dom.hasNonZeroBoundingRect_ = function(element) {
        1863 var rect = goog.isFunction(element['getBoundingClientRect']) ?
        1864 element.getBoundingClientRect() :
        1865 {'height': element.offsetHeight, 'width': element.offsetWidth};
        1866 return goog.isDefAndNotNull(rect) && rect.height > 0 && rect.width > 0;
        1867};
        1868
        1869
        1870/**
        1871 * Returns the text content of the current node, without markup and invisible
        1872 * symbols. New lines are stripped and whitespace is collapsed,
        1873 * such that each character would be visible.
        1874 *
        1875 * In browsers that support it, innerText is used. Other browsers attempt to
        1876 * simulate it via node traversal. Line breaks are canonicalized in IE.
        1877 *
        1878 * @param {Node} node The node from which we are getting content.
        1879 * @return {string} The text content.
        1880 */
        1881goog.dom.getTextContent = function(node) {
        1882 var textContent;
        1883 // Note(arv): IE9, Opera, and Safari 3 support innerText but they include
        1884 // text nodes in script tags. So we revert to use a user agent test here.
        1885 if (goog.dom.BrowserFeature.CAN_USE_INNER_TEXT && ('innerText' in node)) {
        1886 textContent = goog.string.canonicalizeNewlines(node.innerText);
        1887 // Unfortunately .innerText() returns text with &shy; symbols
        1888 // We need to filter it out and then remove duplicate whitespaces
        1889 } else {
        1890 var buf = [];
        1891 goog.dom.getTextContent_(node, buf, true);
        1892 textContent = buf.join('');
        1893 }
        1894
        1895 // Strip &shy; entities. goog.format.insertWordBreaks inserts them in Opera.
        1896 textContent = textContent.replace(/ \xAD /g, ' ').replace(/\xAD/g, '');
        1897 // Strip &#8203; entities. goog.format.insertWordBreaks inserts them in IE8.
        1898 textContent = textContent.replace(/\u200B/g, '');
        1899
        1900 // Skip this replacement on old browsers with working innerText, which
        1901 // automatically turns &nbsp; into ' ' and / +/ into ' ' when reading
        1902 // innerText.
        1903 if (!goog.dom.BrowserFeature.CAN_USE_INNER_TEXT) {
        1904 textContent = textContent.replace(/ +/g, ' ');
        1905 }
        1906 if (textContent != ' ') {
        1907 textContent = textContent.replace(/^\s*/, '');
        1908 }
        1909
        1910 return textContent;
        1911};
        1912
        1913
        1914/**
        1915 * Returns the text content of the current node, without markup.
        1916 *
        1917 * Unlike {@code getTextContent} this method does not collapse whitespaces
        1918 * or normalize lines breaks.
        1919 *
        1920 * @param {Node} node The node from which we are getting content.
        1921 * @return {string} The raw text content.
        1922 */
        1923goog.dom.getRawTextContent = function(node) {
        1924 var buf = [];
        1925 goog.dom.getTextContent_(node, buf, false);
        1926
        1927 return buf.join('');
        1928};
        1929
        1930
        1931/**
        1932 * Recursive support function for text content retrieval.
        1933 *
        1934 * @param {Node} node The node from which we are getting content.
        1935 * @param {Array<string>} buf string buffer.
        1936 * @param {boolean} normalizeWhitespace Whether to normalize whitespace.
        1937 * @private
        1938 */
        1939goog.dom.getTextContent_ = function(node, buf, normalizeWhitespace) {
        1940 if (node.nodeName in goog.dom.TAGS_TO_IGNORE_) {
        1941 // ignore certain tags
        1942 } else if (node.nodeType == goog.dom.NodeType.TEXT) {
        1943 if (normalizeWhitespace) {
        1944 buf.push(String(node.nodeValue).replace(/(\r\n|\r|\n)/g, ''));
        1945 } else {
        1946 buf.push(node.nodeValue);
        1947 }
        1948 } else if (node.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
        1949 buf.push(goog.dom.PREDEFINED_TAG_VALUES_[node.nodeName]);
        1950 } else {
        1951 var child = node.firstChild;
        1952 while (child) {
        1953 goog.dom.getTextContent_(child, buf, normalizeWhitespace);
        1954 child = child.nextSibling;
        1955 }
        1956 }
        1957};
        1958
        1959
        1960/**
        1961 * Returns the text length of the text contained in a node, without markup. This
        1962 * is equivalent to the selection length if the node was selected, or the number
        1963 * of cursor movements to traverse the node. Images & BRs take one space. New
        1964 * lines are ignored.
        1965 *
        1966 * @param {Node} node The node whose text content length is being calculated.
        1967 * @return {number} The length of {@code node}'s text content.
        1968 */
        1969goog.dom.getNodeTextLength = function(node) {
        1970 return goog.dom.getTextContent(node).length;
        1971};
        1972
        1973
        1974/**
        1975 * Returns the text offset of a node relative to one of its ancestors. The text
        1976 * length is the same as the length calculated by goog.dom.getNodeTextLength.
        1977 *
        1978 * @param {Node} node The node whose offset is being calculated.
        1979 * @param {Node=} opt_offsetParent The node relative to which the offset will
        1980 * be calculated. Defaults to the node's owner document's body.
        1981 * @return {number} The text offset.
        1982 */
        1983goog.dom.getNodeTextOffset = function(node, opt_offsetParent) {
        1984 var root = opt_offsetParent || goog.dom.getOwnerDocument(node).body;
        1985 var buf = [];
        1986 while (node && node != root) {
        1987 var cur = node;
        1988 while ((cur = cur.previousSibling)) {
        1989 buf.unshift(goog.dom.getTextContent(cur));
        1990 }
        1991 node = node.parentNode;
        1992 }
        1993 // Trim left to deal with FF cases when there might be line breaks and empty
        1994 // nodes at the front of the text
        1995 return goog.string.trimLeft(buf.join('')).replace(/ +/g, ' ').length;
        1996};
        1997
        1998
        1999/**
        2000 * Returns the node at a given offset in a parent node. If an object is
        2001 * provided for the optional third parameter, the node and the remainder of the
        2002 * offset will stored as properties of this object.
        2003 * @param {Node} parent The parent node.
        2004 * @param {number} offset The offset into the parent node.
        2005 * @param {Object=} opt_result Object to be used to store the return value. The
        2006 * return value will be stored in the form {node: Node, remainder: number}
        2007 * if this object is provided.
        2008 * @return {Node} The node at the given offset.
        2009 */
        2010goog.dom.getNodeAtOffset = function(parent, offset, opt_result) {
        2011 var stack = [parent], pos = 0, cur = null;
        2012 while (stack.length > 0 && pos < offset) {
        2013 cur = stack.pop();
        2014 if (cur.nodeName in goog.dom.TAGS_TO_IGNORE_) {
        2015 // ignore certain tags
        2016 } else if (cur.nodeType == goog.dom.NodeType.TEXT) {
        2017 var text = cur.nodeValue.replace(/(\r\n|\r|\n)/g, '').replace(/ +/g, ' ');
        2018 pos += text.length;
        2019 } else if (cur.nodeName in goog.dom.PREDEFINED_TAG_VALUES_) {
        2020 pos += goog.dom.PREDEFINED_TAG_VALUES_[cur.nodeName].length;
        2021 } else {
        2022 for (var i = cur.childNodes.length - 1; i >= 0; i--) {
        2023 stack.push(cur.childNodes[i]);
        2024 }
        2025 }
        2026 }
        2027 if (goog.isObject(opt_result)) {
        2028 opt_result.remainder = cur ? cur.nodeValue.length + offset - pos - 1 : 0;
        2029 opt_result.node = cur;
        2030 }
        2031
        2032 return cur;
        2033};
        2034
        2035
        2036/**
        2037 * Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
        2038 * the object must have a numeric length property and an item function (which
        2039 * has type 'string' on IE for some reason).
        2040 * @param {Object} val Object to test.
        2041 * @return {boolean} Whether the object is a NodeList.
        2042 */
        2043goog.dom.isNodeList = function(val) {
        2044 // TODO(attila): Now the isNodeList is part of goog.dom we can use
        2045 // goog.userAgent to make this simpler.
        2046 // A NodeList must have a length property of type 'number' on all platforms.
        2047 if (val && typeof val.length == 'number') {
        2048 // A NodeList is an object everywhere except Safari, where it's a function.
        2049 if (goog.isObject(val)) {
        2050 // A NodeList must have an item function (on non-IE platforms) or an item
        2051 // property of type 'string' (on IE).
        2052 return typeof val.item == 'function' || typeof val.item == 'string';
        2053 } else if (goog.isFunction(val)) {
        2054 // On Safari, a NodeList is a function with an item property that is also
        2055 // a function.
        2056 return typeof val.item == 'function';
        2057 }
        2058 }
        2059
        2060 // Not a NodeList.
        2061 return false;
        2062};
        2063
        2064
        2065/**
        2066 * Walks up the DOM hierarchy returning the first ancestor that has the passed
        2067 * tag name and/or class name. If the passed element matches the specified
        2068 * criteria, the element itself is returned.
        2069 * @param {Node} element The DOM node to start with.
        2070 * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
        2071 * null/undefined to match only based on class name).
        2072 * @param {?string=} opt_class The class name to match (or null/undefined to
        2073 * match only based on tag name).
        2074 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2075 * dom.
        2076 * @return {Element} The first ancestor that matches the passed criteria, or
        2077 * null if no match is found.
        2078 */
        2079goog.dom.getAncestorByTagNameAndClass = function(element, opt_tag, opt_class,
        2080 opt_maxSearchSteps) {
        2081 if (!opt_tag && !opt_class) {
        2082 return null;
        2083 }
        2084 var tagName = opt_tag ? opt_tag.toUpperCase() : null;
        2085 return /** @type {Element} */ (goog.dom.getAncestor(element,
        2086 function(node) {
        2087 return (!tagName || node.nodeName == tagName) &&
        2088 (!opt_class || goog.isString(node.className) &&
        2089 goog.array.contains(node.className.split(/\s+/), opt_class));
        2090 }, true, opt_maxSearchSteps));
        2091};
        2092
        2093
        2094/**
        2095 * Walks up the DOM hierarchy returning the first ancestor that has the passed
        2096 * class name. If the passed element matches the specified criteria, the
        2097 * element itself is returned.
        2098 * @param {Node} element The DOM node to start with.
        2099 * @param {string} className The class name to match.
        2100 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2101 * dom.
        2102 * @return {Element} The first ancestor that matches the passed criteria, or
        2103 * null if none match.
        2104 */
        2105goog.dom.getAncestorByClass = function(element, className, opt_maxSearchSteps) {
        2106 return goog.dom.getAncestorByTagNameAndClass(element, null, className,
        2107 opt_maxSearchSteps);
        2108};
        2109
        2110
        2111/**
        2112 * Walks up the DOM hierarchy returning the first ancestor that passes the
        2113 * matcher function.
        2114 * @param {Node} element The DOM node to start with.
        2115 * @param {function(Node) : boolean} matcher A function that returns true if the
        2116 * passed node matches the desired criteria.
        2117 * @param {boolean=} opt_includeNode If true, the node itself is included in
        2118 * the search (the first call to the matcher will pass startElement as
        2119 * the node to test).
        2120 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2121 * dom.
        2122 * @return {Node} DOM node that matched the matcher, or null if there was
        2123 * no match.
        2124 */
        2125goog.dom.getAncestor = function(
        2126 element, matcher, opt_includeNode, opt_maxSearchSteps) {
        2127 if (!opt_includeNode) {
        2128 element = element.parentNode;
        2129 }
        2130 var ignoreSearchSteps = opt_maxSearchSteps == null;
        2131 var steps = 0;
        2132 while (element && (ignoreSearchSteps || steps <= opt_maxSearchSteps)) {
        2133 goog.asserts.assert(element.name != 'parentNode');
        2134 if (matcher(element)) {
        2135 return element;
        2136 }
        2137 element = element.parentNode;
        2138 steps++;
        2139 }
        2140 // Reached the root of the DOM without a match
        2141 return null;
        2142};
        2143
        2144
        2145/**
        2146 * Determines the active element in the given document.
        2147 * @param {Document} doc The document to look in.
        2148 * @return {Element} The active element.
        2149 */
        2150goog.dom.getActiveElement = function(doc) {
        2151 try {
        2152 return doc && doc.activeElement;
        2153 } catch (e) {
        2154 // NOTE(nicksantos): Sometimes, evaluating document.activeElement in IE
        2155 // throws an exception. I'm not 100% sure why, but I suspect it chokes
        2156 // on document.activeElement if the activeElement has been recently
        2157 // removed from the DOM by a JS operation.
        2158 //
        2159 // We assume that an exception here simply means
        2160 // "there is no active element."
        2161 }
        2162
        2163 return null;
        2164};
        2165
        2166
        2167/**
        2168 * Gives the current devicePixelRatio.
        2169 *
        2170 * By default, this is the value of window.devicePixelRatio (which should be
        2171 * preferred if present).
        2172 *
        2173 * If window.devicePixelRatio is not present, the ratio is calculated with
        2174 * window.matchMedia, if present. Otherwise, gives 1.0.
        2175 *
        2176 * Some browsers (including Chrome) consider the browser zoom level in the pixel
        2177 * ratio, so the value may change across multiple calls.
        2178 *
        2179 * @return {number} The number of actual pixels per virtual pixel.
        2180 */
        2181goog.dom.getPixelRatio = function() {
        2182 var win = goog.dom.getWindow();
        2183
        2184 // devicePixelRatio does not work on Mobile firefox.
        2185 // TODO(user): Enable this check on a known working mobile Gecko version.
        2186 // Filed a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=896804
        2187 var isFirefoxMobile = goog.userAgent.GECKO && goog.userAgent.MOBILE;
        2188
        2189 if (goog.isDef(win.devicePixelRatio) && !isFirefoxMobile) {
        2190 return win.devicePixelRatio;
        2191 } else if (win.matchMedia) {
        2192 return goog.dom.matchesPixelRatio_(.75) ||
        2193 goog.dom.matchesPixelRatio_(1.5) ||
        2194 goog.dom.matchesPixelRatio_(2) ||
        2195 goog.dom.matchesPixelRatio_(3) || 1;
        2196 }
        2197 return 1;
        2198};
        2199
        2200
        2201/**
        2202 * Calculates a mediaQuery to check if the current device supports the
        2203 * given actual to virtual pixel ratio.
        2204 * @param {number} pixelRatio The ratio of actual pixels to virtual pixels.
        2205 * @return {number} pixelRatio if applicable, otherwise 0.
        2206 * @private
        2207 */
        2208goog.dom.matchesPixelRatio_ = function(pixelRatio) {
        2209 var win = goog.dom.getWindow();
        2210 var query = ('(-webkit-min-device-pixel-ratio: ' + pixelRatio + '),' +
        2211 '(min--moz-device-pixel-ratio: ' + pixelRatio + '),' +
        2212 '(min-resolution: ' + pixelRatio + 'dppx)');
        2213 return win.matchMedia(query).matches ? pixelRatio : 0;
        2214};
        2215
        2216
        2217
        2218/**
        2219 * Create an instance of a DOM helper with a new document object.
        2220 * @param {Document=} opt_document Document object to associate with this
        2221 * DOM helper.
        2222 * @constructor
        2223 */
        2224goog.dom.DomHelper = function(opt_document) {
        2225 /**
        2226 * Reference to the document object to use
        2227 * @type {!Document}
        2228 * @private
        2229 */
        2230 this.document_ = opt_document || goog.global.document || document;
        2231};
        2232
        2233
        2234/**
        2235 * Gets the dom helper object for the document where the element resides.
        2236 * @param {Node=} opt_node If present, gets the DomHelper for this node.
        2237 * @return {!goog.dom.DomHelper} The DomHelper.
        2238 */
        2239goog.dom.DomHelper.prototype.getDomHelper = goog.dom.getDomHelper;
        2240
        2241
        2242/**
        2243 * Sets the document object.
        2244 * @param {!Document} document Document object.
        2245 */
        2246goog.dom.DomHelper.prototype.setDocument = function(document) {
        2247 this.document_ = document;
        2248};
        2249
        2250
        2251/**
        2252 * Gets the document object being used by the dom library.
        2253 * @return {!Document} Document object.
        2254 */
        2255goog.dom.DomHelper.prototype.getDocument = function() {
        2256 return this.document_;
        2257};
        2258
        2259
        2260/**
        2261 * Alias for {@code getElementById}. If a DOM node is passed in then we just
        2262 * return that.
        2263 * @param {string|Element} element Element ID or a DOM node.
        2264 * @return {Element} The element with the given ID, or the node passed in.
        2265 */
        2266goog.dom.DomHelper.prototype.getElement = function(element) {
        2267 return goog.dom.getElementHelper_(this.document_, element);
        2268};
        2269
        2270
        2271/**
        2272 * Gets an element by id, asserting that the element is found.
        2273 *
        2274 * This is used when an element is expected to exist, and should fail with
        2275 * an assertion error if it does not (if assertions are enabled).
        2276 *
        2277 * @param {string} id Element ID.
        2278 * @return {!Element} The element with the given ID, if it exists.
        2279 */
        2280goog.dom.DomHelper.prototype.getRequiredElement = function(id) {
        2281 return goog.dom.getRequiredElementHelper_(this.document_, id);
        2282};
        2283
        2284
        2285/**
        2286 * Alias for {@code getElement}.
        2287 * @param {string|Element} element Element ID or a DOM node.
        2288 * @return {Element} The element with the given ID, or the node passed in.
        2289 * @deprecated Use {@link goog.dom.DomHelper.prototype.getElement} instead.
        2290 */
        2291goog.dom.DomHelper.prototype.$ = goog.dom.DomHelper.prototype.getElement;
        2292
        2293
        2294/**
        2295 * Looks up elements by both tag and class name, using browser native functions
        2296 * ({@code querySelectorAll}, {@code getElementsByTagName} or
        2297 * {@code getElementsByClassName}) where possible. The returned array is a live
        2298 * NodeList or a static list depending on the code path taken.
        2299 *
        2300 * @see goog.dom.query
        2301 *
        2302 * @param {?string=} opt_tag Element tag name or * for all tags.
        2303 * @param {?string=} opt_class Optional class name.
        2304 * @param {(Document|Element)=} opt_el Optional element to look in.
        2305 * @return { {length: number} } Array-like list of elements (only a length
        2306 * property and numerical indices are guaranteed to exist).
        2307 */
        2308goog.dom.DomHelper.prototype.getElementsByTagNameAndClass = function(opt_tag,
        2309 opt_class,
        2310 opt_el) {
        2311 return goog.dom.getElementsByTagNameAndClass_(this.document_, opt_tag,
        2312 opt_class, opt_el);
        2313};
        2314
        2315
        2316/**
        2317 * Returns an array of all the elements with the provided className.
        2318 * @see {goog.dom.query}
        2319 * @param {string} className the name of the class to look for.
        2320 * @param {Element|Document=} opt_el Optional element to look in.
        2321 * @return { {length: number} } The items found with the class name provided.
        2322 */
        2323goog.dom.DomHelper.prototype.getElementsByClass = function(className, opt_el) {
        2324 var doc = opt_el || this.document_;
        2325 return goog.dom.getElementsByClass(className, doc);
        2326};
        2327
        2328
        2329/**
        2330 * Returns the first element we find matching the provided class name.
        2331 * @see {goog.dom.query}
        2332 * @param {string} className the name of the class to look for.
        2333 * @param {(Element|Document)=} opt_el Optional element to look in.
        2334 * @return {Element} The first item found with the class name provided.
        2335 */
        2336goog.dom.DomHelper.prototype.getElementByClass = function(className, opt_el) {
        2337 var doc = opt_el || this.document_;
        2338 return goog.dom.getElementByClass(className, doc);
        2339};
        2340
        2341
        2342/**
        2343 * Ensures an element with the given className exists, and then returns the
        2344 * first element with the provided className.
        2345 * @see {goog.dom.query}
        2346 * @param {string} className the name of the class to look for.
        2347 * @param {(!Element|!Document)=} opt_root Optional element or document to look
        2348 * in.
        2349 * @return {!Element} The first item found with the class name provided.
        2350 * @throws {goog.asserts.AssertionError} Thrown if no element is found.
        2351 */
        2352goog.dom.DomHelper.prototype.getRequiredElementByClass = function(className,
        2353 opt_root) {
        2354 var root = opt_root || this.document_;
        2355 return goog.dom.getRequiredElementByClass(className, root);
        2356};
        2357
        2358
        2359/**
        2360 * Alias for {@code getElementsByTagNameAndClass}.
        2361 * @deprecated Use DomHelper getElementsByTagNameAndClass.
        2362 * @see goog.dom.query
        2363 *
        2364 * @param {?string=} opt_tag Element tag name.
        2365 * @param {?string=} opt_class Optional class name.
        2366 * @param {Element=} opt_el Optional element to look in.
        2367 * @return { {length: number} } Array-like list of elements (only a length
        2368 * property and numerical indices are guaranteed to exist).
        2369 */
        2370goog.dom.DomHelper.prototype.$$ =
        2371 goog.dom.DomHelper.prototype.getElementsByTagNameAndClass;
        2372
        2373
        2374/**
        2375 * Sets a number of properties on a node.
        2376 * @param {Element} element DOM node to set properties on.
        2377 * @param {Object} properties Hash of property:value pairs.
        2378 */
        2379goog.dom.DomHelper.prototype.setProperties = goog.dom.setProperties;
        2380
        2381
        2382/**
        2383 * Gets the dimensions of the viewport.
        2384 * @param {Window=} opt_window Optional window element to test. Defaults to
        2385 * the window of the Dom Helper.
        2386 * @return {!goog.math.Size} Object with values 'width' and 'height'.
        2387 */
        2388goog.dom.DomHelper.prototype.getViewportSize = function(opt_window) {
        2389 // TODO(arv): This should not take an argument. That breaks the rule of a
        2390 // a DomHelper representing a single frame/window/document.
        2391 return goog.dom.getViewportSize(opt_window || this.getWindow());
        2392};
        2393
        2394
        2395/**
        2396 * Calculates the height of the document.
        2397 *
        2398 * @return {number} The height of the document.
        2399 */
        2400goog.dom.DomHelper.prototype.getDocumentHeight = function() {
        2401 return goog.dom.getDocumentHeight_(this.getWindow());
        2402};
        2403
        2404
        2405/**
        2406 * Typedef for use with goog.dom.createDom and goog.dom.append.
        2407 * @typedef {Object|string|Array|NodeList}
        2408 */
        2409goog.dom.Appendable;
        2410
        2411
        2412/**
        2413 * Returns a dom node with a set of attributes. This function accepts varargs
        2414 * for subsequent nodes to be added. Subsequent nodes will be added to the
        2415 * first node as childNodes.
        2416 *
        2417 * So:
        2418 * <code>createDom('div', null, createDom('p'), createDom('p'));</code>
        2419 * would return a div with two child paragraphs
        2420 *
        2421 * An easy way to move all child nodes of an existing element to a new parent
        2422 * element is:
        2423 * <code>createDom('div', null, oldElement.childNodes);</code>
        2424 * which will remove all child nodes from the old element and add them as
        2425 * child nodes of the new DIV.
        2426 *
        2427 * @param {string} tagName Tag to create.
        2428 * @param {Object|string=} opt_attributes If object, then a map of name-value
        2429 * pairs for attributes. If a string, then this is the className of the new
        2430 * element.
        2431 * @param {...goog.dom.Appendable} var_args Further DOM nodes or
        2432 * strings for text nodes. If one of the var_args is an array or
        2433 * NodeList, its elements will be added as childNodes instead.
        2434 * @return {!Element} Reference to a DOM node.
        2435 */
        2436goog.dom.DomHelper.prototype.createDom = function(tagName,
        2437 opt_attributes,
        2438 var_args) {
        2439 return goog.dom.createDom_(this.document_, arguments);
        2440};
        2441
        2442
        2443/**
        2444 * Alias for {@code createDom}.
        2445 * @param {string} tagName Tag to create.
        2446 * @param {(Object|string)=} opt_attributes If object, then a map of name-value
        2447 * pairs for attributes. If a string, then this is the className of the new
        2448 * element.
        2449 * @param {...goog.dom.Appendable} var_args Further DOM nodes or strings for
        2450 * text nodes. If one of the var_args is an array, its children will be
        2451 * added as childNodes instead.
        2452 * @return {!Element} Reference to a DOM node.
        2453 * @deprecated Use {@link goog.dom.DomHelper.prototype.createDom} instead.
        2454 */
        2455goog.dom.DomHelper.prototype.$dom = goog.dom.DomHelper.prototype.createDom;
        2456
        2457
        2458/**
        2459 * Creates a new element.
        2460 * @param {string} name Tag name.
        2461 * @return {!Element} The new element.
        2462 */
        2463goog.dom.DomHelper.prototype.createElement = function(name) {
        2464 return this.document_.createElement(name);
        2465};
        2466
        2467
        2468/**
        2469 * Creates a new text node.
        2470 * @param {number|string} content Content.
        2471 * @return {!Text} The new text node.
        2472 */
        2473goog.dom.DomHelper.prototype.createTextNode = function(content) {
        2474 return this.document_.createTextNode(String(content));
        2475};
        2476
        2477
        2478/**
        2479 * Create a table.
        2480 * @param {number} rows The number of rows in the table. Must be >= 1.
        2481 * @param {number} columns The number of columns in the table. Must be >= 1.
        2482 * @param {boolean=} opt_fillWithNbsp If true, fills table entries with
        2483 * {@code goog.string.Unicode.NBSP} characters.
        2484 * @return {!HTMLElement} The created table.
        2485 */
        2486goog.dom.DomHelper.prototype.createTable = function(rows, columns,
        2487 opt_fillWithNbsp) {
        2488 return goog.dom.createTable_(this.document_, rows, columns,
        2489 !!opt_fillWithNbsp);
        2490};
        2491
        2492
        2493/**
        2494 * Converts an HTML into a node or a document fragment. A single Node is used if
        2495 * {@code html} only generates a single node. If {@code html} generates multiple
        2496 * nodes then these are put inside a {@code DocumentFragment}.
        2497 * @param {!goog.html.SafeHtml} html The HTML markup to convert.
        2498 * @return {!Node} The resulting node.
        2499 */
        2500goog.dom.DomHelper.prototype.safeHtmlToNode = function(html) {
        2501 return goog.dom.safeHtmlToNode_(this.document_, html);
        2502};
        2503
        2504
        2505/**
        2506 * Converts an HTML string into a node or a document fragment. A single Node
        2507 * is used if the {@code htmlString} only generates a single node. If the
        2508 * {@code htmlString} generates multiple nodes then these are put inside a
        2509 * {@code DocumentFragment}.
        2510 *
        2511 * @param {string} htmlString The HTML string to convert.
        2512 * @return {!Node} The resulting node.
        2513 */
        2514goog.dom.DomHelper.prototype.htmlToDocumentFragment = function(htmlString) {
        2515 return goog.dom.htmlToDocumentFragment_(this.document_, htmlString);
        2516};
        2517
        2518
        2519/**
        2520 * Returns true if the browser is in "CSS1-compatible" (standards-compliant)
        2521 * mode, false otherwise.
        2522 * @return {boolean} True if in CSS1-compatible mode.
        2523 */
        2524goog.dom.DomHelper.prototype.isCss1CompatMode = function() {
        2525 return goog.dom.isCss1CompatMode_(this.document_);
        2526};
        2527
        2528
        2529/**
        2530 * Gets the window object associated with the document.
        2531 * @return {!Window} The window associated with the given document.
        2532 */
        2533goog.dom.DomHelper.prototype.getWindow = function() {
        2534 return goog.dom.getWindow_(this.document_);
        2535};
        2536
        2537
        2538/**
        2539 * Gets the document scroll element.
        2540 * @return {!Element} Scrolling element.
        2541 */
        2542goog.dom.DomHelper.prototype.getDocumentScrollElement = function() {
        2543 return goog.dom.getDocumentScrollElement_(this.document_);
        2544};
        2545
        2546
        2547/**
        2548 * Gets the document scroll distance as a coordinate object.
        2549 * @return {!goog.math.Coordinate} Object with properties 'x' and 'y'.
        2550 */
        2551goog.dom.DomHelper.prototype.getDocumentScroll = function() {
        2552 return goog.dom.getDocumentScroll_(this.document_);
        2553};
        2554
        2555
        2556/**
        2557 * Determines the active element in the given document.
        2558 * @param {Document=} opt_doc The document to look in.
        2559 * @return {Element} The active element.
        2560 */
        2561goog.dom.DomHelper.prototype.getActiveElement = function(opt_doc) {
        2562 return goog.dom.getActiveElement(opt_doc || this.document_);
        2563};
        2564
        2565
        2566/**
        2567 * Appends a child to a node.
        2568 * @param {Node} parent Parent.
        2569 * @param {Node} child Child.
        2570 */
        2571goog.dom.DomHelper.prototype.appendChild = goog.dom.appendChild;
        2572
        2573
        2574/**
        2575 * Appends a node with text or other nodes.
        2576 * @param {!Node} parent The node to append nodes to.
        2577 * @param {...goog.dom.Appendable} var_args The things to append to the node.
        2578 * If this is a Node it is appended as is.
        2579 * If this is a string then a text node is appended.
        2580 * If this is an array like object then fields 0 to length - 1 are appended.
        2581 */
        2582goog.dom.DomHelper.prototype.append = goog.dom.append;
        2583
        2584
        2585/**
        2586 * Determines if the given node can contain children, intended to be used for
        2587 * HTML generation.
        2588 *
        2589 * @param {Node} node The node to check.
        2590 * @return {boolean} Whether the node can contain children.
        2591 */
        2592goog.dom.DomHelper.prototype.canHaveChildren = goog.dom.canHaveChildren;
        2593
        2594
        2595/**
        2596 * Removes all the child nodes on a DOM node.
        2597 * @param {Node} node Node to remove children from.
        2598 */
        2599goog.dom.DomHelper.prototype.removeChildren = goog.dom.removeChildren;
        2600
        2601
        2602/**
        2603 * Inserts a new node before an existing reference node (i.e., as the previous
        2604 * sibling). If the reference node has no parent, then does nothing.
        2605 * @param {Node} newNode Node to insert.
        2606 * @param {Node} refNode Reference node to insert before.
        2607 */
        2608goog.dom.DomHelper.prototype.insertSiblingBefore = goog.dom.insertSiblingBefore;
        2609
        2610
        2611/**
        2612 * Inserts a new node after an existing reference node (i.e., as the next
        2613 * sibling). If the reference node has no parent, then does nothing.
        2614 * @param {Node} newNode Node to insert.
        2615 * @param {Node} refNode Reference node to insert after.
        2616 */
        2617goog.dom.DomHelper.prototype.insertSiblingAfter = goog.dom.insertSiblingAfter;
        2618
        2619
        2620/**
        2621 * Insert a child at a given index. If index is larger than the number of child
        2622 * nodes that the parent currently has, the node is inserted as the last child
        2623 * node.
        2624 * @param {Element} parent The element into which to insert the child.
        2625 * @param {Node} child The element to insert.
        2626 * @param {number} index The index at which to insert the new child node. Must
        2627 * not be negative.
        2628 */
        2629goog.dom.DomHelper.prototype.insertChildAt = goog.dom.insertChildAt;
        2630
        2631
        2632/**
        2633 * Removes a node from its parent.
        2634 * @param {Node} node The node to remove.
        2635 * @return {Node} The node removed if removed; else, null.
        2636 */
        2637goog.dom.DomHelper.prototype.removeNode = goog.dom.removeNode;
        2638
        2639
        2640/**
        2641 * Replaces a node in the DOM tree. Will do nothing if {@code oldNode} has no
        2642 * parent.
        2643 * @param {Node} newNode Node to insert.
        2644 * @param {Node} oldNode Node to replace.
        2645 */
        2646goog.dom.DomHelper.prototype.replaceNode = goog.dom.replaceNode;
        2647
        2648
        2649/**
        2650 * Flattens an element. That is, removes it and replace it with its children.
        2651 * @param {Element} element The element to flatten.
        2652 * @return {Element|undefined} The original element, detached from the document
        2653 * tree, sans children, or undefined if the element was already not in the
        2654 * document.
        2655 */
        2656goog.dom.DomHelper.prototype.flattenElement = goog.dom.flattenElement;
        2657
        2658
        2659/**
        2660 * Returns an array containing just the element children of the given element.
        2661 * @param {Element} element The element whose element children we want.
        2662 * @return {!(Array|NodeList)} An array or array-like list of just the element
        2663 * children of the given element.
        2664 */
        2665goog.dom.DomHelper.prototype.getChildren = goog.dom.getChildren;
        2666
        2667
        2668/**
        2669 * Returns the first child node that is an element.
        2670 * @param {Node} node The node to get the first child element of.
        2671 * @return {Element} The first child node of {@code node} that is an element.
        2672 */
        2673goog.dom.DomHelper.prototype.getFirstElementChild =
        2674 goog.dom.getFirstElementChild;
        2675
        2676
        2677/**
        2678 * Returns the last child node that is an element.
        2679 * @param {Node} node The node to get the last child element of.
        2680 * @return {Element} The last child node of {@code node} that is an element.
        2681 */
        2682goog.dom.DomHelper.prototype.getLastElementChild = goog.dom.getLastElementChild;
        2683
        2684
        2685/**
        2686 * Returns the first next sibling that is an element.
        2687 * @param {Node} node The node to get the next sibling element of.
        2688 * @return {Element} The next sibling of {@code node} that is an element.
        2689 */
        2690goog.dom.DomHelper.prototype.getNextElementSibling =
        2691 goog.dom.getNextElementSibling;
        2692
        2693
        2694/**
        2695 * Returns the first previous sibling that is an element.
        2696 * @param {Node} node The node to get the previous sibling element of.
        2697 * @return {Element} The first previous sibling of {@code node} that is
        2698 * an element.
        2699 */
        2700goog.dom.DomHelper.prototype.getPreviousElementSibling =
        2701 goog.dom.getPreviousElementSibling;
        2702
        2703
        2704/**
        2705 * Returns the next node in source order from the given node.
        2706 * @param {Node} node The node.
        2707 * @return {Node} The next node in the DOM tree, or null if this was the last
        2708 * node.
        2709 */
        2710goog.dom.DomHelper.prototype.getNextNode = goog.dom.getNextNode;
        2711
        2712
        2713/**
        2714 * Returns the previous node in source order from the given node.
        2715 * @param {Node} node The node.
        2716 * @return {Node} The previous node in the DOM tree, or null if this was the
        2717 * first node.
        2718 */
        2719goog.dom.DomHelper.prototype.getPreviousNode = goog.dom.getPreviousNode;
        2720
        2721
        2722/**
        2723 * Whether the object looks like a DOM node.
        2724 * @param {?} obj The object being tested for node likeness.
        2725 * @return {boolean} Whether the object looks like a DOM node.
        2726 */
        2727goog.dom.DomHelper.prototype.isNodeLike = goog.dom.isNodeLike;
        2728
        2729
        2730/**
        2731 * Whether the object looks like an Element.
        2732 * @param {?} obj The object being tested for Element likeness.
        2733 * @return {boolean} Whether the object looks like an Element.
        2734 */
        2735goog.dom.DomHelper.prototype.isElement = goog.dom.isElement;
        2736
        2737
        2738/**
        2739 * Returns true if the specified value is a Window object. This includes the
        2740 * global window for HTML pages, and iframe windows.
        2741 * @param {?} obj Variable to test.
        2742 * @return {boolean} Whether the variable is a window.
        2743 */
        2744goog.dom.DomHelper.prototype.isWindow = goog.dom.isWindow;
        2745
        2746
        2747/**
        2748 * Returns an element's parent, if it's an Element.
        2749 * @param {Element} element The DOM element.
        2750 * @return {Element} The parent, or null if not an Element.
        2751 */
        2752goog.dom.DomHelper.prototype.getParentElement = goog.dom.getParentElement;
        2753
        2754
        2755/**
        2756 * Whether a node contains another node.
        2757 * @param {Node} parent The node that should contain the other node.
        2758 * @param {Node} descendant The node to test presence of.
        2759 * @return {boolean} Whether the parent node contains the descendent node.
        2760 */
        2761goog.dom.DomHelper.prototype.contains = goog.dom.contains;
        2762
        2763
        2764/**
        2765 * Compares the document order of two nodes, returning 0 if they are the same
        2766 * node, a negative number if node1 is before node2, and a positive number if
        2767 * node2 is before node1. Note that we compare the order the tags appear in the
        2768 * document so in the tree <b><i>text</i></b> the B node is considered to be
        2769 * before the I node.
        2770 *
        2771 * @param {Node} node1 The first node to compare.
        2772 * @param {Node} node2 The second node to compare.
        2773 * @return {number} 0 if the nodes are the same node, a negative number if node1
        2774 * is before node2, and a positive number if node2 is before node1.
        2775 */
        2776goog.dom.DomHelper.prototype.compareNodeOrder = goog.dom.compareNodeOrder;
        2777
        2778
        2779/**
        2780 * Find the deepest common ancestor of the given nodes.
        2781 * @param {...Node} var_args The nodes to find a common ancestor of.
        2782 * @return {Node} The common ancestor of the nodes, or null if there is none.
        2783 * null will only be returned if two or more of the nodes are from different
        2784 * documents.
        2785 */
        2786goog.dom.DomHelper.prototype.findCommonAncestor = goog.dom.findCommonAncestor;
        2787
        2788
        2789/**
        2790 * Returns the owner document for a node.
        2791 * @param {Node} node The node to get the document for.
        2792 * @return {!Document} The document owning the node.
        2793 */
        2794goog.dom.DomHelper.prototype.getOwnerDocument = goog.dom.getOwnerDocument;
        2795
        2796
        2797/**
        2798 * Cross browser function for getting the document element of an iframe.
        2799 * @param {Element} iframe Iframe element.
        2800 * @return {!Document} The frame content document.
        2801 */
        2802goog.dom.DomHelper.prototype.getFrameContentDocument =
        2803 goog.dom.getFrameContentDocument;
        2804
        2805
        2806/**
        2807 * Cross browser function for getting the window of a frame or iframe.
        2808 * @param {Element} frame Frame element.
        2809 * @return {Window} The window associated with the given frame.
        2810 */
        2811goog.dom.DomHelper.prototype.getFrameContentWindow =
        2812 goog.dom.getFrameContentWindow;
        2813
        2814
        2815/**
        2816 * Sets the text content of a node, with cross-browser support.
        2817 * @param {Node} node The node to change the text content of.
        2818 * @param {string|number} text The value that should replace the node's content.
        2819 */
        2820goog.dom.DomHelper.prototype.setTextContent = goog.dom.setTextContent;
        2821
        2822
        2823/**
        2824 * Gets the outerHTML of a node, which islike innerHTML, except that it
        2825 * actually contains the HTML of the node itself.
        2826 * @param {Element} element The element to get the HTML of.
        2827 * @return {string} The outerHTML of the given element.
        2828 */
        2829goog.dom.DomHelper.prototype.getOuterHtml = goog.dom.getOuterHtml;
        2830
        2831
        2832/**
        2833 * Finds the first descendant node that matches the filter function. This does
        2834 * a depth first search.
        2835 * @param {Node} root The root of the tree to search.
        2836 * @param {function(Node) : boolean} p The filter function.
        2837 * @return {Node|undefined} The found node or undefined if none is found.
        2838 */
        2839goog.dom.DomHelper.prototype.findNode = goog.dom.findNode;
        2840
        2841
        2842/**
        2843 * Finds all the descendant nodes that matches the filter function. This does a
        2844 * depth first search.
        2845 * @param {Node} root The root of the tree to search.
        2846 * @param {function(Node) : boolean} p The filter function.
        2847 * @return {Array<Node>} The found nodes or an empty array if none are found.
        2848 */
        2849goog.dom.DomHelper.prototype.findNodes = goog.dom.findNodes;
        2850
        2851
        2852/**
        2853 * Returns true if the element has a tab index that allows it to receive
        2854 * keyboard focus (tabIndex >= 0), false otherwise. Note that some elements
        2855 * natively support keyboard focus, even if they have no tab index.
        2856 * @param {!Element} element Element to check.
        2857 * @return {boolean} Whether the element has a tab index that allows keyboard
        2858 * focus.
        2859 */
        2860goog.dom.DomHelper.prototype.isFocusableTabIndex = goog.dom.isFocusableTabIndex;
        2861
        2862
        2863/**
        2864 * Enables or disables keyboard focus support on the element via its tab index.
        2865 * Only elements for which {@link goog.dom.isFocusableTabIndex} returns true
        2866 * (or elements that natively support keyboard focus, like form elements) can
        2867 * receive keyboard focus. See http://go/tabindex for more info.
        2868 * @param {Element} element Element whose tab index is to be changed.
        2869 * @param {boolean} enable Whether to set or remove a tab index on the element
        2870 * that supports keyboard focus.
        2871 */
        2872goog.dom.DomHelper.prototype.setFocusableTabIndex =
        2873 goog.dom.setFocusableTabIndex;
        2874
        2875
        2876/**
        2877 * Returns true if the element can be focused, i.e. it has a tab index that
        2878 * allows it to receive keyboard focus (tabIndex >= 0), or it is an element
        2879 * that natively supports keyboard focus.
        2880 * @param {!Element} element Element to check.
        2881 * @return {boolean} Whether the element allows keyboard focus.
        2882 */
        2883goog.dom.DomHelper.prototype.isFocusable = goog.dom.isFocusable;
        2884
        2885
        2886/**
        2887 * Returns the text contents of the current node, without markup. New lines are
        2888 * stripped and whitespace is collapsed, such that each character would be
        2889 * visible.
        2890 *
        2891 * In browsers that support it, innerText is used. Other browsers attempt to
        2892 * simulate it via node traversal. Line breaks are canonicalized in IE.
        2893 *
        2894 * @param {Node} node The node from which we are getting content.
        2895 * @return {string} The text content.
        2896 */
        2897goog.dom.DomHelper.prototype.getTextContent = goog.dom.getTextContent;
        2898
        2899
        2900/**
        2901 * Returns the text length of the text contained in a node, without markup. This
        2902 * is equivalent to the selection length if the node was selected, or the number
        2903 * of cursor movements to traverse the node. Images & BRs take one space. New
        2904 * lines are ignored.
        2905 *
        2906 * @param {Node} node The node whose text content length is being calculated.
        2907 * @return {number} The length of {@code node}'s text content.
        2908 */
        2909goog.dom.DomHelper.prototype.getNodeTextLength = goog.dom.getNodeTextLength;
        2910
        2911
        2912/**
        2913 * Returns the text offset of a node relative to one of its ancestors. The text
        2914 * length is the same as the length calculated by
        2915 * {@code goog.dom.getNodeTextLength}.
        2916 *
        2917 * @param {Node} node The node whose offset is being calculated.
        2918 * @param {Node=} opt_offsetParent Defaults to the node's owner document's body.
        2919 * @return {number} The text offset.
        2920 */
        2921goog.dom.DomHelper.prototype.getNodeTextOffset = goog.dom.getNodeTextOffset;
        2922
        2923
        2924/**
        2925 * Returns the node at a given offset in a parent node. If an object is
        2926 * provided for the optional third parameter, the node and the remainder of the
        2927 * offset will stored as properties of this object.
        2928 * @param {Node} parent The parent node.
        2929 * @param {number} offset The offset into the parent node.
        2930 * @param {Object=} opt_result Object to be used to store the return value. The
        2931 * return value will be stored in the form {node: Node, remainder: number}
        2932 * if this object is provided.
        2933 * @return {Node} The node at the given offset.
        2934 */
        2935goog.dom.DomHelper.prototype.getNodeAtOffset = goog.dom.getNodeAtOffset;
        2936
        2937
        2938/**
        2939 * Returns true if the object is a {@code NodeList}. To qualify as a NodeList,
        2940 * the object must have a numeric length property and an item function (which
        2941 * has type 'string' on IE for some reason).
        2942 * @param {Object} val Object to test.
        2943 * @return {boolean} Whether the object is a NodeList.
        2944 */
        2945goog.dom.DomHelper.prototype.isNodeList = goog.dom.isNodeList;
        2946
        2947
        2948/**
        2949 * Walks up the DOM hierarchy returning the first ancestor that has the passed
        2950 * tag name and/or class name. If the passed element matches the specified
        2951 * criteria, the element itself is returned.
        2952 * @param {Node} element The DOM node to start with.
        2953 * @param {?(goog.dom.TagName|string)=} opt_tag The tag name to match (or
        2954 * null/undefined to match only based on class name).
        2955 * @param {?string=} opt_class The class name to match (or null/undefined to
        2956 * match only based on tag name).
        2957 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2958 * dom.
        2959 * @return {Element} The first ancestor that matches the passed criteria, or
        2960 * null if no match is found.
        2961 */
        2962goog.dom.DomHelper.prototype.getAncestorByTagNameAndClass =
        2963 goog.dom.getAncestorByTagNameAndClass;
        2964
        2965
        2966/**
        2967 * Walks up the DOM hierarchy returning the first ancestor that has the passed
        2968 * class name. If the passed element matches the specified criteria, the
        2969 * element itself is returned.
        2970 * @param {Node} element The DOM node to start with.
        2971 * @param {string} class The class name to match.
        2972 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2973 * dom.
        2974 * @return {Element} The first ancestor that matches the passed criteria, or
        2975 * null if none match.
        2976 */
        2977goog.dom.DomHelper.prototype.getAncestorByClass =
        2978 goog.dom.getAncestorByClass;
        2979
        2980
        2981/**
        2982 * Walks up the DOM hierarchy returning the first ancestor that passes the
        2983 * matcher function.
        2984 * @param {Node} element The DOM node to start with.
        2985 * @param {function(Node) : boolean} matcher A function that returns true if the
        2986 * passed node matches the desired criteria.
        2987 * @param {boolean=} opt_includeNode If true, the node itself is included in
        2988 * the search (the first call to the matcher will pass startElement as
        2989 * the node to test).
        2990 * @param {number=} opt_maxSearchSteps Maximum number of levels to search up the
        2991 * dom.
        2992 * @return {Node} DOM node that matched the matcher, or null if there was
        2993 * no match.
        2994 */
        2995goog.dom.DomHelper.prototype.getAncestor = goog.dom.getAncestor;
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/nodetype.js.src.html b/docs/source/lib/goog/dom/nodetype.js.src.html new file mode 100644 index 0000000..587e392 --- /dev/null +++ b/docs/source/lib/goog/dom/nodetype.js.src.html @@ -0,0 +1 @@ +nodetype.js

        lib/goog/dom/nodetype.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Definition of goog.dom.NodeType.
        17 */
        18
        19goog.provide('goog.dom.NodeType');
        20
        21
        22/**
        23 * Constants for the nodeType attribute in the Node interface.
        24 *
        25 * These constants match those specified in the Node interface. These are
        26 * usually present on the Node object in recent browsers, but not in older
        27 * browsers (specifically, early IEs) and thus are given here.
        28 *
        29 * In some browsers (early IEs), these are not defined on the Node object,
        30 * so they are provided here.
        31 *
        32 * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247
        33 * @enum {number}
        34 */
        35goog.dom.NodeType = {
        36 ELEMENT: 1,
        37 ATTRIBUTE: 2,
        38 TEXT: 3,
        39 CDATA_SECTION: 4,
        40 ENTITY_REFERENCE: 5,
        41 ENTITY: 6,
        42 PROCESSING_INSTRUCTION: 7,
        43 COMMENT: 8,
        44 DOCUMENT: 9,
        45 DOCUMENT_TYPE: 10,
        46 DOCUMENT_FRAGMENT: 11,
        47 NOTATION: 12
        48};
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/safe.js.src.html b/docs/source/lib/goog/dom/safe.js.src.html new file mode 100644 index 0000000..e37c7c3 --- /dev/null +++ b/docs/source/lib/goog/dom/safe.js.src.html @@ -0,0 +1 @@ +safe.js

        lib/goog/dom/safe.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Type-safe wrappers for unsafe DOM APIs.
        17 *
        18 * This file provides type-safe wrappers for DOM APIs that can result in
        19 * cross-site scripting (XSS) vulnerabilities, if the API is supplied with
        20 * untrusted (attacker-controlled) input. Instead of plain strings, the type
        21 * safe wrappers consume values of types from the goog.html package whose
        22 * contract promises that values are safe to use in the corresponding context.
        23 *
        24 * Hence, a program that exclusively uses the wrappers in this file (i.e., whose
        25 * only reference to security-sensitive raw DOM APIs are in this file) is
        26 * guaranteed to be free of XSS due to incorrect use of such DOM APIs (modulo
        27 * correctness of code that produces values of the respective goog.html types,
        28 * and absent code that violates type safety).
        29 *
        30 * For example, assigning to an element's .innerHTML property a string that is
        31 * derived (even partially) from untrusted input typically results in an XSS
        32 * vulnerability. The type-safe wrapper goog.html.setInnerHtml consumes a value
        33 * of type goog.html.SafeHtml, whose contract states that using its values in a
        34 * HTML context will not result in XSS. Hence a program that is free of direct
        35 * assignments to any element's innerHTML property (with the exception of the
        36 * assignment to .innerHTML in this file) is guaranteed to be free of XSS due to
        37 * assignment of untrusted strings to the innerHTML property.
        38 */
        39
        40goog.provide('goog.dom.safe');
        41goog.provide('goog.dom.safe.InsertAdjacentHtmlPosition');
        42
        43goog.require('goog.asserts');
        44goog.require('goog.html.SafeHtml');
        45goog.require('goog.html.SafeUrl');
        46goog.require('goog.html.TrustedResourceUrl');
        47goog.require('goog.string');
        48goog.require('goog.string.Const');
        49
        50
        51/** @enum {string} */
        52goog.dom.safe.InsertAdjacentHtmlPosition = {
        53 AFTERBEGIN: 'afterbegin',
        54 AFTEREND: 'afterend',
        55 BEFOREBEGIN: 'beforebegin',
        56 BEFOREEND: 'beforeend'
        57};
        58
        59
        60/**
        61 * Inserts known-safe HTML into a Node, at the specified position.
        62 * @param {!Node} node The node on which to call insertAdjacentHTML.
        63 * @param {!goog.dom.safe.InsertAdjacentHtmlPosition} position Position where
        64 * to insert the HTML.
        65 * @param {!goog.html.SafeHtml} html The known-safe HTML to insert.
        66 */
        67goog.dom.safe.insertAdjacentHtml = function(node, position, html) {
        68 node.insertAdjacentHTML(position, goog.html.SafeHtml.unwrap(html));
        69};
        70
        71
        72/**
        73 * Assigns known-safe HTML to an element's innerHTML property.
        74 * @param {!Element} elem The element whose innerHTML is to be assigned to.
        75 * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
        76 */
        77goog.dom.safe.setInnerHtml = function(elem, html) {
        78 elem.innerHTML = goog.html.SafeHtml.unwrap(html);
        79};
        80
        81
        82/**
        83 * Assigns known-safe HTML to an element's outerHTML property.
        84 * @param {!Element} elem The element whose outerHTML is to be assigned to.
        85 * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
        86 */
        87goog.dom.safe.setOuterHtml = function(elem, html) {
        88 elem.outerHTML = goog.html.SafeHtml.unwrap(html);
        89};
        90
        91
        92/**
        93 * Writes known-safe HTML to a document.
        94 * @param {!Document} doc The document to be written to.
        95 * @param {!goog.html.SafeHtml} html The known-safe HTML to assign.
        96 */
        97goog.dom.safe.documentWrite = function(doc, html) {
        98 doc.write(goog.html.SafeHtml.unwrap(html));
        99};
        100
        101
        102/**
        103 * Safely assigns a URL to an anchor element's href property.
        104 *
        105 * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
        106 * anchor's href property. If url is of type string however, it is first
        107 * sanitized using goog.html.SafeUrl.sanitize.
        108 *
        109 * Example usage:
        110 * goog.dom.safe.setAnchorHref(anchorEl, url);
        111 * which is a safe alternative to
        112 * anchorEl.href = url;
        113 * The latter can result in XSS vulnerabilities if url is a
        114 * user-/attacker-controlled value.
        115 *
        116 * @param {!HTMLAnchorElement} anchor The anchor element whose href property
        117 * is to be assigned to.
        118 * @param {string|!goog.html.SafeUrl} url The URL to assign.
        119 * @see goog.html.SafeUrl#sanitize
        120 */
        121goog.dom.safe.setAnchorHref = function(anchor, url) {
        122 /** @type {!goog.html.SafeUrl} */
        123 var safeUrl;
        124 if (url instanceof goog.html.SafeUrl) {
        125 safeUrl = url;
        126 } else {
        127 safeUrl = goog.html.SafeUrl.sanitize(url);
        128 }
        129 anchor.href = goog.html.SafeUrl.unwrap(safeUrl);
        130};
        131
        132
        133/**
        134 * Safely assigns a URL to an embed element's src property.
        135 *
        136 * Example usage:
        137 * goog.dom.safe.setEmbedSrc(embedEl, url);
        138 * which is a safe alternative to
        139 * embedEl.src = url;
        140 * The latter can result in loading untrusted code unless it is ensured that
        141 * the URL refers to a trustworthy resource.
        142 *
        143 * @param {!HTMLEmbedElement} embed The embed element whose src property
        144 * is to be assigned to.
        145 * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
        146 */
        147goog.dom.safe.setEmbedSrc = function(embed, url) {
        148 embed.src = goog.html.TrustedResourceUrl.unwrap(url);
        149};
        150
        151
        152/**
        153 * Safely assigns a URL to a frame element's src property.
        154 *
        155 * Example usage:
        156 * goog.dom.safe.setFrameSrc(frameEl, url);
        157 * which is a safe alternative to
        158 * frameEl.src = url;
        159 * The latter can result in loading untrusted code unless it is ensured that
        160 * the URL refers to a trustworthy resource.
        161 *
        162 * @param {!HTMLFrameElement} frame The frame element whose src property
        163 * is to be assigned to.
        164 * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
        165 */
        166goog.dom.safe.setFrameSrc = function(frame, url) {
        167 frame.src = goog.html.TrustedResourceUrl.unwrap(url);
        168};
        169
        170
        171/**
        172 * Safely assigns a URL to an iframe element's src property.
        173 *
        174 * Example usage:
        175 * goog.dom.safe.setIframeSrc(iframeEl, url);
        176 * which is a safe alternative to
        177 * iframeEl.src = url;
        178 * The latter can result in loading untrusted code unless it is ensured that
        179 * the URL refers to a trustworthy resource.
        180 *
        181 * @param {!HTMLIFrameElement} iframe The iframe element whose src property
        182 * is to be assigned to.
        183 * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
        184 */
        185goog.dom.safe.setIframeSrc = function(iframe, url) {
        186 iframe.src = goog.html.TrustedResourceUrl.unwrap(url);
        187};
        188
        189
        190/**
        191 * Safely sets a link element's href and rel properties. Whether or not
        192 * the URL assigned to href has to be a goog.html.TrustedResourceUrl
        193 * depends on the value of the rel property. If rel contains "stylesheet"
        194 * then a TrustedResourceUrl is required.
        195 *
        196 * Example usage:
        197 * goog.dom.safe.setLinkHrefAndRel(linkEl, url, 'stylesheet');
        198 * which is a safe alternative to
        199 * linkEl.rel = 'stylesheet';
        200 * linkEl.href = url;
        201 * The latter can result in loading untrusted code unless it is ensured that
        202 * the URL refers to a trustworthy resource.
        203 *
        204 * @param {!HTMLLinkElement} link The link element whose href property
        205 * is to be assigned to.
        206 * @param {string|!goog.html.SafeUrl|!goog.html.TrustedResourceUrl} url The URL
        207 * to assign to the href property. Must be a TrustedResourceUrl if the
        208 * value assigned to rel contains "stylesheet". A string value is
        209 * sanitized with goog.html.SafeUrl.sanitize.
        210 * @param {string} rel The value to assign to the rel property.
        211 * @throws {Error} if rel contains "stylesheet" and url is not a
        212 * TrustedResourceUrl
        213 * @see goog.html.SafeUrl#sanitize
        214 */
        215goog.dom.safe.setLinkHrefAndRel = function(link, url, rel) {
        216 link.rel = rel;
        217 if (goog.string.caseInsensitiveContains(rel, 'stylesheet')) {
        218 goog.asserts.assert(
        219 url instanceof goog.html.TrustedResourceUrl,
        220 'URL must be TrustedResourceUrl because "rel" contains "stylesheet"');
        221 link.href = goog.html.TrustedResourceUrl.unwrap(url);
        222 } else if (url instanceof goog.html.TrustedResourceUrl) {
        223 link.href = goog.html.TrustedResourceUrl.unwrap(url);
        224 } else if (url instanceof goog.html.SafeUrl) {
        225 link.href = goog.html.SafeUrl.unwrap(url);
        226 } else { // string
        227 // SafeUrl.sanitize must return legitimate SafeUrl when passed a string.
        228 link.href = goog.html.SafeUrl.sanitize(url).getTypedStringValue();
        229 }
        230};
        231
        232
        233/**
        234 * Safely assigns a URL to an object element's data property.
        235 *
        236 * Example usage:
        237 * goog.dom.safe.setObjectData(objectEl, url);
        238 * which is a safe alternative to
        239 * objectEl.data = url;
        240 * The latter can result in loading untrusted code unless setit is ensured that
        241 * the URL refers to a trustworthy resource.
        242 *
        243 * @param {!HTMLObjectElement} object The object element whose data property
        244 * is to be assigned to.
        245 * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
        246 */
        247goog.dom.safe.setObjectData = function(object, url) {
        248 object.data = goog.html.TrustedResourceUrl.unwrap(url);
        249};
        250
        251
        252/**
        253 * Safely assigns a URL to an iframe element's src property.
        254 *
        255 * Example usage:
        256 * goog.dom.safe.setScriptSrc(scriptEl, url);
        257 * which is a safe alternative to
        258 * scriptEl.src = url;
        259 * The latter can result in loading untrusted code unless it is ensured that
        260 * the URL refers to a trustworthy resource.
        261 *
        262 * @param {!HTMLScriptElement} script The script element whose src property
        263 * is to be assigned to.
        264 * @param {!goog.html.TrustedResourceUrl} url The URL to assign.
        265 */
        266goog.dom.safe.setScriptSrc = function(script, url) {
        267 script.src = goog.html.TrustedResourceUrl.unwrap(url);
        268};
        269
        270
        271/**
        272 * Safely assigns a URL to a Location object's href property.
        273 *
        274 * If url is of type goog.html.SafeUrl, its value is unwrapped and assigned to
        275 * loc's href property. If url is of type string however, it is first sanitized
        276 * using goog.html.SafeUrl.sanitize.
        277 *
        278 * Example usage:
        279 * goog.dom.safe.setLocationHref(document.location, redirectUrl);
        280 * which is a safe alternative to
        281 * document.location.href = redirectUrl;
        282 * The latter can result in XSS vulnerabilities if redirectUrl is a
        283 * user-/attacker-controlled value.
        284 *
        285 * @param {!Location} loc The Location object whose href property is to be
        286 * assigned to.
        287 * @param {string|!goog.html.SafeUrl} url The URL to assign.
        288 * @see goog.html.SafeUrl#sanitize
        289 */
        290goog.dom.safe.setLocationHref = function(loc, url) {
        291 /** @type {!goog.html.SafeUrl} */
        292 var safeUrl;
        293 if (url instanceof goog.html.SafeUrl) {
        294 safeUrl = url;
        295 } else {
        296 safeUrl = goog.html.SafeUrl.sanitize(url);
        297 }
        298 loc.href = goog.html.SafeUrl.unwrap(safeUrl);
        299};
        300
        301
        302/**
        303 * Safely opens a URL in a new window (via window.open).
        304 *
        305 * If url is of type goog.html.SafeUrl, its value is unwrapped and passed in to
        306 * window.open. If url is of type string however, it is first sanitized
        307 * using goog.html.SafeUrl.sanitize.
        308 *
        309 * Note that this function does not prevent leakages via the referer that is
        310 * sent by window.open. It is advised to only use this to open 1st party URLs.
        311 *
        312 * Example usage:
        313 * goog.dom.safe.openInWindow(url);
        314 * which is a safe alternative to
        315 * window.open(url);
        316 * The latter can result in XSS vulnerabilities if redirectUrl is a
        317 * user-/attacker-controlled value.
        318 *
        319 * @param {string|!goog.html.SafeUrl} url The URL to open.
        320 * @param {Window=} opt_openerWin Window of which to call the .open() method.
        321 * Defaults to the global window.
        322 * @param {!goog.string.Const=} opt_name Name of the window to open in. Can be
        323 * _top, etc as allowed by window.open().
        324 * @param {string=} opt_specs Comma-separated list of specifications, same as
        325 * in window.open().
        326 * @param {boolean=} opt_replace Whether to replace the current entry in browser
        327 * history, same as in window.open().
        328 * @return {Window} Window the url was opened in.
        329 */
        330goog.dom.safe.openInWindow = function(
        331 url, opt_openerWin, opt_name, opt_specs, opt_replace) {
        332 /** @type {!goog.html.SafeUrl} */
        333 var safeUrl;
        334 if (url instanceof goog.html.SafeUrl) {
        335 safeUrl = url;
        336 } else {
        337 safeUrl = goog.html.SafeUrl.sanitize(url);
        338 }
        339 var win = opt_openerWin || window;
        340 return win.open(goog.html.SafeUrl.unwrap(safeUrl),
        341 // If opt_name is undefined, simply passing that in to open() causes IE to
        342 // reuse the current window instead of opening a new one. Thus we pass ''
        343 // in instead, which according to spec opens a new window. See
        344 // https://html.spec.whatwg.org/multipage/browsers.html#dom-open .
        345 opt_name ? goog.string.Const.unwrap(opt_name) : '',
        346 opt_specs, opt_replace);
        347};
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/tagname.js.src.html b/docs/source/lib/goog/dom/tagname.js.src.html new file mode 100644 index 0000000..871a3c4 --- /dev/null +++ b/docs/source/lib/goog/dom/tagname.js.src.html @@ -0,0 +1 @@ +tagname.js

        lib/goog/dom/tagname.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines the goog.dom.TagName enum. This enumerates
        17 * all HTML tag names specified in either the the W3C HTML 4.01 index of
        18 * elements or the HTML5 draft specification.
        19 *
        20 * References:
        21 * http://www.w3.org/TR/html401/index/elements.html
        22 * http://dev.w3.org/html5/spec/section-index.html
        23 *
        24 */
        25goog.provide('goog.dom.TagName');
        26
        27
        28/**
        29 * Enum of all html tag names specified by the W3C HTML4.01 and HTML5
        30 * specifications.
        31 * @enum {string}
        32 */
        33goog.dom.TagName = {
        34 A: 'A',
        35 ABBR: 'ABBR',
        36 ACRONYM: 'ACRONYM',
        37 ADDRESS: 'ADDRESS',
        38 APPLET: 'APPLET',
        39 AREA: 'AREA',
        40 ARTICLE: 'ARTICLE',
        41 ASIDE: 'ASIDE',
        42 AUDIO: 'AUDIO',
        43 B: 'B',
        44 BASE: 'BASE',
        45 BASEFONT: 'BASEFONT',
        46 BDI: 'BDI',
        47 BDO: 'BDO',
        48 BIG: 'BIG',
        49 BLOCKQUOTE: 'BLOCKQUOTE',
        50 BODY: 'BODY',
        51 BR: 'BR',
        52 BUTTON: 'BUTTON',
        53 CANVAS: 'CANVAS',
        54 CAPTION: 'CAPTION',
        55 CENTER: 'CENTER',
        56 CITE: 'CITE',
        57 CODE: 'CODE',
        58 COL: 'COL',
        59 COLGROUP: 'COLGROUP',
        60 COMMAND: 'COMMAND',
        61 DATA: 'DATA',
        62 DATALIST: 'DATALIST',
        63 DD: 'DD',
        64 DEL: 'DEL',
        65 DETAILS: 'DETAILS',
        66 DFN: 'DFN',
        67 DIALOG: 'DIALOG',
        68 DIR: 'DIR',
        69 DIV: 'DIV',
        70 DL: 'DL',
        71 DT: 'DT',
        72 EM: 'EM',
        73 EMBED: 'EMBED',
        74 FIELDSET: 'FIELDSET',
        75 FIGCAPTION: 'FIGCAPTION',
        76 FIGURE: 'FIGURE',
        77 FONT: 'FONT',
        78 FOOTER: 'FOOTER',
        79 FORM: 'FORM',
        80 FRAME: 'FRAME',
        81 FRAMESET: 'FRAMESET',
        82 H1: 'H1',
        83 H2: 'H2',
        84 H3: 'H3',
        85 H4: 'H4',
        86 H5: 'H5',
        87 H6: 'H6',
        88 HEAD: 'HEAD',
        89 HEADER: 'HEADER',
        90 HGROUP: 'HGROUP',
        91 HR: 'HR',
        92 HTML: 'HTML',
        93 I: 'I',
        94 IFRAME: 'IFRAME',
        95 IMG: 'IMG',
        96 INPUT: 'INPUT',
        97 INS: 'INS',
        98 ISINDEX: 'ISINDEX',
        99 KBD: 'KBD',
        100 KEYGEN: 'KEYGEN',
        101 LABEL: 'LABEL',
        102 LEGEND: 'LEGEND',
        103 LI: 'LI',
        104 LINK: 'LINK',
        105 MAP: 'MAP',
        106 MARK: 'MARK',
        107 MATH: 'MATH',
        108 MENU: 'MENU',
        109 META: 'META',
        110 METER: 'METER',
        111 NAV: 'NAV',
        112 NOFRAMES: 'NOFRAMES',
        113 NOSCRIPT: 'NOSCRIPT',
        114 OBJECT: 'OBJECT',
        115 OL: 'OL',
        116 OPTGROUP: 'OPTGROUP',
        117 OPTION: 'OPTION',
        118 OUTPUT: 'OUTPUT',
        119 P: 'P',
        120 PARAM: 'PARAM',
        121 PRE: 'PRE',
        122 PROGRESS: 'PROGRESS',
        123 Q: 'Q',
        124 RP: 'RP',
        125 RT: 'RT',
        126 RUBY: 'RUBY',
        127 S: 'S',
        128 SAMP: 'SAMP',
        129 SCRIPT: 'SCRIPT',
        130 SECTION: 'SECTION',
        131 SELECT: 'SELECT',
        132 SMALL: 'SMALL',
        133 SOURCE: 'SOURCE',
        134 SPAN: 'SPAN',
        135 STRIKE: 'STRIKE',
        136 STRONG: 'STRONG',
        137 STYLE: 'STYLE',
        138 SUB: 'SUB',
        139 SUMMARY: 'SUMMARY',
        140 SUP: 'SUP',
        141 SVG: 'SVG',
        142 TABLE: 'TABLE',
        143 TBODY: 'TBODY',
        144 TD: 'TD',
        145 TEMPLATE: 'TEMPLATE',
        146 TEXTAREA: 'TEXTAREA',
        147 TFOOT: 'TFOOT',
        148 TH: 'TH',
        149 THEAD: 'THEAD',
        150 TIME: 'TIME',
        151 TITLE: 'TITLE',
        152 TR: 'TR',
        153 TRACK: 'TRACK',
        154 TT: 'TT',
        155 U: 'U',
        156 UL: 'UL',
        157 VAR: 'VAR',
        158 VIDEO: 'VIDEO',
        159 WBR: 'WBR'
        160};
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/tags.js.src.html b/docs/source/lib/goog/dom/tags.js.src.html new file mode 100644 index 0000000..cf3bd61 --- /dev/null +++ b/docs/source/lib/goog/dom/tags.js.src.html @@ -0,0 +1 @@ +tags.js

        lib/goog/dom/tags.js

        1// Copyright 2014 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for HTML element tag names.
        17 */
        18goog.provide('goog.dom.tags');
        19
        20goog.require('goog.object');
        21
        22
        23/**
        24 * The void elements specified by
        25 * http://www.w3.org/TR/html-markup/syntax.html#void-elements.
        26 * @const
        27 * @type {!Object}
        28 * @private
        29 */
        30goog.dom.tags.VOID_TAGS_ = goog.object.createSet(('area,base,br,col,command,' +
        31 'embed,hr,img,input,keygen,link,meta,param,source,track,wbr').split(','));
        32
        33
        34/**
        35 * Checks whether the tag is void (with no contents allowed and no legal end
        36 * tag), for example 'br'.
        37 * @param {string} tagName The tag name in lower case.
        38 * @return {boolean}
        39 */
        40goog.dom.tags.isVoidTag = function(tagName) {
        41 return goog.dom.tags.VOID_TAGS_[tagName] === true;
        42};
        \ No newline at end of file diff --git a/docs/source/lib/goog/dom/vendor.js.src.html b/docs/source/lib/goog/dom/vendor.js.src.html new file mode 100644 index 0000000..d97e89d --- /dev/null +++ b/docs/source/lib/goog/dom/vendor.js.src.html @@ -0,0 +1 @@ +vendor.js

        lib/goog/dom/vendor.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Vendor prefix getters.
        17 */
        18
        19goog.provide('goog.dom.vendor');
        20
        21goog.require('goog.string');
        22goog.require('goog.userAgent');
        23
        24
        25/**
        26 * Returns the JS vendor prefix used in CSS properties. Different vendors
        27 * use different methods of changing the case of the property names.
        28 *
        29 * @return {?string} The JS vendor prefix or null if there is none.
        30 */
        31goog.dom.vendor.getVendorJsPrefix = function() {
        32 if (goog.userAgent.WEBKIT) {
        33 return 'Webkit';
        34 } else if (goog.userAgent.GECKO) {
        35 return 'Moz';
        36 } else if (goog.userAgent.IE) {
        37 return 'ms';
        38 } else if (goog.userAgent.OPERA) {
        39 return 'O';
        40 }
        41
        42 return null;
        43};
        44
        45
        46/**
        47 * Returns the vendor prefix used in CSS properties.
        48 *
        49 * @return {?string} The vendor prefix or null if there is none.
        50 */
        51goog.dom.vendor.getVendorPrefix = function() {
        52 if (goog.userAgent.WEBKIT) {
        53 return '-webkit';
        54 } else if (goog.userAgent.GECKO) {
        55 return '-moz';
        56 } else if (goog.userAgent.IE) {
        57 return '-ms';
        58 } else if (goog.userAgent.OPERA) {
        59 return '-o';
        60 }
        61
        62 return null;
        63};
        64
        65
        66/**
        67 * @param {string} propertyName A property name.
        68 * @param {!Object=} opt_object If provided, we verify if the property exists in
        69 * the object.
        70 * @return {?string} A vendor prefixed property name, or null if it does not
        71 * exist.
        72 */
        73goog.dom.vendor.getPrefixedPropertyName = function(propertyName, opt_object) {
        74 // We first check for a non-prefixed property, if available.
        75 if (opt_object && propertyName in opt_object) {
        76 return propertyName;
        77 }
        78 var prefix = goog.dom.vendor.getVendorJsPrefix();
        79 if (prefix) {
        80 prefix = prefix.toLowerCase();
        81 var prefixedPropertyName = prefix + goog.string.toTitleCase(propertyName);
        82 return (!goog.isDef(opt_object) || prefixedPropertyName in opt_object) ?
        83 prefixedPropertyName : null;
        84 }
        85 return null;
        86};
        87
        88
        89/**
        90 * @param {string} eventType An event type.
        91 * @return {string} A lower-cased vendor prefixed event type.
        92 */
        93goog.dom.vendor.getPrefixedEventType = function(eventType) {
        94 var prefix = goog.dom.vendor.getVendorJsPrefix() || '';
        95 return (prefix + eventType).toLowerCase();
        96};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/browserevent.js.src.html b/docs/source/lib/goog/events/browserevent.js.src.html new file mode 100644 index 0000000..7088496 --- /dev/null +++ b/docs/source/lib/goog/events/browserevent.js.src.html @@ -0,0 +1 @@ +browserevent.js

        lib/goog/events/browserevent.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A patched, standardized event object for browser events.
        17 *
        18 * <pre>
        19 * The patched event object contains the following members:
        20 * - type {string} Event type, e.g. 'click'
        21 * - target {Object} The element that actually triggered the event
        22 * - currentTarget {Object} The element the listener is attached to
        23 * - relatedTarget {Object} For mouseover and mouseout, the previous object
        24 * - offsetX {number} X-coordinate relative to target
        25 * - offsetY {number} Y-coordinate relative to target
        26 * - clientX {number} X-coordinate relative to viewport
        27 * - clientY {number} Y-coordinate relative to viewport
        28 * - screenX {number} X-coordinate relative to the edge of the screen
        29 * - screenY {number} Y-coordinate relative to the edge of the screen
        30 * - button {number} Mouse button. Use isButton() to test.
        31 * - keyCode {number} Key-code
        32 * - ctrlKey {boolean} Was ctrl key depressed
        33 * - altKey {boolean} Was alt key depressed
        34 * - shiftKey {boolean} Was shift key depressed
        35 * - metaKey {boolean} Was meta key depressed
        36 * - defaultPrevented {boolean} Whether the default action has been prevented
        37 * - state {Object} History state object
        38 *
        39 * NOTE: The keyCode member contains the raw browser keyCode. For normalized
        40 * key and character code use {@link goog.events.KeyHandler}.
        41 * </pre>
        42 *
        43 * @author arv@google.com (Erik Arvidsson)
        44 */
        45
        46goog.provide('goog.events.BrowserEvent');
        47goog.provide('goog.events.BrowserEvent.MouseButton');
        48
        49goog.require('goog.events.BrowserFeature');
        50goog.require('goog.events.Event');
        51goog.require('goog.events.EventType');
        52goog.require('goog.reflect');
        53goog.require('goog.userAgent');
        54
        55
        56
        57/**
        58 * Accepts a browser event object and creates a patched, cross browser event
        59 * object.
        60 * The content of this object will not be initialized if no event object is
        61 * provided. If this is the case, init() needs to be invoked separately.
        62 * @param {Event=} opt_e Browser event object.
        63 * @param {EventTarget=} opt_currentTarget Current target for event.
        64 * @constructor
        65 * @extends {goog.events.Event}
        66 */
        67goog.events.BrowserEvent = function(opt_e, opt_currentTarget) {
        68 goog.events.BrowserEvent.base(this, 'constructor', opt_e ? opt_e.type : '');
        69
        70 /**
        71 * Target that fired the event.
        72 * @override
        73 * @type {Node}
        74 */
        75 this.target = null;
        76
        77 /**
        78 * Node that had the listener attached.
        79 * @override
        80 * @type {Node|undefined}
        81 */
        82 this.currentTarget = null;
        83
        84 /**
        85 * For mouseover and mouseout events, the related object for the event.
        86 * @type {Node}
        87 */
        88 this.relatedTarget = null;
        89
        90 /**
        91 * X-coordinate relative to target.
        92 * @type {number}
        93 */
        94 this.offsetX = 0;
        95
        96 /**
        97 * Y-coordinate relative to target.
        98 * @type {number}
        99 */
        100 this.offsetY = 0;
        101
        102 /**
        103 * X-coordinate relative to the window.
        104 * @type {number}
        105 */
        106 this.clientX = 0;
        107
        108 /**
        109 * Y-coordinate relative to the window.
        110 * @type {number}
        111 */
        112 this.clientY = 0;
        113
        114 /**
        115 * X-coordinate relative to the monitor.
        116 * @type {number}
        117 */
        118 this.screenX = 0;
        119
        120 /**
        121 * Y-coordinate relative to the monitor.
        122 * @type {number}
        123 */
        124 this.screenY = 0;
        125
        126 /**
        127 * Which mouse button was pressed.
        128 * @type {number}
        129 */
        130 this.button = 0;
        131
        132 /**
        133 * Keycode of key press.
        134 * @type {number}
        135 */
        136 this.keyCode = 0;
        137
        138 /**
        139 * Keycode of key press.
        140 * @type {number}
        141 */
        142 this.charCode = 0;
        143
        144 /**
        145 * Whether control was pressed at time of event.
        146 * @type {boolean}
        147 */
        148 this.ctrlKey = false;
        149
        150 /**
        151 * Whether alt was pressed at time of event.
        152 * @type {boolean}
        153 */
        154 this.altKey = false;
        155
        156 /**
        157 * Whether shift was pressed at time of event.
        158 * @type {boolean}
        159 */
        160 this.shiftKey = false;
        161
        162 /**
        163 * Whether the meta key was pressed at time of event.
        164 * @type {boolean}
        165 */
        166 this.metaKey = false;
        167
        168 /**
        169 * History state object, only set for PopState events where it's a copy of the
        170 * state object provided to pushState or replaceState.
        171 * @type {Object}
        172 */
        173 this.state = null;
        174
        175 /**
        176 * Whether the default platform modifier key was pressed at time of event.
        177 * (This is control for all platforms except Mac, where it's Meta.)
        178 * @type {boolean}
        179 */
        180 this.platformModifierKey = false;
        181
        182 /**
        183 * The browser event object.
        184 * @private {Event}
        185 */
        186 this.event_ = null;
        187
        188 if (opt_e) {
        189 this.init(opt_e, opt_currentTarget);
        190 }
        191};
        192goog.inherits(goog.events.BrowserEvent, goog.events.Event);
        193
        194
        195/**
        196 * Normalized button constants for the mouse.
        197 * @enum {number}
        198 */
        199goog.events.BrowserEvent.MouseButton = {
        200 LEFT: 0,
        201 MIDDLE: 1,
        202 RIGHT: 2
        203};
        204
        205
        206/**
        207 * Static data for mapping mouse buttons.
        208 * @type {!Array<number>}
        209 */
        210goog.events.BrowserEvent.IEButtonMap = [
        211 1, // LEFT
        212 4, // MIDDLE
        213 2 // RIGHT
        214];
        215
        216
        217/**
        218 * Accepts a browser event object and creates a patched, cross browser event
        219 * object.
        220 * @param {Event} e Browser event object.
        221 * @param {EventTarget=} opt_currentTarget Current target for event.
        222 */
        223goog.events.BrowserEvent.prototype.init = function(e, opt_currentTarget) {
        224 var type = this.type = e.type;
        225
        226 // TODO(nicksantos): Change this.target to type EventTarget.
        227 this.target = /** @type {Node} */ (e.target) || e.srcElement;
        228
        229 // TODO(nicksantos): Change this.currentTarget to type EventTarget.
        230 this.currentTarget = /** @type {Node} */ (opt_currentTarget);
        231
        232 var relatedTarget = /** @type {Node} */ (e.relatedTarget);
        233 if (relatedTarget) {
        234 // There's a bug in FireFox where sometimes, relatedTarget will be a
        235 // chrome element, and accessing any property of it will get a permission
        236 // denied exception. See:
        237 // https://bugzilla.mozilla.org/show_bug.cgi?id=497780
        238 if (goog.userAgent.GECKO) {
        239 if (!goog.reflect.canAccessProperty(relatedTarget, 'nodeName')) {
        240 relatedTarget = null;
        241 }
        242 }
        243 // TODO(arv): Use goog.events.EventType when it has been refactored into its
        244 // own file.
        245 } else if (type == goog.events.EventType.MOUSEOVER) {
        246 relatedTarget = e.fromElement;
        247 } else if (type == goog.events.EventType.MOUSEOUT) {
        248 relatedTarget = e.toElement;
        249 }
        250
        251 this.relatedTarget = relatedTarget;
        252
        253 // Webkit emits a lame warning whenever layerX/layerY is accessed.
        254 // http://code.google.com/p/chromium/issues/detail?id=101733
        255 this.offsetX = (goog.userAgent.WEBKIT || e.offsetX !== undefined) ?
        256 e.offsetX : e.layerX;
        257 this.offsetY = (goog.userAgent.WEBKIT || e.offsetY !== undefined) ?
        258 e.offsetY : e.layerY;
        259
        260 this.clientX = e.clientX !== undefined ? e.clientX : e.pageX;
        261 this.clientY = e.clientY !== undefined ? e.clientY : e.pageY;
        262 this.screenX = e.screenX || 0;
        263 this.screenY = e.screenY || 0;
        264
        265 this.button = e.button;
        266
        267 this.keyCode = e.keyCode || 0;
        268 this.charCode = e.charCode || (type == 'keypress' ? e.keyCode : 0);
        269 this.ctrlKey = e.ctrlKey;
        270 this.altKey = e.altKey;
        271 this.shiftKey = e.shiftKey;
        272 this.metaKey = e.metaKey;
        273 this.platformModifierKey = goog.userAgent.MAC ? e.metaKey : e.ctrlKey;
        274 this.state = e.state;
        275 this.event_ = e;
        276 if (e.defaultPrevented) {
        277 this.preventDefault();
        278 }
        279};
        280
        281
        282/**
        283 * Tests to see which button was pressed during the event. This is really only
        284 * useful in IE and Gecko browsers. And in IE, it's only useful for
        285 * mousedown/mouseup events, because click only fires for the left mouse button.
        286 *
        287 * Safari 2 only reports the left button being clicked, and uses the value '1'
        288 * instead of 0. Opera only reports a mousedown event for the middle button, and
        289 * no mouse events for the right button. Opera has default behavior for left and
        290 * middle click that can only be overridden via a configuration setting.
        291 *
        292 * There's a nice table of this mess at http://www.unixpapa.com/js/mouse.html.
        293 *
        294 * @param {goog.events.BrowserEvent.MouseButton} button The button
        295 * to test for.
        296 * @return {boolean} True if button was pressed.
        297 */
        298goog.events.BrowserEvent.prototype.isButton = function(button) {
        299 if (!goog.events.BrowserFeature.HAS_W3C_BUTTON) {
        300 if (this.type == 'click') {
        301 return button == goog.events.BrowserEvent.MouseButton.LEFT;
        302 } else {
        303 return !!(this.event_.button &
        304 goog.events.BrowserEvent.IEButtonMap[button]);
        305 }
        306 } else {
        307 return this.event_.button == button;
        308 }
        309};
        310
        311
        312/**
        313 * Whether this has an "action"-producing mouse button.
        314 *
        315 * By definition, this includes left-click on windows/linux, and left-click
        316 * without the ctrl key on Macs.
        317 *
        318 * @return {boolean} The result.
        319 */
        320goog.events.BrowserEvent.prototype.isMouseActionButton = function() {
        321 // Webkit does not ctrl+click to be a right-click, so we
        322 // normalize it to behave like Gecko and Opera.
        323 return this.isButton(goog.events.BrowserEvent.MouseButton.LEFT) &&
        324 !(goog.userAgent.WEBKIT && goog.userAgent.MAC && this.ctrlKey);
        325};
        326
        327
        328/**
        329 * @override
        330 */
        331goog.events.BrowserEvent.prototype.stopPropagation = function() {
        332 goog.events.BrowserEvent.superClass_.stopPropagation.call(this);
        333 if (this.event_.stopPropagation) {
        334 this.event_.stopPropagation();
        335 } else {
        336 this.event_.cancelBubble = true;
        337 }
        338};
        339
        340
        341/**
        342 * @override
        343 */
        344goog.events.BrowserEvent.prototype.preventDefault = function() {
        345 goog.events.BrowserEvent.superClass_.preventDefault.call(this);
        346 var be = this.event_;
        347 if (!be.preventDefault) {
        348 be.returnValue = false;
        349 if (goog.events.BrowserFeature.SET_KEY_CODE_TO_PREVENT_DEFAULT) {
        350 /** @preserveTry */
        351 try {
        352 // Most keys can be prevented using returnValue. Some special keys
        353 // require setting the keyCode to -1 as well:
        354 //
        355 // In IE7:
        356 // F3, F5, F10, F11, Ctrl+P, Crtl+O, Ctrl+F (these are taken from IE6)
        357 //
        358 // In IE8:
        359 // Ctrl+P, Crtl+O, Ctrl+F (F1-F12 cannot be stopped through the event)
        360 //
        361 // We therefore do this for all function keys as well as when Ctrl key
        362 // is pressed.
        363 var VK_F1 = 112;
        364 var VK_F12 = 123;
        365 if (be.ctrlKey || be.keyCode >= VK_F1 && be.keyCode <= VK_F12) {
        366 be.keyCode = -1;
        367 }
        368 } catch (ex) {
        369 // IE throws an 'access denied' exception when trying to change
        370 // keyCode in some situations (e.g. srcElement is input[type=file],
        371 // or srcElement is an anchor tag rewritten by parent's innerHTML).
        372 // Do nothing in this case.
        373 }
        374 }
        375 } else {
        376 be.preventDefault();
        377 }
        378};
        379
        380
        381/**
        382 * @return {Event} The underlying browser event object.
        383 */
        384goog.events.BrowserEvent.prototype.getBrowserEvent = function() {
        385 return this.event_;
        386};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/browserfeature.js.src.html b/docs/source/lib/goog/events/browserfeature.js.src.html new file mode 100644 index 0000000..e7faa18 --- /dev/null +++ b/docs/source/lib/goog/events/browserfeature.js.src.html @@ -0,0 +1 @@ +browserfeature.js

        lib/goog/events/browserfeature.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Browser capability checks for the events package.
        17 *
        18 */
        19
        20
        21goog.provide('goog.events.BrowserFeature');
        22
        23goog.require('goog.userAgent');
        24
        25
        26/**
        27 * Enum of browser capabilities.
        28 * @enum {boolean}
        29 */
        30goog.events.BrowserFeature = {
        31 /**
        32 * Whether the button attribute of the event is W3C compliant. False in
        33 * Internet Explorer prior to version 9; document-version dependent.
        34 */
        35 HAS_W3C_BUTTON: !goog.userAgent.IE ||
        36 goog.userAgent.isDocumentModeOrHigher(9),
        37
        38 /**
        39 * Whether the browser supports full W3C event model.
        40 */
        41 HAS_W3C_EVENT_SUPPORT: !goog.userAgent.IE ||
        42 goog.userAgent.isDocumentModeOrHigher(9),
        43
        44 /**
        45 * To prevent default in IE7-8 for certain keydown events we need set the
        46 * keyCode to -1.
        47 */
        48 SET_KEY_CODE_TO_PREVENT_DEFAULT: goog.userAgent.IE &&
        49 !goog.userAgent.isVersionOrHigher('9'),
        50
        51 /**
        52 * Whether the {@code navigator.onLine} property is supported.
        53 */
        54 HAS_NAVIGATOR_ONLINE_PROPERTY: !goog.userAgent.WEBKIT ||
        55 goog.userAgent.isVersionOrHigher('528'),
        56
        57 /**
        58 * Whether HTML5 network online/offline events are supported.
        59 */
        60 HAS_HTML5_NETWORK_EVENT_SUPPORT:
        61 goog.userAgent.GECKO && goog.userAgent.isVersionOrHigher('1.9b') ||
        62 goog.userAgent.IE && goog.userAgent.isVersionOrHigher('8') ||
        63 goog.userAgent.OPERA && goog.userAgent.isVersionOrHigher('9.5') ||
        64 goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('528'),
        65
        66 /**
        67 * Whether HTML5 network events fire on document.body, or otherwise the
        68 * window.
        69 */
        70 HTML5_NETWORK_EVENTS_FIRE_ON_BODY:
        71 goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('8') ||
        72 goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9'),
        73
        74 /**
        75 * Whether touch is enabled in the browser.
        76 */
        77 TOUCH_ENABLED:
        78 ('ontouchstart' in goog.global ||
        79 !!(goog.global['document'] &&
        80 document.documentElement &&
        81 'ontouchstart' in document.documentElement) ||
        82 // IE10 uses non-standard touch events, so it has a different check.
        83 !!(goog.global['navigator'] &&
        84 goog.global['navigator']['msMaxTouchPoints']))
        85};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/event.js.src.html b/docs/source/lib/goog/events/event.js.src.html new file mode 100644 index 0000000..23bb1d5 --- /dev/null +++ b/docs/source/lib/goog/events/event.js.src.html @@ -0,0 +1 @@ +event.js

        lib/goog/events/event.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A base class for event objects.
        17 *
        18 */
        19
        20
        21goog.provide('goog.events.Event');
        22goog.provide('goog.events.EventLike');
        23
        24/**
        25 * goog.events.Event no longer depends on goog.Disposable. Keep requiring
        26 * goog.Disposable here to not break projects which assume this dependency.
        27 * @suppress {extraRequire}
        28 */
        29goog.require('goog.Disposable');
        30goog.require('goog.events.EventId');
        31
        32
        33/**
        34 * A typedef for event like objects that are dispatchable via the
        35 * goog.events.dispatchEvent function. strings are treated as the type for a
        36 * goog.events.Event. Objects are treated as an extension of a new
        37 * goog.events.Event with the type property of the object being used as the type
        38 * of the Event.
        39 * @typedef {string|Object|goog.events.Event|goog.events.EventId}
        40 */
        41goog.events.EventLike;
        42
        43
        44
        45/**
        46 * A base class for event objects, so that they can support preventDefault and
        47 * stopPropagation.
        48 *
        49 * @param {string|!goog.events.EventId} type Event Type.
        50 * @param {Object=} opt_target Reference to the object that is the target of
        51 * this event. It has to implement the {@code EventTarget} interface
        52 * declared at {@link http://developer.mozilla.org/en/DOM/EventTarget}.
        53 * @constructor
        54 */
        55goog.events.Event = function(type, opt_target) {
        56 /**
        57 * Event type.
        58 * @type {string}
        59 */
        60 this.type = type instanceof goog.events.EventId ? String(type) : type;
        61
        62 /**
        63 * TODO(tbreisacher): The type should probably be
        64 * EventTarget|goog.events.EventTarget.
        65 *
        66 * Target of the event.
        67 * @type {Object|undefined}
        68 */
        69 this.target = opt_target;
        70
        71 /**
        72 * Object that had the listener attached.
        73 * @type {Object|undefined}
        74 */
        75 this.currentTarget = this.target;
        76
        77 /**
        78 * Whether to cancel the event in internal capture/bubble processing for IE.
        79 * @type {boolean}
        80 * @public
        81 * @suppress {underscore|visibility} Technically public, but referencing this
        82 * outside this package is strongly discouraged.
        83 */
        84 this.propagationStopped_ = false;
        85
        86 /**
        87 * Whether the default action has been prevented.
        88 * This is a property to match the W3C specification at
        89 * {@link http://www.w3.org/TR/DOM-Level-3-Events/
        90 * #events-event-type-defaultPrevented}.
        91 * Must be treated as read-only outside the class.
        92 * @type {boolean}
        93 */
        94 this.defaultPrevented = false;
        95
        96 /**
        97 * Return value for in internal capture/bubble processing for IE.
        98 * @type {boolean}
        99 * @public
        100 * @suppress {underscore|visibility} Technically public, but referencing this
        101 * outside this package is strongly discouraged.
        102 */
        103 this.returnValue_ = true;
        104};
        105
        106
        107/**
        108 * Stops event propagation.
        109 */
        110goog.events.Event.prototype.stopPropagation = function() {
        111 this.propagationStopped_ = true;
        112};
        113
        114
        115/**
        116 * Prevents the default action, for example a link redirecting to a url.
        117 */
        118goog.events.Event.prototype.preventDefault = function() {
        119 this.defaultPrevented = true;
        120 this.returnValue_ = false;
        121};
        122
        123
        124/**
        125 * Stops the propagation of the event. It is equivalent to
        126 * {@code e.stopPropagation()}, but can be used as the callback argument of
        127 * {@link goog.events.listen} without declaring another function.
        128 * @param {!goog.events.Event} e An event.
        129 */
        130goog.events.Event.stopPropagation = function(e) {
        131 e.stopPropagation();
        132};
        133
        134
        135/**
        136 * Prevents the default action. It is equivalent to
        137 * {@code e.preventDefault()}, but can be used as the callback argument of
        138 * {@link goog.events.listen} without declaring another function.
        139 * @param {!goog.events.Event} e An event.
        140 */
        141goog.events.Event.preventDefault = function(e) {
        142 e.preventDefault();
        143};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/eventid.js.src.html b/docs/source/lib/goog/events/eventid.js.src.html new file mode 100644 index 0000000..b6ed918 --- /dev/null +++ b/docs/source/lib/goog/events/eventid.js.src.html @@ -0,0 +1 @@ +eventid.js

        lib/goog/events/eventid.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.events.EventId');
        16
        17
        18
        19/**
        20 * A templated class that is used when registering for events. Typical usage:
        21 * <code>
        22 * /** @type {goog.events.EventId<MyEventObj>}
        23 * var myEventId = new goog.events.EventId(
        24 * goog.events.getUniqueId(('someEvent'));
        25 *
        26 * // No need to cast or declare here since the compiler knows the correct
        27 * // type of 'evt' (MyEventObj).
        28 * something.listen(myEventId, function(evt) {});
        29 * </code>
        30 *
        31 * @param {string} eventId
        32 * @template T
        33 * @constructor
        34 * @struct
        35 * @final
        36 */
        37goog.events.EventId = function(eventId) {
        38 /** @const */ this.id = eventId;
        39};
        40
        41
        42/**
        43 * @override
        44 */
        45goog.events.EventId.prototype.toString = function() {
        46 return this.id;
        47};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/events.js.src.html b/docs/source/lib/goog/events/events.js.src.html new file mode 100644 index 0000000..7706988 --- /dev/null +++ b/docs/source/lib/goog/events/events.js.src.html @@ -0,0 +1 @@ +events.js

        lib/goog/events/events.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview An event manager for both native browser event
        17 * targets and custom JavaScript event targets
        18 * ({@code goog.events.Listenable}). This provides an abstraction
        19 * over browsers' event systems.
        20 *
        21 * It also provides a simulation of W3C event model's capture phase in
        22 * Internet Explorer (IE 8 and below). Caveat: the simulation does not
        23 * interact well with listeners registered directly on the elements
        24 * (bypassing goog.events) or even with listeners registered via
        25 * goog.events in a separate JS binary. In these cases, we provide
        26 * no ordering guarantees.
        27 *
        28 * The listeners will receive a "patched" event object. Such event object
        29 * contains normalized values for certain event properties that differs in
        30 * different browsers.
        31 *
        32 * Example usage:
        33 * <pre>
        34 * goog.events.listen(myNode, 'click', function(e) { alert('woo') });
        35 * goog.events.listen(myNode, 'mouseover', mouseHandler, true);
        36 * goog.events.unlisten(myNode, 'mouseover', mouseHandler, true);
        37 * goog.events.removeAll(myNode);
        38 * </pre>
        39 *
        40 * in IE and event object patching]
        41 * @author arv@google.com (Erik Arvidsson)
        42 *
        43 * @see ../demos/events.html
        44 * @see ../demos/event-propagation.html
        45 * @see ../demos/stopevent.html
        46 */
        47
        48// IMPLEMENTATION NOTES:
        49// goog.events stores an auxiliary data structure on each EventTarget
        50// source being listened on. This allows us to take advantage of GC,
        51// having the data structure GC'd when the EventTarget is GC'd. This
        52// GC behavior is equivalent to using W3C DOM Events directly.
        53
        54goog.provide('goog.events');
        55goog.provide('goog.events.CaptureSimulationMode');
        56goog.provide('goog.events.Key');
        57goog.provide('goog.events.ListenableType');
        58
        59goog.require('goog.asserts');
        60goog.require('goog.debug.entryPointRegistry');
        61goog.require('goog.events.BrowserEvent');
        62goog.require('goog.events.BrowserFeature');
        63goog.require('goog.events.Listenable');
        64goog.require('goog.events.ListenerMap');
        65
        66goog.forwardDeclare('goog.debug.ErrorHandler');
        67goog.forwardDeclare('goog.events.EventWrapper');
        68
        69
        70/**
        71 * @typedef {number|goog.events.ListenableKey}
        72 */
        73goog.events.Key;
        74
        75
        76/**
        77 * @typedef {EventTarget|goog.events.Listenable}
        78 */
        79goog.events.ListenableType;
        80
        81
        82/**
        83 * Property name on a native event target for the listener map
        84 * associated with the event target.
        85 * @private @const {string}
        86 */
        87goog.events.LISTENER_MAP_PROP_ = 'closure_lm_' + ((Math.random() * 1e6) | 0);
        88
        89
        90/**
        91 * String used to prepend to IE event types.
        92 * @const
        93 * @private
        94 */
        95goog.events.onString_ = 'on';
        96
        97
        98/**
        99 * Map of computed "on<eventname>" strings for IE event types. Caching
        100 * this removes an extra object allocation in goog.events.listen which
        101 * improves IE6 performance.
        102 * @const
        103 * @dict
        104 * @private
        105 */
        106goog.events.onStringMap_ = {};
        107
        108
        109/**
        110 * @enum {number} Different capture simulation mode for IE8-.
        111 */
        112goog.events.CaptureSimulationMode = {
        113 /**
        114 * Does not perform capture simulation. Will asserts in IE8- when you
        115 * add capture listeners.
        116 */
        117 OFF_AND_FAIL: 0,
        118
        119 /**
        120 * Does not perform capture simulation, silently ignore capture
        121 * listeners.
        122 */
        123 OFF_AND_SILENT: 1,
        124
        125 /**
        126 * Performs capture simulation.
        127 */
        128 ON: 2
        129};
        130
        131
        132/**
        133 * @define {number} The capture simulation mode for IE8-. By default,
        134 * this is ON.
        135 */
        136goog.define('goog.events.CAPTURE_SIMULATION_MODE', 2);
        137
        138
        139/**
        140 * Estimated count of total native listeners.
        141 * @private {number}
        142 */
        143goog.events.listenerCountEstimate_ = 0;
        144
        145
        146/**
        147 * Adds an event listener for a specific event on a native event
        148 * target (such as a DOM element) or an object that has implemented
        149 * {@link goog.events.Listenable}. A listener can only be added once
        150 * to an object and if it is added again the key for the listener is
        151 * returned. Note that if the existing listener is a one-off listener
        152 * (registered via listenOnce), it will no longer be a one-off
        153 * listener after a call to listen().
        154 *
        155 * @param {EventTarget|goog.events.Listenable} src The node to listen
        156 * to events on.
        157 * @param {string|Array<string>|
        158 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
        159 * type Event type or array of event types.
        160 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
        161 * listener Callback method, or an object with a handleEvent function.
        162 * WARNING: passing an Object is now softly deprecated.
        163 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
        164 * false).
        165 * @param {T=} opt_handler Element in whose scope to call the listener.
        166 * @return {goog.events.Key} Unique key for the listener.
        167 * @template T,EVENTOBJ
        168 */
        169goog.events.listen = function(src, type, listener, opt_capt, opt_handler) {
        170 if (goog.isArray(type)) {
        171 for (var i = 0; i < type.length; i++) {
        172 goog.events.listen(src, type[i], listener, opt_capt, opt_handler);
        173 }
        174 return null;
        175 }
        176
        177 listener = goog.events.wrapListener(listener);
        178 if (goog.events.Listenable.isImplementedBy(src)) {
        179 return src.listen(
        180 /** @type {string|!goog.events.EventId} */ (type),
        181 listener, opt_capt, opt_handler);
        182 } else {
        183 return goog.events.listen_(
        184 /** @type {!EventTarget} */ (src),
        185 /** @type {string|!goog.events.EventId} */ (type),
        186 listener, /* callOnce */ false, opt_capt, opt_handler);
        187 }
        188};
        189
        190
        191/**
        192 * Adds an event listener for a specific event on a native event
        193 * target. A listener can only be added once to an object and if it
        194 * is added again the key for the listener is returned.
        195 *
        196 * Note that a one-off listener will not change an existing listener,
        197 * if any. On the other hand a normal listener will change existing
        198 * one-off listener to become a normal listener.
        199 *
        200 * @param {EventTarget} src The node to listen to events on.
        201 * @param {string|!goog.events.EventId} type Event type.
        202 * @param {!Function} listener Callback function.
        203 * @param {boolean} callOnce Whether the listener is a one-off
        204 * listener or otherwise.
        205 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
        206 * false).
        207 * @param {Object=} opt_handler Element in whose scope to call the listener.
        208 * @return {goog.events.ListenableKey} Unique key for the listener.
        209 * @private
        210 */
        211goog.events.listen_ = function(
        212 src, type, listener, callOnce, opt_capt, opt_handler) {
        213 if (!type) {
        214 throw Error('Invalid event type');
        215 }
        216
        217 var capture = !!opt_capt;
        218 if (capture && !goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
        219 if (goog.events.CAPTURE_SIMULATION_MODE ==
        220 goog.events.CaptureSimulationMode.OFF_AND_FAIL) {
        221 goog.asserts.fail('Can not register capture listener in IE8-.');
        222 return null;
        223 } else if (goog.events.CAPTURE_SIMULATION_MODE ==
        224 goog.events.CaptureSimulationMode.OFF_AND_SILENT) {
        225 return null;
        226 }
        227 }
        228
        229 var listenerMap = goog.events.getListenerMap_(src);
        230 if (!listenerMap) {
        231 src[goog.events.LISTENER_MAP_PROP_] = listenerMap =
        232 new goog.events.ListenerMap(src);
        233 }
        234
        235 var listenerObj = listenerMap.add(
        236 type, listener, callOnce, opt_capt, opt_handler);
        237
        238 // If the listenerObj already has a proxy, it has been set up
        239 // previously. We simply return.
        240 if (listenerObj.proxy) {
        241 return listenerObj;
        242 }
        243
        244 var proxy = goog.events.getProxy();
        245 listenerObj.proxy = proxy;
        246
        247 proxy.src = src;
        248 proxy.listener = listenerObj;
        249
        250 // Attach the proxy through the browser's API
        251 if (src.addEventListener) {
        252 src.addEventListener(type.toString(), proxy, capture);
        253 } else if (src.attachEvent) {
        254 // The else if above used to be an unconditional else. It would call
        255 // exception on IE11, spoiling the day of some callers. The previous
        256 // incarnation of this code, from 2007, indicates that it replaced an
        257 // earlier still version that caused excess allocations on IE6.
        258 src.attachEvent(goog.events.getOnString_(type.toString()), proxy);
        259 } else {
        260 throw Error('addEventListener and attachEvent are unavailable.');
        261 }
        262
        263 goog.events.listenerCountEstimate_++;
        264 return listenerObj;
        265};
        266
        267
        268/**
        269 * Helper function for returning a proxy function.
        270 * @return {!Function} A new or reused function object.
        271 */
        272goog.events.getProxy = function() {
        273 var proxyCallbackFunction = goog.events.handleBrowserEvent_;
        274 // Use a local var f to prevent one allocation.
        275 var f = goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT ?
        276 function(eventObject) {
        277 return proxyCallbackFunction.call(f.src, f.listener, eventObject);
        278 } :
        279 function(eventObject) {
        280 var v = proxyCallbackFunction.call(f.src, f.listener, eventObject);
        281 // NOTE(chrishenry): In IE, we hack in a capture phase. However, if
        282 // there is inline event handler which tries to prevent default (for
        283 // example <a href="..." onclick="return false">...</a>) in a
        284 // descendant element, the prevent default will be overridden
        285 // by this listener if this listener were to return true. Hence, we
        286 // return undefined.
        287 if (!v) return v;
        288 };
        289 return f;
        290};
        291
        292
        293/**
        294 * Adds an event listener for a specific event on a native event
        295 * target (such as a DOM element) or an object that has implemented
        296 * {@link goog.events.Listenable}. After the event has fired the event
        297 * listener is removed from the target.
        298 *
        299 * If an existing listener already exists, listenOnce will do
        300 * nothing. In particular, if the listener was previously registered
        301 * via listen(), listenOnce() will not turn the listener into a
        302 * one-off listener. Similarly, if there is already an existing
        303 * one-off listener, listenOnce does not modify the listeners (it is
        304 * still a once listener).
        305 *
        306 * @param {EventTarget|goog.events.Listenable} src The node to listen
        307 * to events on.
        308 * @param {string|Array<string>|
        309 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
        310 * type Event type or array of event types.
        311 * @param {function(this:T, EVENTOBJ):?|{handleEvent:function(?):?}|null}
        312 * listener Callback method.
        313 * @param {boolean=} opt_capt Fire in capture phase?.
        314 * @param {T=} opt_handler Element in whose scope to call the listener.
        315 * @return {goog.events.Key} Unique key for the listener.
        316 * @template T,EVENTOBJ
        317 */
        318goog.events.listenOnce = function(src, type, listener, opt_capt, opt_handler) {
        319 if (goog.isArray(type)) {
        320 for (var i = 0; i < type.length; i++) {
        321 goog.events.listenOnce(src, type[i], listener, opt_capt, opt_handler);
        322 }
        323 return null;
        324 }
        325
        326 listener = goog.events.wrapListener(listener);
        327 if (goog.events.Listenable.isImplementedBy(src)) {
        328 return src.listenOnce(
        329 /** @type {string|!goog.events.EventId} */ (type),
        330 listener, opt_capt, opt_handler);
        331 } else {
        332 return goog.events.listen_(
        333 /** @type {!EventTarget} */ (src),
        334 /** @type {string|!goog.events.EventId} */ (type),
        335 listener, /* callOnce */ true, opt_capt, opt_handler);
        336 }
        337};
        338
        339
        340/**
        341 * Adds an event listener with a specific event wrapper on a DOM Node or an
        342 * object that has implemented {@link goog.events.Listenable}. A listener can
        343 * only be added once to an object.
        344 *
        345 * @param {EventTarget|goog.events.Listenable} src The target to
        346 * listen to events on.
        347 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
        348 * @param {function(this:T, ?):?|{handleEvent:function(?):?}|null} listener
        349 * Callback method, or an object with a handleEvent function.
        350 * @param {boolean=} opt_capt Whether to fire in capture phase (defaults to
        351 * false).
        352 * @param {T=} opt_handler Element in whose scope to call the listener.
        353 * @template T
        354 */
        355goog.events.listenWithWrapper = function(src, wrapper, listener, opt_capt,
        356 opt_handler) {
        357 wrapper.listen(src, listener, opt_capt, opt_handler);
        358};
        359
        360
        361/**
        362 * Removes an event listener which was added with listen().
        363 *
        364 * @param {EventTarget|goog.events.Listenable} src The target to stop
        365 * listening to events on.
        366 * @param {string|Array<string>|
        367 * !goog.events.EventId<EVENTOBJ>|!Array<!goog.events.EventId<EVENTOBJ>>}
        368 * type Event type or array of event types to unlisten to.
        369 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
        370 * listener function to remove.
        371 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
        372 * whether the listener is fired during the capture or bubble phase of the
        373 * event.
        374 * @param {Object=} opt_handler Element in whose scope to call the listener.
        375 * @return {?boolean} indicating whether the listener was there to remove.
        376 * @template EVENTOBJ
        377 */
        378goog.events.unlisten = function(src, type, listener, opt_capt, opt_handler) {
        379 if (goog.isArray(type)) {
        380 for (var i = 0; i < type.length; i++) {
        381 goog.events.unlisten(src, type[i], listener, opt_capt, opt_handler);
        382 }
        383 return null;
        384 }
        385
        386 listener = goog.events.wrapListener(listener);
        387 if (goog.events.Listenable.isImplementedBy(src)) {
        388 return src.unlisten(
        389 /** @type {string|!goog.events.EventId} */ (type),
        390 listener, opt_capt, opt_handler);
        391 }
        392
        393 if (!src) {
        394 // TODO(chrishenry): We should tighten the API to only accept
        395 // non-null objects, or add an assertion here.
        396 return false;
        397 }
        398
        399 var capture = !!opt_capt;
        400 var listenerMap = goog.events.getListenerMap_(
        401 /** @type {!EventTarget} */ (src));
        402 if (listenerMap) {
        403 var listenerObj = listenerMap.getListener(
        404 /** @type {string|!goog.events.EventId} */ (type),
        405 listener, capture, opt_handler);
        406 if (listenerObj) {
        407 return goog.events.unlistenByKey(listenerObj);
        408 }
        409 }
        410
        411 return false;
        412};
        413
        414
        415/**
        416 * Removes an event listener which was added with listen() by the key
        417 * returned by listen().
        418 *
        419 * @param {goog.events.Key} key The key returned by listen() for this
        420 * event listener.
        421 * @return {boolean} indicating whether the listener was there to remove.
        422 */
        423goog.events.unlistenByKey = function(key) {
        424 // TODO(chrishenry): Remove this check when tests that rely on this
        425 // are fixed.
        426 if (goog.isNumber(key)) {
        427 return false;
        428 }
        429
        430 var listener = /** @type {goog.events.ListenableKey} */ (key);
        431 if (!listener || listener.removed) {
        432 return false;
        433 }
        434
        435 var src = listener.src;
        436 if (goog.events.Listenable.isImplementedBy(src)) {
        437 return src.unlistenByKey(listener);
        438 }
        439
        440 var type = listener.type;
        441 var proxy = listener.proxy;
        442 if (src.removeEventListener) {
        443 src.removeEventListener(type, proxy, listener.capture);
        444 } else if (src.detachEvent) {
        445 src.detachEvent(goog.events.getOnString_(type), proxy);
        446 }
        447 goog.events.listenerCountEstimate_--;
        448
        449 var listenerMap = goog.events.getListenerMap_(
        450 /** @type {!EventTarget} */ (src));
        451 // TODO(chrishenry): Try to remove this conditional and execute the
        452 // first branch always. This should be safe.
        453 if (listenerMap) {
        454 listenerMap.removeByKey(listener);
        455 if (listenerMap.getTypeCount() == 0) {
        456 // Null the src, just because this is simple to do (and useful
        457 // for IE <= 7).
        458 listenerMap.src = null;
        459 // We don't use delete here because IE does not allow delete
        460 // on a window object.
        461 src[goog.events.LISTENER_MAP_PROP_] = null;
        462 }
        463 } else {
        464 listener.markAsRemoved();
        465 }
        466
        467 return true;
        468};
        469
        470
        471/**
        472 * Removes an event listener which was added with listenWithWrapper().
        473 *
        474 * @param {EventTarget|goog.events.Listenable} src The target to stop
        475 * listening to events on.
        476 * @param {goog.events.EventWrapper} wrapper Event wrapper to use.
        477 * @param {function(?):?|{handleEvent:function(?):?}|null} listener The
        478 * listener function to remove.
        479 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
        480 * whether the listener is fired during the capture or bubble phase of the
        481 * event.
        482 * @param {Object=} opt_handler Element in whose scope to call the listener.
        483 */
        484goog.events.unlistenWithWrapper = function(src, wrapper, listener, opt_capt,
        485 opt_handler) {
        486 wrapper.unlisten(src, listener, opt_capt, opt_handler);
        487};
        488
        489
        490/**
        491 * Removes all listeners from an object. You can also optionally
        492 * remove listeners of a particular type.
        493 *
        494 * @param {Object|undefined} obj Object to remove listeners from. Must be an
        495 * EventTarget or a goog.events.Listenable.
        496 * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
        497 * Default is all types.
        498 * @return {number} Number of listeners removed.
        499 */
        500goog.events.removeAll = function(obj, opt_type) {
        501 // TODO(chrishenry): Change the type of obj to
        502 // (!EventTarget|!goog.events.Listenable).
        503
        504 if (!obj) {
        505 return 0;
        506 }
        507
        508 if (goog.events.Listenable.isImplementedBy(obj)) {
        509 return obj.removeAllListeners(opt_type);
        510 }
        511
        512 var listenerMap = goog.events.getListenerMap_(
        513 /** @type {!EventTarget} */ (obj));
        514 if (!listenerMap) {
        515 return 0;
        516 }
        517
        518 var count = 0;
        519 var typeStr = opt_type && opt_type.toString();
        520 for (var type in listenerMap.listeners) {
        521 if (!typeStr || type == typeStr) {
        522 // Clone so that we don't need to worry about unlistenByKey
        523 // changing the content of the ListenerMap.
        524 var listeners = listenerMap.listeners[type].concat();
        525 for (var i = 0; i < listeners.length; ++i) {
        526 if (goog.events.unlistenByKey(listeners[i])) {
        527 ++count;
        528 }
        529 }
        530 }
        531 }
        532 return count;
        533};
        534
        535
        536/**
        537 * Gets the listeners for a given object, type and capture phase.
        538 *
        539 * @param {Object} obj Object to get listeners for.
        540 * @param {string|!goog.events.EventId} type Event type.
        541 * @param {boolean} capture Capture phase?.
        542 * @return {Array<goog.events.Listener>} Array of listener objects.
        543 */
        544goog.events.getListeners = function(obj, type, capture) {
        545 if (goog.events.Listenable.isImplementedBy(obj)) {
        546 return obj.getListeners(type, capture);
        547 } else {
        548 if (!obj) {
        549 // TODO(chrishenry): We should tighten the API to accept
        550 // !EventTarget|goog.events.Listenable, and add an assertion here.
        551 return [];
        552 }
        553
        554 var listenerMap = goog.events.getListenerMap_(
        555 /** @type {!EventTarget} */ (obj));
        556 return listenerMap ? listenerMap.getListeners(type, capture) : [];
        557 }
        558};
        559
        560
        561/**
        562 * Gets the goog.events.Listener for the event or null if no such listener is
        563 * in use.
        564 *
        565 * @param {EventTarget|goog.events.Listenable} src The target from
        566 * which to get listeners.
        567 * @param {?string|!goog.events.EventId<EVENTOBJ>} type The type of the event.
        568 * @param {function(EVENTOBJ):?|{handleEvent:function(?):?}|null} listener The
        569 * listener function to get.
        570 * @param {boolean=} opt_capt In DOM-compliant browsers, this determines
        571 * whether the listener is fired during the
        572 * capture or bubble phase of the event.
        573 * @param {Object=} opt_handler Element in whose scope to call the listener.
        574 * @return {goog.events.ListenableKey} the found listener or null if not found.
        575 * @template EVENTOBJ
        576 */
        577goog.events.getListener = function(src, type, listener, opt_capt, opt_handler) {
        578 // TODO(chrishenry): Change type from ?string to string, or add assertion.
        579 type = /** @type {string} */ (type);
        580 listener = goog.events.wrapListener(listener);
        581 var capture = !!opt_capt;
        582 if (goog.events.Listenable.isImplementedBy(src)) {
        583 return src.getListener(type, listener, capture, opt_handler);
        584 }
        585
        586 if (!src) {
        587 // TODO(chrishenry): We should tighten the API to only accept
        588 // non-null objects, or add an assertion here.
        589 return null;
        590 }
        591
        592 var listenerMap = goog.events.getListenerMap_(
        593 /** @type {!EventTarget} */ (src));
        594 if (listenerMap) {
        595 return listenerMap.getListener(type, listener, capture, opt_handler);
        596 }
        597 return null;
        598};
        599
        600
        601/**
        602 * Returns whether an event target has any active listeners matching the
        603 * specified signature. If either the type or capture parameters are
        604 * unspecified, the function will match on the remaining criteria.
        605 *
        606 * @param {EventTarget|goog.events.Listenable} obj Target to get
        607 * listeners for.
        608 * @param {string|!goog.events.EventId=} opt_type Event type.
        609 * @param {boolean=} opt_capture Whether to check for capture or bubble-phase
        610 * listeners.
        611 * @return {boolean} Whether an event target has one or more listeners matching
        612 * the requested type and/or capture phase.
        613 */
        614goog.events.hasListener = function(obj, opt_type, opt_capture) {
        615 if (goog.events.Listenable.isImplementedBy(obj)) {
        616 return obj.hasListener(opt_type, opt_capture);
        617 }
        618
        619 var listenerMap = goog.events.getListenerMap_(
        620 /** @type {!EventTarget} */ (obj));
        621 return !!listenerMap && listenerMap.hasListener(opt_type, opt_capture);
        622};
        623
        624
        625/**
        626 * Provides a nice string showing the normalized event objects public members
        627 * @param {Object} e Event Object.
        628 * @return {string} String of the public members of the normalized event object.
        629 */
        630goog.events.expose = function(e) {
        631 var str = [];
        632 for (var key in e) {
        633 if (e[key] && e[key].id) {
        634 str.push(key + ' = ' + e[key] + ' (' + e[key].id + ')');
        635 } else {
        636 str.push(key + ' = ' + e[key]);
        637 }
        638 }
        639 return str.join('\n');
        640};
        641
        642
        643/**
        644 * Returns a string with on prepended to the specified type. This is used for IE
        645 * which expects "on" to be prepended. This function caches the string in order
        646 * to avoid extra allocations in steady state.
        647 * @param {string} type Event type.
        648 * @return {string} The type string with 'on' prepended.
        649 * @private
        650 */
        651goog.events.getOnString_ = function(type) {
        652 if (type in goog.events.onStringMap_) {
        653 return goog.events.onStringMap_[type];
        654 }
        655 return goog.events.onStringMap_[type] = goog.events.onString_ + type;
        656};
        657
        658
        659/**
        660 * Fires an object's listeners of a particular type and phase
        661 *
        662 * @param {Object} obj Object whose listeners to call.
        663 * @param {string|!goog.events.EventId} type Event type.
        664 * @param {boolean} capture Which event phase.
        665 * @param {Object} eventObject Event object to be passed to listener.
        666 * @return {boolean} True if all listeners returned true else false.
        667 */
        668goog.events.fireListeners = function(obj, type, capture, eventObject) {
        669 if (goog.events.Listenable.isImplementedBy(obj)) {
        670 return obj.fireListeners(type, capture, eventObject);
        671 }
        672
        673 return goog.events.fireListeners_(obj, type, capture, eventObject);
        674};
        675
        676
        677/**
        678 * Fires an object's listeners of a particular type and phase.
        679 * @param {Object} obj Object whose listeners to call.
        680 * @param {string|!goog.events.EventId} type Event type.
        681 * @param {boolean} capture Which event phase.
        682 * @param {Object} eventObject Event object to be passed to listener.
        683 * @return {boolean} True if all listeners returned true else false.
        684 * @private
        685 */
        686goog.events.fireListeners_ = function(obj, type, capture, eventObject) {
        687 /** @type {boolean} */
        688 var retval = true;
        689
        690 var listenerMap = goog.events.getListenerMap_(
        691 /** @type {EventTarget} */ (obj));
        692 if (listenerMap) {
        693 // TODO(chrishenry): Original code avoids array creation when there
        694 // is no listener, so we do the same. If this optimization turns
        695 // out to be not required, we can replace this with
        696 // listenerMap.getListeners(type, capture) instead, which is simpler.
        697 var listenerArray = listenerMap.listeners[type.toString()];
        698 if (listenerArray) {
        699 listenerArray = listenerArray.concat();
        700 for (var i = 0; i < listenerArray.length; i++) {
        701 var listener = listenerArray[i];
        702 // We might not have a listener if the listener was removed.
        703 if (listener && listener.capture == capture && !listener.removed) {
        704 var result = goog.events.fireListener(listener, eventObject);
        705 retval = retval && (result !== false);
        706 }
        707 }
        708 }
        709 }
        710 return retval;
        711};
        712
        713
        714/**
        715 * Fires a listener with a set of arguments
        716 *
        717 * @param {goog.events.Listener} listener The listener object to call.
        718 * @param {Object} eventObject The event object to pass to the listener.
        719 * @return {boolean} Result of listener.
        720 */
        721goog.events.fireListener = function(listener, eventObject) {
        722 var listenerFn = listener.listener;
        723 var listenerHandler = listener.handler || listener.src;
        724
        725 if (listener.callOnce) {
        726 goog.events.unlistenByKey(listener);
        727 }
        728 return listenerFn.call(listenerHandler, eventObject);
        729};
        730
        731
        732/**
        733 * Gets the total number of listeners currently in the system.
        734 * @return {number} Number of listeners.
        735 * @deprecated This returns estimated count, now that Closure no longer
        736 * stores a central listener registry. We still return an estimation
        737 * to keep existing listener-related tests passing. In the near future,
        738 * this function will be removed.
        739 */
        740goog.events.getTotalListenerCount = function() {
        741 return goog.events.listenerCountEstimate_;
        742};
        743
        744
        745/**
        746 * Dispatches an event (or event like object) and calls all listeners
        747 * listening for events of this type. The type of the event is decided by the
        748 * type property on the event object.
        749 *
        750 * If any of the listeners returns false OR calls preventDefault then this
        751 * function will return false. If one of the capture listeners calls
        752 * stopPropagation, then the bubble listeners won't fire.
        753 *
        754 * @param {goog.events.Listenable} src The event target.
        755 * @param {goog.events.EventLike} e Event object.
        756 * @return {boolean} If anyone called preventDefault on the event object (or
        757 * if any of the handlers returns false) this will also return false.
        758 * If there are no handlers, or if all handlers return true, this returns
        759 * true.
        760 */
        761goog.events.dispatchEvent = function(src, e) {
        762 goog.asserts.assert(
        763 goog.events.Listenable.isImplementedBy(src),
        764 'Can not use goog.events.dispatchEvent with ' +
        765 'non-goog.events.Listenable instance.');
        766 return src.dispatchEvent(e);
        767};
        768
        769
        770/**
        771 * Installs exception protection for the browser event entry point using the
        772 * given error handler.
        773 *
        774 * @param {goog.debug.ErrorHandler} errorHandler Error handler with which to
        775 * protect the entry point.
        776 */
        777goog.events.protectBrowserEventEntryPoint = function(errorHandler) {
        778 goog.events.handleBrowserEvent_ = errorHandler.protectEntryPoint(
        779 goog.events.handleBrowserEvent_);
        780};
        781
        782
        783/**
        784 * Handles an event and dispatches it to the correct listeners. This
        785 * function is a proxy for the real listener the user specified.
        786 *
        787 * @param {goog.events.Listener} listener The listener object.
        788 * @param {Event=} opt_evt Optional event object that gets passed in via the
        789 * native event handlers.
        790 * @return {boolean} Result of the event handler.
        791 * @this {EventTarget} The object or Element that fired the event.
        792 * @private
        793 */
        794goog.events.handleBrowserEvent_ = function(listener, opt_evt) {
        795 if (listener.removed) {
        796 return true;
        797 }
        798
        799 // Synthesize event propagation if the browser does not support W3C
        800 // event model.
        801 if (!goog.events.BrowserFeature.HAS_W3C_EVENT_SUPPORT) {
        802 var ieEvent = opt_evt ||
        803 /** @type {Event} */ (goog.getObjectByName('window.event'));
        804 var evt = new goog.events.BrowserEvent(ieEvent, this);
        805 /** @type {boolean} */
        806 var retval = true;
        807
        808 if (goog.events.CAPTURE_SIMULATION_MODE ==
        809 goog.events.CaptureSimulationMode.ON) {
        810 // If we have not marked this event yet, we should perform capture
        811 // simulation.
        812 if (!goog.events.isMarkedIeEvent_(ieEvent)) {
        813 goog.events.markIeEvent_(ieEvent);
        814
        815 var ancestors = [];
        816 for (var parent = evt.currentTarget; parent;
        817 parent = parent.parentNode) {
        818 ancestors.push(parent);
        819 }
        820
        821 // Fire capture listeners.
        822 var type = listener.type;
        823 for (var i = ancestors.length - 1; !evt.propagationStopped_ && i >= 0;
        824 i--) {
        825 evt.currentTarget = ancestors[i];
        826 var result = goog.events.fireListeners_(ancestors[i], type, true, evt);
        827 retval = retval && result;
        828 }
        829
        830 // Fire bubble listeners.
        831 //
        832 // We can technically rely on IE to perform bubble event
        833 // propagation. However, it turns out that IE fires events in
        834 // opposite order of attachEvent registration, which broke
        835 // some code and tests that rely on the order. (While W3C DOM
        836 // Level 2 Events TR leaves the event ordering unspecified,
        837 // modern browsers and W3C DOM Level 3 Events Working Draft
        838 // actually specify the order as the registration order.)
        839 for (var i = 0; !evt.propagationStopped_ && i < ancestors.length; i++) {
        840 evt.currentTarget = ancestors[i];
        841 var result = goog.events.fireListeners_(ancestors[i], type, false, evt);
        842 retval = retval && result;
        843 }
        844 }
        845 } else {
        846 retval = goog.events.fireListener(listener, evt);
        847 }
        848 return retval;
        849 }
        850
        851 // Otherwise, simply fire the listener.
        852 return goog.events.fireListener(
        853 listener, new goog.events.BrowserEvent(opt_evt, this));
        854};
        855
        856
        857/**
        858 * This is used to mark the IE event object so we do not do the Closure pass
        859 * twice for a bubbling event.
        860 * @param {Event} e The IE browser event.
        861 * @private
        862 */
        863goog.events.markIeEvent_ = function(e) {
        864 // Only the keyCode and the returnValue can be changed. We use keyCode for
        865 // non keyboard events.
        866 // event.returnValue is a bit more tricky. It is undefined by default. A
        867 // boolean false prevents the default action. In a window.onbeforeunload and
        868 // the returnValue is non undefined it will be alerted. However, we will only
        869 // modify the returnValue for keyboard events. We can get a problem if non
        870 // closure events sets the keyCode or the returnValue
        871
        872 var useReturnValue = false;
        873
        874 if (e.keyCode == 0) {
        875 // We cannot change the keyCode in case that srcElement is input[type=file].
        876 // We could test that that is the case but that would allocate 3 objects.
        877 // If we use try/catch we will only allocate extra objects in the case of a
        878 // failure.
        879 /** @preserveTry */
        880 try {
        881 e.keyCode = -1;
        882 return;
        883 } catch (ex) {
        884 useReturnValue = true;
        885 }
        886 }
        887
        888 if (useReturnValue ||
        889 /** @type {boolean|undefined} */ (e.returnValue) == undefined) {
        890 e.returnValue = true;
        891 }
        892};
        893
        894
        895/**
        896 * This is used to check if an IE event has already been handled by the Closure
        897 * system so we do not do the Closure pass twice for a bubbling event.
        898 * @param {Event} e The IE browser event.
        899 * @return {boolean} True if the event object has been marked.
        900 * @private
        901 */
        902goog.events.isMarkedIeEvent_ = function(e) {
        903 return e.keyCode < 0 || e.returnValue != undefined;
        904};
        905
        906
        907/**
        908 * Counter to create unique event ids.
        909 * @private {number}
        910 */
        911goog.events.uniqueIdCounter_ = 0;
        912
        913
        914/**
        915 * Creates a unique event id.
        916 *
        917 * @param {string} identifier The identifier.
        918 * @return {string} A unique identifier.
        919 * @idGenerator
        920 */
        921goog.events.getUniqueId = function(identifier) {
        922 return identifier + '_' + goog.events.uniqueIdCounter_++;
        923};
        924
        925
        926/**
        927 * @param {EventTarget} src The source object.
        928 * @return {goog.events.ListenerMap} A listener map for the given
        929 * source object, or null if none exists.
        930 * @private
        931 */
        932goog.events.getListenerMap_ = function(src) {
        933 var listenerMap = src[goog.events.LISTENER_MAP_PROP_];
        934 // IE serializes the property as well (e.g. when serializing outer
        935 // HTML). So we must check that the value is of the correct type.
        936 return listenerMap instanceof goog.events.ListenerMap ? listenerMap : null;
        937};
        938
        939
        940/**
        941 * Expando property for listener function wrapper for Object with
        942 * handleEvent.
        943 * @private @const {string}
        944 */
        945goog.events.LISTENER_WRAPPER_PROP_ = '__closure_events_fn_' +
        946 ((Math.random() * 1e9) >>> 0);
        947
        948
        949/**
        950 * @param {Object|Function} listener The listener function or an
        951 * object that contains handleEvent method.
        952 * @return {!Function} Either the original function or a function that
        953 * calls obj.handleEvent. If the same listener is passed to this
        954 * function more than once, the same function is guaranteed to be
        955 * returned.
        956 */
        957goog.events.wrapListener = function(listener) {
        958 goog.asserts.assert(listener, 'Listener can not be null.');
        959
        960 if (goog.isFunction(listener)) {
        961 return listener;
        962 }
        963
        964 goog.asserts.assert(
        965 listener.handleEvent, 'An object listener must have handleEvent method.');
        966 if (!listener[goog.events.LISTENER_WRAPPER_PROP_]) {
        967 listener[goog.events.LISTENER_WRAPPER_PROP_] =
        968 function(e) { return listener.handleEvent(e); };
        969 }
        970 return listener[goog.events.LISTENER_WRAPPER_PROP_];
        971};
        972
        973
        974// Register the browser event handler as an entry point, so that
        975// it can be monitored for exception handling, etc.
        976goog.debug.entryPointRegistry.register(
        977 /**
        978 * @param {function(!Function): !Function} transformer The transforming
        979 * function.
        980 */
        981 function(transformer) {
        982 goog.events.handleBrowserEvent_ = transformer(
        983 goog.events.handleBrowserEvent_);
        984 });
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/eventtarget.js.src.html b/docs/source/lib/goog/events/eventtarget.js.src.html new file mode 100644 index 0000000..ea50cac --- /dev/null +++ b/docs/source/lib/goog/events/eventtarget.js.src.html @@ -0,0 +1 @@ +eventtarget.js

        lib/goog/events/eventtarget.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A disposable implementation of a custom
        17 * listenable/event target. See also: documentation for
        18 * {@code goog.events.Listenable}.
        19 *
        20 * @author arv@google.com (Erik Arvidsson) [Original implementation]
        21 * @see ../demos/eventtarget.html
        22 * @see goog.events.Listenable
        23 */
        24
        25goog.provide('goog.events.EventTarget');
        26
        27goog.require('goog.Disposable');
        28goog.require('goog.asserts');
        29goog.require('goog.events');
        30goog.require('goog.events.Event');
        31goog.require('goog.events.Listenable');
        32goog.require('goog.events.ListenerMap');
        33goog.require('goog.object');
        34
        35
        36
        37/**
        38 * An implementation of {@code goog.events.Listenable} with full W3C
        39 * EventTarget-like support (capture/bubble mechanism, stopping event
        40 * propagation, preventing default actions).
        41 *
        42 * You may subclass this class to turn your class into a Listenable.
        43 *
        44 * Unless propagation is stopped, an event dispatched by an
        45 * EventTarget will bubble to the parent returned by
        46 * {@code getParentEventTarget}. To set the parent, call
        47 * {@code setParentEventTarget}. Subclasses that don't support
        48 * changing the parent can override the setter to throw an error.
        49 *
        50 * Example usage:
        51 * <pre>
        52 * var source = new goog.events.EventTarget();
        53 * function handleEvent(e) {
        54 * alert('Type: ' + e.type + '; Target: ' + e.target);
        55 * }
        56 * source.listen('foo', handleEvent);
        57 * // Or: goog.events.listen(source, 'foo', handleEvent);
        58 * ...
        59 * source.dispatchEvent('foo'); // will call handleEvent
        60 * ...
        61 * source.unlisten('foo', handleEvent);
        62 * // Or: goog.events.unlisten(source, 'foo', handleEvent);
        63 * </pre>
        64 *
        65 * @constructor
        66 * @extends {goog.Disposable}
        67 * @implements {goog.events.Listenable}
        68 */
        69goog.events.EventTarget = function() {
        70 goog.Disposable.call(this);
        71
        72 /**
        73 * Maps of event type to an array of listeners.
        74 * @private {!goog.events.ListenerMap}
        75 */
        76 this.eventTargetListeners_ = new goog.events.ListenerMap(this);
        77
        78 /**
        79 * The object to use for event.target. Useful when mixing in an
        80 * EventTarget to another object.
        81 * @private {!Object}
        82 */
        83 this.actualEventTarget_ = this;
        84
        85 /**
        86 * Parent event target, used during event bubbling.
        87 *
        88 * TODO(chrishenry): Change this to goog.events.Listenable. This
        89 * currently breaks people who expect getParentEventTarget to return
        90 * goog.events.EventTarget.
        91 *
        92 * @private {goog.events.EventTarget}
        93 */
        94 this.parentEventTarget_ = null;
        95};
        96goog.inherits(goog.events.EventTarget, goog.Disposable);
        97goog.events.Listenable.addImplementation(goog.events.EventTarget);
        98
        99
        100/**
        101 * An artificial cap on the number of ancestors you can have. This is mainly
        102 * for loop detection.
        103 * @const {number}
        104 * @private
        105 */
        106goog.events.EventTarget.MAX_ANCESTORS_ = 1000;
        107
        108
        109/**
        110 * Returns the parent of this event target to use for bubbling.
        111 *
        112 * @return {goog.events.EventTarget} The parent EventTarget or null if
        113 * there is no parent.
        114 * @override
        115 */
        116goog.events.EventTarget.prototype.getParentEventTarget = function() {
        117 return this.parentEventTarget_;
        118};
        119
        120
        121/**
        122 * Sets the parent of this event target to use for capture/bubble
        123 * mechanism.
        124 * @param {goog.events.EventTarget} parent Parent listenable (null if none).
        125 */
        126goog.events.EventTarget.prototype.setParentEventTarget = function(parent) {
        127 this.parentEventTarget_ = parent;
        128};
        129
        130
        131/**
        132 * Adds an event listener to the event target. The same handler can only be
        133 * added once per the type. Even if you add the same handler multiple times
        134 * using the same type then it will only be called once when the event is
        135 * dispatched.
        136 *
        137 * @param {string} type The type of the event to listen for.
        138 * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
        139 * to handle the event. The handler can also be an object that implements
        140 * the handleEvent method which takes the event object as argument.
        141 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
        142 * whether the listener is fired during the capture or bubble phase
        143 * of the event.
        144 * @param {Object=} opt_handlerScope Object in whose scope to call
        145 * the listener.
        146 * @deprecated Use {@code #listen} instead, when possible. Otherwise, use
        147 * {@code goog.events.listen} if you are passing Object
        148 * (instead of Function) as handler.
        149 */
        150goog.events.EventTarget.prototype.addEventListener = function(
        151 type, handler, opt_capture, opt_handlerScope) {
        152 goog.events.listen(this, type, handler, opt_capture, opt_handlerScope);
        153};
        154
        155
        156/**
        157 * Removes an event listener from the event target. The handler must be the
        158 * same object as the one added. If the handler has not been added then
        159 * nothing is done.
        160 *
        161 * @param {string} type The type of the event to listen for.
        162 * @param {function(?):?|{handleEvent:function(?):?}|null} handler The function
        163 * to handle the event. The handler can also be an object that implements
        164 * the handleEvent method which takes the event object as argument.
        165 * @param {boolean=} opt_capture In DOM-compliant browsers, this determines
        166 * whether the listener is fired during the capture or bubble phase
        167 * of the event.
        168 * @param {Object=} opt_handlerScope Object in whose scope to call
        169 * the listener.
        170 * @deprecated Use {@code #unlisten} instead, when possible. Otherwise, use
        171 * {@code goog.events.unlisten} if you are passing Object
        172 * (instead of Function) as handler.
        173 */
        174goog.events.EventTarget.prototype.removeEventListener = function(
        175 type, handler, opt_capture, opt_handlerScope) {
        176 goog.events.unlisten(this, type, handler, opt_capture, opt_handlerScope);
        177};
        178
        179
        180/** @override */
        181goog.events.EventTarget.prototype.dispatchEvent = function(e) {
        182 this.assertInitialized_();
        183
        184 var ancestorsTree, ancestor = this.getParentEventTarget();
        185 if (ancestor) {
        186 ancestorsTree = [];
        187 var ancestorCount = 1;
        188 for (; ancestor; ancestor = ancestor.getParentEventTarget()) {
        189 ancestorsTree.push(ancestor);
        190 goog.asserts.assert(
        191 (++ancestorCount < goog.events.EventTarget.MAX_ANCESTORS_),
        192 'infinite loop');
        193 }
        194 }
        195
        196 return goog.events.EventTarget.dispatchEventInternal_(
        197 this.actualEventTarget_, e, ancestorsTree);
        198};
        199
        200
        201/**
        202 * Removes listeners from this object. Classes that extend EventTarget may
        203 * need to override this method in order to remove references to DOM Elements
        204 * and additional listeners.
        205 * @override
        206 */
        207goog.events.EventTarget.prototype.disposeInternal = function() {
        208 goog.events.EventTarget.superClass_.disposeInternal.call(this);
        209
        210 this.removeAllListeners();
        211 this.parentEventTarget_ = null;
        212};
        213
        214
        215/** @override */
        216goog.events.EventTarget.prototype.listen = function(
        217 type, listener, opt_useCapture, opt_listenerScope) {
        218 this.assertInitialized_();
        219 return this.eventTargetListeners_.add(
        220 String(type), listener, false /* callOnce */, opt_useCapture,
        221 opt_listenerScope);
        222};
        223
        224
        225/** @override */
        226goog.events.EventTarget.prototype.listenOnce = function(
        227 type, listener, opt_useCapture, opt_listenerScope) {
        228 return this.eventTargetListeners_.add(
        229 String(type), listener, true /* callOnce */, opt_useCapture,
        230 opt_listenerScope);
        231};
        232
        233
        234/** @override */
        235goog.events.EventTarget.prototype.unlisten = function(
        236 type, listener, opt_useCapture, opt_listenerScope) {
        237 return this.eventTargetListeners_.remove(
        238 String(type), listener, opt_useCapture, opt_listenerScope);
        239};
        240
        241
        242/** @override */
        243goog.events.EventTarget.prototype.unlistenByKey = function(key) {
        244 return this.eventTargetListeners_.removeByKey(key);
        245};
        246
        247
        248/** @override */
        249goog.events.EventTarget.prototype.removeAllListeners = function(opt_type) {
        250 // TODO(chrishenry): Previously, removeAllListeners can be called on
        251 // uninitialized EventTarget, so we preserve that behavior. We
        252 // should remove this when usages that rely on that fact are purged.
        253 if (!this.eventTargetListeners_) {
        254 return 0;
        255 }
        256 return this.eventTargetListeners_.removeAll(opt_type);
        257};
        258
        259
        260/** @override */
        261goog.events.EventTarget.prototype.fireListeners = function(
        262 type, capture, eventObject) {
        263 // TODO(chrishenry): Original code avoids array creation when there
        264 // is no listener, so we do the same. If this optimization turns
        265 // out to be not required, we can replace this with
        266 // getListeners(type, capture) instead, which is simpler.
        267 var listenerArray = this.eventTargetListeners_.listeners[String(type)];
        268 if (!listenerArray) {
        269 return true;
        270 }
        271 listenerArray = listenerArray.concat();
        272
        273 var rv = true;
        274 for (var i = 0; i < listenerArray.length; ++i) {
        275 var listener = listenerArray[i];
        276 // We might not have a listener if the listener was removed.
        277 if (listener && !listener.removed && listener.capture == capture) {
        278 var listenerFn = listener.listener;
        279 var listenerHandler = listener.handler || listener.src;
        280
        281 if (listener.callOnce) {
        282 this.unlistenByKey(listener);
        283 }
        284 rv = listenerFn.call(listenerHandler, eventObject) !== false && rv;
        285 }
        286 }
        287
        288 return rv && eventObject.returnValue_ != false;
        289};
        290
        291
        292/** @override */
        293goog.events.EventTarget.prototype.getListeners = function(type, capture) {
        294 return this.eventTargetListeners_.getListeners(String(type), capture);
        295};
        296
        297
        298/** @override */
        299goog.events.EventTarget.prototype.getListener = function(
        300 type, listener, capture, opt_listenerScope) {
        301 return this.eventTargetListeners_.getListener(
        302 String(type), listener, capture, opt_listenerScope);
        303};
        304
        305
        306/** @override */
        307goog.events.EventTarget.prototype.hasListener = function(
        308 opt_type, opt_capture) {
        309 var id = goog.isDef(opt_type) ? String(opt_type) : undefined;
        310 return this.eventTargetListeners_.hasListener(id, opt_capture);
        311};
        312
        313
        314/**
        315 * Sets the target to be used for {@code event.target} when firing
        316 * event. Mainly used for testing. For example, see
        317 * {@code goog.testing.events.mixinListenable}.
        318 * @param {!Object} target The target.
        319 */
        320goog.events.EventTarget.prototype.setTargetForTesting = function(target) {
        321 this.actualEventTarget_ = target;
        322};
        323
        324
        325/**
        326 * Asserts that the event target instance is initialized properly.
        327 * @private
        328 */
        329goog.events.EventTarget.prototype.assertInitialized_ = function() {
        330 goog.asserts.assert(
        331 this.eventTargetListeners_,
        332 'Event target is not initialized. Did you call the superclass ' +
        333 '(goog.events.EventTarget) constructor?');
        334};
        335
        336
        337/**
        338 * Dispatches the given event on the ancestorsTree.
        339 *
        340 * @param {!Object} target The target to dispatch on.
        341 * @param {goog.events.Event|Object|string} e The event object.
        342 * @param {Array<goog.events.Listenable>=} opt_ancestorsTree The ancestors
        343 * tree of the target, in reverse order from the closest ancestor
        344 * to the root event target. May be null if the target has no ancestor.
        345 * @return {boolean} If anyone called preventDefault on the event object (or
        346 * if any of the listeners returns false) this will also return false.
        347 * @private
        348 */
        349goog.events.EventTarget.dispatchEventInternal_ = function(
        350 target, e, opt_ancestorsTree) {
        351 var type = e.type || /** @type {string} */ (e);
        352
        353 // If accepting a string or object, create a custom event object so that
        354 // preventDefault and stopPropagation work with the event.
        355 if (goog.isString(e)) {
        356 e = new goog.events.Event(e, target);
        357 } else if (!(e instanceof goog.events.Event)) {
        358 var oldEvent = e;
        359 e = new goog.events.Event(type, target);
        360 goog.object.extend(e, oldEvent);
        361 } else {
        362 e.target = e.target || target;
        363 }
        364
        365 var rv = true, currentTarget;
        366
        367 // Executes all capture listeners on the ancestors, if any.
        368 if (opt_ancestorsTree) {
        369 for (var i = opt_ancestorsTree.length - 1; !e.propagationStopped_ && i >= 0;
        370 i--) {
        371 currentTarget = e.currentTarget = opt_ancestorsTree[i];
        372 rv = currentTarget.fireListeners(type, true, e) && rv;
        373 }
        374 }
        375
        376 // Executes capture and bubble listeners on the target.
        377 if (!e.propagationStopped_) {
        378 currentTarget = e.currentTarget = target;
        379 rv = currentTarget.fireListeners(type, true, e) && rv;
        380 if (!e.propagationStopped_) {
        381 rv = currentTarget.fireListeners(type, false, e) && rv;
        382 }
        383 }
        384
        385 // Executes all bubble listeners on the ancestors, if any.
        386 if (opt_ancestorsTree) {
        387 for (i = 0; !e.propagationStopped_ && i < opt_ancestorsTree.length; i++) {
        388 currentTarget = e.currentTarget = opt_ancestorsTree[i];
        389 rv = currentTarget.fireListeners(type, false, e) && rv;
        390 }
        391 }
        392
        393 return rv;
        394};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/eventtype.js.src.html b/docs/source/lib/goog/events/eventtype.js.src.html new file mode 100644 index 0000000..0ff725e --- /dev/null +++ b/docs/source/lib/goog/events/eventtype.js.src.html @@ -0,0 +1 @@ +eventtype.js

        lib/goog/events/eventtype.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Event Types.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 */
        20
        21
        22goog.provide('goog.events.EventType');
        23
        24goog.require('goog.userAgent');
        25
        26
        27/**
        28 * Returns a prefixed event name for the current browser.
        29 * @param {string} eventName The name of the event.
        30 * @return {string} The prefixed event name.
        31 * @suppress {missingRequire|missingProvide}
        32 * @private
        33 */
        34goog.events.getVendorPrefixedName_ = function(eventName) {
        35 return goog.userAgent.WEBKIT ? 'webkit' + eventName :
        36 (goog.userAgent.OPERA ? 'o' + eventName.toLowerCase() :
        37 eventName.toLowerCase());
        38};
        39
        40
        41/**
        42 * Constants for event names.
        43 * @enum {string}
        44 */
        45goog.events.EventType = {
        46 // Mouse events
        47 CLICK: 'click',
        48 RIGHTCLICK: 'rightclick',
        49 DBLCLICK: 'dblclick',
        50 MOUSEDOWN: 'mousedown',
        51 MOUSEUP: 'mouseup',
        52 MOUSEOVER: 'mouseover',
        53 MOUSEOUT: 'mouseout',
        54 MOUSEMOVE: 'mousemove',
        55 MOUSEENTER: 'mouseenter',
        56 MOUSELEAVE: 'mouseleave',
        57 // Select start is non-standard.
        58 // See http://msdn.microsoft.com/en-us/library/ie/ms536969(v=vs.85).aspx.
        59 SELECTSTART: 'selectstart', // IE, Safari, Chrome
        60
        61 // Wheel events
        62 // http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
        63 WHEEL: 'wheel',
        64
        65 // Key events
        66 KEYPRESS: 'keypress',
        67 KEYDOWN: 'keydown',
        68 KEYUP: 'keyup',
        69
        70 // Focus
        71 BLUR: 'blur',
        72 FOCUS: 'focus',
        73 DEACTIVATE: 'deactivate', // IE only
        74 // NOTE: The following two events are not stable in cross-browser usage.
        75 // WebKit and Opera implement DOMFocusIn/Out.
        76 // IE implements focusin/out.
        77 // Gecko implements neither see bug at
        78 // https://bugzilla.mozilla.org/show_bug.cgi?id=396927.
        79 // The DOM Events Level 3 Draft deprecates DOMFocusIn in favor of focusin:
        80 // http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html
        81 // You can use FOCUS in Capture phase until implementations converge.
        82 FOCUSIN: goog.userAgent.IE ? 'focusin' : 'DOMFocusIn',
        83 FOCUSOUT: goog.userAgent.IE ? 'focusout' : 'DOMFocusOut',
        84
        85 // Forms
        86 CHANGE: 'change',
        87 RESET: 'reset',
        88 SELECT: 'select',
        89 SUBMIT: 'submit',
        90 INPUT: 'input',
        91 PROPERTYCHANGE: 'propertychange', // IE only
        92
        93 // Drag and drop
        94 DRAGSTART: 'dragstart',
        95 DRAG: 'drag',
        96 DRAGENTER: 'dragenter',
        97 DRAGOVER: 'dragover',
        98 DRAGLEAVE: 'dragleave',
        99 DROP: 'drop',
        100 DRAGEND: 'dragend',
        101
        102 // Touch events
        103 // Note that other touch events exist, but we should follow the W3C list here.
        104 // http://www.w3.org/TR/touch-events/#list-of-touchevent-types
        105 TOUCHSTART: 'touchstart',
        106 TOUCHMOVE: 'touchmove',
        107 TOUCHEND: 'touchend',
        108 TOUCHCANCEL: 'touchcancel',
        109
        110 // Misc
        111 BEFOREUNLOAD: 'beforeunload',
        112 CONSOLEMESSAGE: 'consolemessage',
        113 CONTEXTMENU: 'contextmenu',
        114 DOMCONTENTLOADED: 'DOMContentLoaded',
        115 ERROR: 'error',
        116 HELP: 'help',
        117 LOAD: 'load',
        118 LOSECAPTURE: 'losecapture',
        119 ORIENTATIONCHANGE: 'orientationchange',
        120 READYSTATECHANGE: 'readystatechange',
        121 RESIZE: 'resize',
        122 SCROLL: 'scroll',
        123 UNLOAD: 'unload',
        124
        125 // HTML 5 History events
        126 // See http://www.w3.org/TR/html5/history.html#event-definitions
        127 HASHCHANGE: 'hashchange',
        128 PAGEHIDE: 'pagehide',
        129 PAGESHOW: 'pageshow',
        130 POPSTATE: 'popstate',
        131
        132 // Copy and Paste
        133 // Support is limited. Make sure it works on your favorite browser
        134 // before using.
        135 // http://www.quirksmode.org/dom/events/cutcopypaste.html
        136 COPY: 'copy',
        137 PASTE: 'paste',
        138 CUT: 'cut',
        139 BEFORECOPY: 'beforecopy',
        140 BEFORECUT: 'beforecut',
        141 BEFOREPASTE: 'beforepaste',
        142
        143 // HTML5 online/offline events.
        144 // http://www.w3.org/TR/offline-webapps/#related
        145 ONLINE: 'online',
        146 OFFLINE: 'offline',
        147
        148 // HTML 5 worker events
        149 MESSAGE: 'message',
        150 CONNECT: 'connect',
        151
        152 // CSS animation events.
        153 /** @suppress {missingRequire} */
        154 ANIMATIONSTART: goog.events.getVendorPrefixedName_('AnimationStart'),
        155 /** @suppress {missingRequire} */
        156 ANIMATIONEND: goog.events.getVendorPrefixedName_('AnimationEnd'),
        157 /** @suppress {missingRequire} */
        158 ANIMATIONITERATION: goog.events.getVendorPrefixedName_('AnimationIteration'),
        159
        160 // CSS transition events. Based on the browser support described at:
        161 // https://developer.mozilla.org/en/css/css_transitions#Browser_compatibility
        162 /** @suppress {missingRequire} */
        163 TRANSITIONEND: goog.events.getVendorPrefixedName_('TransitionEnd'),
        164
        165 // W3C Pointer Events
        166 // http://www.w3.org/TR/pointerevents/
        167 POINTERDOWN: 'pointerdown',
        168 POINTERUP: 'pointerup',
        169 POINTERCANCEL: 'pointercancel',
        170 POINTERMOVE: 'pointermove',
        171 POINTEROVER: 'pointerover',
        172 POINTEROUT: 'pointerout',
        173 POINTERENTER: 'pointerenter',
        174 POINTERLEAVE: 'pointerleave',
        175 GOTPOINTERCAPTURE: 'gotpointercapture',
        176 LOSTPOINTERCAPTURE: 'lostpointercapture',
        177
        178 // IE specific events.
        179 // See http://msdn.microsoft.com/en-us/library/ie/hh772103(v=vs.85).aspx
        180 // Note: these events will be supplanted in IE11.
        181 MSGESTURECHANGE: 'MSGestureChange',
        182 MSGESTUREEND: 'MSGestureEnd',
        183 MSGESTUREHOLD: 'MSGestureHold',
        184 MSGESTURESTART: 'MSGestureStart',
        185 MSGESTURETAP: 'MSGestureTap',
        186 MSGOTPOINTERCAPTURE: 'MSGotPointerCapture',
        187 MSINERTIASTART: 'MSInertiaStart',
        188 MSLOSTPOINTERCAPTURE: 'MSLostPointerCapture',
        189 MSPOINTERCANCEL: 'MSPointerCancel',
        190 MSPOINTERDOWN: 'MSPointerDown',
        191 MSPOINTERENTER: 'MSPointerEnter',
        192 MSPOINTERHOVER: 'MSPointerHover',
        193 MSPOINTERLEAVE: 'MSPointerLeave',
        194 MSPOINTERMOVE: 'MSPointerMove',
        195 MSPOINTEROUT: 'MSPointerOut',
        196 MSPOINTEROVER: 'MSPointerOver',
        197 MSPOINTERUP: 'MSPointerUp',
        198
        199 // Native IMEs/input tools events.
        200 TEXT: 'text',
        201 TEXTINPUT: 'textInput',
        202 COMPOSITIONSTART: 'compositionstart',
        203 COMPOSITIONUPDATE: 'compositionupdate',
        204 COMPOSITIONEND: 'compositionend',
        205
        206 // Webview tag events
        207 // See http://developer.chrome.com/dev/apps/webview_tag.html
        208 EXIT: 'exit',
        209 LOADABORT: 'loadabort',
        210 LOADCOMMIT: 'loadcommit',
        211 LOADREDIRECT: 'loadredirect',
        212 LOADSTART: 'loadstart',
        213 LOADSTOP: 'loadstop',
        214 RESPONSIVE: 'responsive',
        215 SIZECHANGED: 'sizechanged',
        216 UNRESPONSIVE: 'unresponsive',
        217
        218 // HTML5 Page Visibility API. See details at
        219 // {@code goog.labs.dom.PageVisibilityMonitor}.
        220 VISIBILITYCHANGE: 'visibilitychange',
        221
        222 // LocalStorage event.
        223 STORAGE: 'storage',
        224
        225 // DOM Level 2 mutation events (deprecated).
        226 DOMSUBTREEMODIFIED: 'DOMSubtreeModified',
        227 DOMNODEINSERTED: 'DOMNodeInserted',
        228 DOMNODEREMOVED: 'DOMNodeRemoved',
        229 DOMNODEREMOVEDFROMDOCUMENT: 'DOMNodeRemovedFromDocument',
        230 DOMNODEINSERTEDINTODOCUMENT: 'DOMNodeInsertedIntoDocument',
        231 DOMATTRMODIFIED: 'DOMAttrModified',
        232 DOMCHARACTERDATAMODIFIED: 'DOMCharacterDataModified'
        233};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/keycodes.js.src.html b/docs/source/lib/goog/events/keycodes.js.src.html new file mode 100644 index 0000000..6f9e252 --- /dev/null +++ b/docs/source/lib/goog/events/keycodes.js.src.html @@ -0,0 +1 @@ +keycodes.js

        lib/goog/events/keycodes.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Constant declarations for common key codes.
        17 *
        18 * @author eae@google.com (Emil A Eklund)
        19 * @see ../demos/keyhandler.html
        20 */
        21
        22goog.provide('goog.events.KeyCodes');
        23
        24goog.require('goog.userAgent');
        25
        26
        27/**
        28 * Key codes for common characters.
        29 *
        30 * This list is not localized and therefore some of the key codes are not
        31 * correct for non US keyboard layouts. See comments below.
        32 *
        33 * @enum {number}
        34 */
        35goog.events.KeyCodes = {
        36 WIN_KEY_FF_LINUX: 0,
        37 MAC_ENTER: 3,
        38 BACKSPACE: 8,
        39 TAB: 9,
        40 NUM_CENTER: 12, // NUMLOCK on FF/Safari Mac
        41 ENTER: 13,
        42 SHIFT: 16,
        43 CTRL: 17,
        44 ALT: 18,
        45 PAUSE: 19,
        46 CAPS_LOCK: 20,
        47 ESC: 27,
        48 SPACE: 32,
        49 PAGE_UP: 33, // also NUM_NORTH_EAST
        50 PAGE_DOWN: 34, // also NUM_SOUTH_EAST
        51 END: 35, // also NUM_SOUTH_WEST
        52 HOME: 36, // also NUM_NORTH_WEST
        53 LEFT: 37, // also NUM_WEST
        54 UP: 38, // also NUM_NORTH
        55 RIGHT: 39, // also NUM_EAST
        56 DOWN: 40, // also NUM_SOUTH
        57 PRINT_SCREEN: 44,
        58 INSERT: 45, // also NUM_INSERT
        59 DELETE: 46, // also NUM_DELETE
        60 ZERO: 48,
        61 ONE: 49,
        62 TWO: 50,
        63 THREE: 51,
        64 FOUR: 52,
        65 FIVE: 53,
        66 SIX: 54,
        67 SEVEN: 55,
        68 EIGHT: 56,
        69 NINE: 57,
        70 FF_SEMICOLON: 59, // Firefox (Gecko) fires this for semicolon instead of 186
        71 FF_EQUALS: 61, // Firefox (Gecko) fires this for equals instead of 187
        72 FF_DASH: 173, // Firefox (Gecko) fires this for dash instead of 189
        73 QUESTION_MARK: 63, // needs localization
        74 A: 65,
        75 B: 66,
        76 C: 67,
        77 D: 68,
        78 E: 69,
        79 F: 70,
        80 G: 71,
        81 H: 72,
        82 I: 73,
        83 J: 74,
        84 K: 75,
        85 L: 76,
        86 M: 77,
        87 N: 78,
        88 O: 79,
        89 P: 80,
        90 Q: 81,
        91 R: 82,
        92 S: 83,
        93 T: 84,
        94 U: 85,
        95 V: 86,
        96 W: 87,
        97 X: 88,
        98 Y: 89,
        99 Z: 90,
        100 META: 91, // WIN_KEY_LEFT
        101 WIN_KEY_RIGHT: 92,
        102 CONTEXT_MENU: 93,
        103 NUM_ZERO: 96,
        104 NUM_ONE: 97,
        105 NUM_TWO: 98,
        106 NUM_THREE: 99,
        107 NUM_FOUR: 100,
        108 NUM_FIVE: 101,
        109 NUM_SIX: 102,
        110 NUM_SEVEN: 103,
        111 NUM_EIGHT: 104,
        112 NUM_NINE: 105,
        113 NUM_MULTIPLY: 106,
        114 NUM_PLUS: 107,
        115 NUM_MINUS: 109,
        116 NUM_PERIOD: 110,
        117 NUM_DIVISION: 111,
        118 F1: 112,
        119 F2: 113,
        120 F3: 114,
        121 F4: 115,
        122 F5: 116,
        123 F6: 117,
        124 F7: 118,
        125 F8: 119,
        126 F9: 120,
        127 F10: 121,
        128 F11: 122,
        129 F12: 123,
        130 NUMLOCK: 144,
        131 SCROLL_LOCK: 145,
        132
        133 // OS-specific media keys like volume controls and browser controls.
        134 FIRST_MEDIA_KEY: 166,
        135 LAST_MEDIA_KEY: 183,
        136
        137 SEMICOLON: 186, // needs localization
        138 DASH: 189, // needs localization
        139 EQUALS: 187, // needs localization
        140 COMMA: 188, // needs localization
        141 PERIOD: 190, // needs localization
        142 SLASH: 191, // needs localization
        143 APOSTROPHE: 192, // needs localization
        144 TILDE: 192, // needs localization
        145 SINGLE_QUOTE: 222, // needs localization
        146 OPEN_SQUARE_BRACKET: 219, // needs localization
        147 BACKSLASH: 220, // needs localization
        148 CLOSE_SQUARE_BRACKET: 221, // needs localization
        149 WIN_KEY: 224,
        150 MAC_FF_META: 224, // Firefox (Gecko) fires this for the meta key instead of 91
        151 MAC_WK_CMD_LEFT: 91, // WebKit Left Command key fired, same as META
        152 MAC_WK_CMD_RIGHT: 93, // WebKit Right Command key fired, different from META
        153 WIN_IME: 229,
        154
        155 // "Reserved for future use". Some programs (e.g. the SlingPlayer 2.4 ActiveX
        156 // control) fire this as a hacky way to disable screensavers.
        157 VK_NONAME: 252,
        158
        159 // We've seen users whose machines fire this keycode at regular one
        160 // second intervals. The common thread among these users is that
        161 // they're all using Dell Inspiron laptops, so we suspect that this
        162 // indicates a hardware/bios problem.
        163 // http://en.community.dell.com/support-forums/laptop/f/3518/p/19285957/19523128.aspx
        164 PHANTOM: 255
        165};
        166
        167
        168/**
        169 * Returns true if the event contains a text modifying key.
        170 * @param {goog.events.BrowserEvent} e A key event.
        171 * @return {boolean} Whether it's a text modifying key.
        172 */
        173goog.events.KeyCodes.isTextModifyingKeyEvent = function(e) {
        174 if (e.altKey && !e.ctrlKey ||
        175 e.metaKey ||
        176 // Function keys don't generate text
        177 e.keyCode >= goog.events.KeyCodes.F1 &&
        178 e.keyCode <= goog.events.KeyCodes.F12) {
        179 return false;
        180 }
        181
        182 // The following keys are quite harmless, even in combination with
        183 // CTRL, ALT or SHIFT.
        184 switch (e.keyCode) {
        185 case goog.events.KeyCodes.ALT:
        186 case goog.events.KeyCodes.CAPS_LOCK:
        187 case goog.events.KeyCodes.CONTEXT_MENU:
        188 case goog.events.KeyCodes.CTRL:
        189 case goog.events.KeyCodes.DOWN:
        190 case goog.events.KeyCodes.END:
        191 case goog.events.KeyCodes.ESC:
        192 case goog.events.KeyCodes.HOME:
        193 case goog.events.KeyCodes.INSERT:
        194 case goog.events.KeyCodes.LEFT:
        195 case goog.events.KeyCodes.MAC_FF_META:
        196 case goog.events.KeyCodes.META:
        197 case goog.events.KeyCodes.NUMLOCK:
        198 case goog.events.KeyCodes.NUM_CENTER:
        199 case goog.events.KeyCodes.PAGE_DOWN:
        200 case goog.events.KeyCodes.PAGE_UP:
        201 case goog.events.KeyCodes.PAUSE:
        202 case goog.events.KeyCodes.PHANTOM:
        203 case goog.events.KeyCodes.PRINT_SCREEN:
        204 case goog.events.KeyCodes.RIGHT:
        205 case goog.events.KeyCodes.SCROLL_LOCK:
        206 case goog.events.KeyCodes.SHIFT:
        207 case goog.events.KeyCodes.UP:
        208 case goog.events.KeyCodes.VK_NONAME:
        209 case goog.events.KeyCodes.WIN_KEY:
        210 case goog.events.KeyCodes.WIN_KEY_RIGHT:
        211 return false;
        212 case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
        213 return !goog.userAgent.GECKO;
        214 default:
        215 return e.keyCode < goog.events.KeyCodes.FIRST_MEDIA_KEY ||
        216 e.keyCode > goog.events.KeyCodes.LAST_MEDIA_KEY;
        217 }
        218};
        219
        220
        221/**
        222 * Returns true if the key fires a keypress event in the current browser.
        223 *
        224 * Accoridng to MSDN [1] IE only fires keypress events for the following keys:
        225 * - Letters: A - Z (uppercase and lowercase)
        226 * - Numerals: 0 - 9
        227 * - Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
        228 * - System: ESC, SPACEBAR, ENTER
        229 *
        230 * That's not entirely correct though, for instance there's no distinction
        231 * between upper and lower case letters.
        232 *
        233 * [1] http://msdn2.microsoft.com/en-us/library/ms536939(VS.85).aspx)
        234 *
        235 * Safari is similar to IE, but does not fire keypress for ESC.
        236 *
        237 * Additionally, IE6 does not fire keydown or keypress events for letters when
        238 * the control or alt keys are held down and the shift key is not. IE7 does
        239 * fire keydown in these cases, though, but not keypress.
        240 *
        241 * @param {number} keyCode A key code.
        242 * @param {number=} opt_heldKeyCode Key code of a currently-held key.
        243 * @param {boolean=} opt_shiftKey Whether the shift key is held down.
        244 * @param {boolean=} opt_ctrlKey Whether the control key is held down.
        245 * @param {boolean=} opt_altKey Whether the alt key is held down.
        246 * @return {boolean} Whether it's a key that fires a keypress event.
        247 */
        248goog.events.KeyCodes.firesKeyPressEvent = function(keyCode, opt_heldKeyCode,
        249 opt_shiftKey, opt_ctrlKey, opt_altKey) {
        250 if (!goog.userAgent.IE &&
        251 !(goog.userAgent.WEBKIT && goog.userAgent.isVersionOrHigher('525'))) {
        252 return true;
        253 }
        254
        255 if (goog.userAgent.MAC && opt_altKey) {
        256 return goog.events.KeyCodes.isCharacterKey(keyCode);
        257 }
        258
        259 // Alt but not AltGr which is represented as Alt+Ctrl.
        260 if (opt_altKey && !opt_ctrlKey) {
        261 return false;
        262 }
        263
        264 // Saves Ctrl or Alt + key for IE and WebKit 525+, which won't fire keypress.
        265 // Non-IE browsers and WebKit prior to 525 won't get this far so no need to
        266 // check the user agent.
        267 if (goog.isNumber(opt_heldKeyCode)) {
        268 opt_heldKeyCode = goog.events.KeyCodes.normalizeKeyCode(opt_heldKeyCode);
        269 }
        270 if (!opt_shiftKey &&
        271 (opt_heldKeyCode == goog.events.KeyCodes.CTRL ||
        272 opt_heldKeyCode == goog.events.KeyCodes.ALT ||
        273 goog.userAgent.MAC &&
        274 opt_heldKeyCode == goog.events.KeyCodes.META)) {
        275 return false;
        276 }
        277
        278 // Some keys with Ctrl/Shift do not issue keypress in WEBKIT.
        279 if (goog.userAgent.WEBKIT && opt_ctrlKey && opt_shiftKey) {
        280 switch (keyCode) {
        281 case goog.events.KeyCodes.BACKSLASH:
        282 case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
        283 case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
        284 case goog.events.KeyCodes.TILDE:
        285 case goog.events.KeyCodes.SEMICOLON:
        286 case goog.events.KeyCodes.DASH:
        287 case goog.events.KeyCodes.EQUALS:
        288 case goog.events.KeyCodes.COMMA:
        289 case goog.events.KeyCodes.PERIOD:
        290 case goog.events.KeyCodes.SLASH:
        291 case goog.events.KeyCodes.APOSTROPHE:
        292 case goog.events.KeyCodes.SINGLE_QUOTE:
        293 return false;
        294 }
        295 }
        296
        297 // When Ctrl+<somekey> is held in IE, it only fires a keypress once, but it
        298 // continues to fire keydown events as the event repeats.
        299 if (goog.userAgent.IE && opt_ctrlKey && opt_heldKeyCode == keyCode) {
        300 return false;
        301 }
        302
        303 switch (keyCode) {
        304 case goog.events.KeyCodes.ENTER:
        305 return true;
        306 case goog.events.KeyCodes.ESC:
        307 return !goog.userAgent.WEBKIT;
        308 }
        309
        310 return goog.events.KeyCodes.isCharacterKey(keyCode);
        311};
        312
        313
        314/**
        315 * Returns true if the key produces a character.
        316 * This does not cover characters on non-US keyboards (Russian, Hebrew, etc.).
        317 *
        318 * @param {number} keyCode A key code.
        319 * @return {boolean} Whether it's a character key.
        320 */
        321goog.events.KeyCodes.isCharacterKey = function(keyCode) {
        322 if (keyCode >= goog.events.KeyCodes.ZERO &&
        323 keyCode <= goog.events.KeyCodes.NINE) {
        324 return true;
        325 }
        326
        327 if (keyCode >= goog.events.KeyCodes.NUM_ZERO &&
        328 keyCode <= goog.events.KeyCodes.NUM_MULTIPLY) {
        329 return true;
        330 }
        331
        332 if (keyCode >= goog.events.KeyCodes.A &&
        333 keyCode <= goog.events.KeyCodes.Z) {
        334 return true;
        335 }
        336
        337 // Safari sends zero key code for non-latin characters.
        338 if (goog.userAgent.WEBKIT && keyCode == 0) {
        339 return true;
        340 }
        341
        342 switch (keyCode) {
        343 case goog.events.KeyCodes.SPACE:
        344 case goog.events.KeyCodes.QUESTION_MARK:
        345 case goog.events.KeyCodes.NUM_PLUS:
        346 case goog.events.KeyCodes.NUM_MINUS:
        347 case goog.events.KeyCodes.NUM_PERIOD:
        348 case goog.events.KeyCodes.NUM_DIVISION:
        349 case goog.events.KeyCodes.SEMICOLON:
        350 case goog.events.KeyCodes.FF_SEMICOLON:
        351 case goog.events.KeyCodes.DASH:
        352 case goog.events.KeyCodes.EQUALS:
        353 case goog.events.KeyCodes.FF_EQUALS:
        354 case goog.events.KeyCodes.COMMA:
        355 case goog.events.KeyCodes.PERIOD:
        356 case goog.events.KeyCodes.SLASH:
        357 case goog.events.KeyCodes.APOSTROPHE:
        358 case goog.events.KeyCodes.SINGLE_QUOTE:
        359 case goog.events.KeyCodes.OPEN_SQUARE_BRACKET:
        360 case goog.events.KeyCodes.BACKSLASH:
        361 case goog.events.KeyCodes.CLOSE_SQUARE_BRACKET:
        362 return true;
        363 default:
        364 return false;
        365 }
        366};
        367
        368
        369/**
        370 * Normalizes key codes from OS/Browser-specific value to the general one.
        371 * @param {number} keyCode The native key code.
        372 * @return {number} The normalized key code.
        373 */
        374goog.events.KeyCodes.normalizeKeyCode = function(keyCode) {
        375 if (goog.userAgent.GECKO) {
        376 return goog.events.KeyCodes.normalizeGeckoKeyCode(keyCode);
        377 } else if (goog.userAgent.MAC && goog.userAgent.WEBKIT) {
        378 return goog.events.KeyCodes.normalizeMacWebKitKeyCode(keyCode);
        379 } else {
        380 return keyCode;
        381 }
        382};
        383
        384
        385/**
        386 * Normalizes key codes from their Gecko-specific value to the general one.
        387 * @param {number} keyCode The native key code.
        388 * @return {number} The normalized key code.
        389 */
        390goog.events.KeyCodes.normalizeGeckoKeyCode = function(keyCode) {
        391 switch (keyCode) {
        392 case goog.events.KeyCodes.FF_EQUALS:
        393 return goog.events.KeyCodes.EQUALS;
        394 case goog.events.KeyCodes.FF_SEMICOLON:
        395 return goog.events.KeyCodes.SEMICOLON;
        396 case goog.events.KeyCodes.FF_DASH:
        397 return goog.events.KeyCodes.DASH;
        398 case goog.events.KeyCodes.MAC_FF_META:
        399 return goog.events.KeyCodes.META;
        400 case goog.events.KeyCodes.WIN_KEY_FF_LINUX:
        401 return goog.events.KeyCodes.WIN_KEY;
        402 default:
        403 return keyCode;
        404 }
        405};
        406
        407
        408/**
        409 * Normalizes key codes from their Mac WebKit-specific value to the general one.
        410 * @param {number} keyCode The native key code.
        411 * @return {number} The normalized key code.
        412 */
        413goog.events.KeyCodes.normalizeMacWebKitKeyCode = function(keyCode) {
        414 switch (keyCode) {
        415 case goog.events.KeyCodes.MAC_WK_CMD_RIGHT: // 93
        416 return goog.events.KeyCodes.META; // 91
        417 default:
        418 return keyCode;
        419 }
        420};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/listenable.js.src.html b/docs/source/lib/goog/events/listenable.js.src.html new file mode 100644 index 0000000..784c459 --- /dev/null +++ b/docs/source/lib/goog/events/listenable.js.src.html @@ -0,0 +1 @@ +listenable.js

        lib/goog/events/listenable.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview An interface for a listenable JavaScript object.
        17 * @author chrishenry@google.com (Chris Henry)
        18 */
        19
        20goog.provide('goog.events.Listenable');
        21goog.provide('goog.events.ListenableKey');
        22
        23/** @suppress {extraRequire} */
        24goog.require('goog.events.EventId');
        25
        26
        27
        28/**
        29 * A listenable interface. A listenable is an object with the ability
        30 * to dispatch/broadcast events to "event listeners" registered via
        31 * listen/listenOnce.
        32 *
        33 * The interface allows for an event propagation mechanism similar
        34 * to one offered by native browser event targets, such as
        35 * capture/bubble mechanism, stopping propagation, and preventing
        36 * default actions. Capture/bubble mechanism depends on the ancestor
        37 * tree constructed via {@code #getParentEventTarget}; this tree
        38 * must be directed acyclic graph. The meaning of default action(s)
        39 * in preventDefault is specific to a particular use case.
        40 *
        41 * Implementations that do not support capture/bubble or can not have
        42 * a parent listenable can simply not implement any ability to set the
        43 * parent listenable (and have {@code #getParentEventTarget} return
        44 * null).
        45 *
        46 * Implementation of this class can be used with or independently from
        47 * goog.events.
        48 *
        49 * Implementation must call {@code #addImplementation(implClass)}.
        50 *
        51 * @interface
        52 * @see goog.events
        53 * @see http://www.w3.org/TR/DOM-Level-2-Events/events.html
        54 */
        55goog.events.Listenable = function() {};
        56
        57
        58/**
        59 * An expando property to indicate that an object implements
        60 * goog.events.Listenable.
        61 *
        62 * See addImplementation/isImplementedBy.
        63 *
        64 * @type {string}
        65 * @const
        66 */
        67goog.events.Listenable.IMPLEMENTED_BY_PROP =
        68 'closure_listenable_' + ((Math.random() * 1e6) | 0);
        69
        70
        71/**
        72 * Marks a given class (constructor) as an implementation of
        73 * Listenable, do that we can query that fact at runtime. The class
        74 * must have already implemented the interface.
        75 * @param {!Function} cls The class constructor. The corresponding
        76 * class must have already implemented the interface.
        77 */
        78goog.events.Listenable.addImplementation = function(cls) {
        79 cls.prototype[goog.events.Listenable.IMPLEMENTED_BY_PROP] = true;
        80};
        81
        82
        83/**
        84 * @param {Object} obj The object to check.
        85 * @return {boolean} Whether a given instance implements Listenable. The
        86 * class/superclass of the instance must call addImplementation.
        87 */
        88goog.events.Listenable.isImplementedBy = function(obj) {
        89 return !!(obj && obj[goog.events.Listenable.IMPLEMENTED_BY_PROP]);
        90};
        91
        92
        93/**
        94 * Adds an event listener. A listener can only be added once to an
        95 * object and if it is added again the key for the listener is
        96 * returned. Note that if the existing listener is a one-off listener
        97 * (registered via listenOnce), it will no longer be a one-off
        98 * listener after a call to listen().
        99 *
        100 * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
        101 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
        102 * method.
        103 * @param {boolean=} opt_useCapture Whether to fire in capture phase
        104 * (defaults to false).
        105 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
        106 * listener.
        107 * @return {goog.events.ListenableKey} Unique key for the listener.
        108 * @template SCOPE,EVENTOBJ
        109 */
        110goog.events.Listenable.prototype.listen;
        111
        112
        113/**
        114 * Adds an event listener that is removed automatically after the
        115 * listener fired once.
        116 *
        117 * If an existing listener already exists, listenOnce will do
        118 * nothing. In particular, if the listener was previously registered
        119 * via listen(), listenOnce() will not turn the listener into a
        120 * one-off listener. Similarly, if there is already an existing
        121 * one-off listener, listenOnce does not modify the listeners (it is
        122 * still a once listener).
        123 *
        124 * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
        125 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
        126 * method.
        127 * @param {boolean=} opt_useCapture Whether to fire in capture phase
        128 * (defaults to false).
        129 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
        130 * listener.
        131 * @return {goog.events.ListenableKey} Unique key for the listener.
        132 * @template SCOPE,EVENTOBJ
        133 */
        134goog.events.Listenable.prototype.listenOnce;
        135
        136
        137/**
        138 * Removes an event listener which was added with listen() or listenOnce().
        139 *
        140 * @param {string|!goog.events.EventId<EVENTOBJ>} type The event type id.
        141 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener Callback
        142 * method.
        143 * @param {boolean=} opt_useCapture Whether to fire in capture phase
        144 * (defaults to false).
        145 * @param {SCOPE=} opt_listenerScope Object in whose scope to call
        146 * the listener.
        147 * @return {boolean} Whether any listener was removed.
        148 * @template SCOPE,EVENTOBJ
        149 */
        150goog.events.Listenable.prototype.unlisten;
        151
        152
        153/**
        154 * Removes an event listener which was added with listen() by the key
        155 * returned by listen().
        156 *
        157 * @param {goog.events.ListenableKey} key The key returned by
        158 * listen() or listenOnce().
        159 * @return {boolean} Whether any listener was removed.
        160 */
        161goog.events.Listenable.prototype.unlistenByKey;
        162
        163
        164/**
        165 * Dispatches an event (or event like object) and calls all listeners
        166 * listening for events of this type. The type of the event is decided by the
        167 * type property on the event object.
        168 *
        169 * If any of the listeners returns false OR calls preventDefault then this
        170 * function will return false. If one of the capture listeners calls
        171 * stopPropagation, then the bubble listeners won't fire.
        172 *
        173 * @param {goog.events.EventLike} e Event object.
        174 * @return {boolean} If anyone called preventDefault on the event object (or
        175 * if any of the listeners returns false) this will also return false.
        176 */
        177goog.events.Listenable.prototype.dispatchEvent;
        178
        179
        180/**
        181 * Removes all listeners from this listenable. If type is specified,
        182 * it will only remove listeners of the particular type. otherwise all
        183 * registered listeners will be removed.
        184 *
        185 * @param {string=} opt_type Type of event to remove, default is to
        186 * remove all types.
        187 * @return {number} Number of listeners removed.
        188 */
        189goog.events.Listenable.prototype.removeAllListeners;
        190
        191
        192/**
        193 * Returns the parent of this event target to use for capture/bubble
        194 * mechanism.
        195 *
        196 * NOTE(chrishenry): The name reflects the original implementation of
        197 * custom event target ({@code goog.events.EventTarget}). We decided
        198 * that changing the name is not worth it.
        199 *
        200 * @return {goog.events.Listenable} The parent EventTarget or null if
        201 * there is no parent.
        202 */
        203goog.events.Listenable.prototype.getParentEventTarget;
        204
        205
        206/**
        207 * Fires all registered listeners in this listenable for the given
        208 * type and capture mode, passing them the given eventObject. This
        209 * does not perform actual capture/bubble. Only implementors of the
        210 * interface should be using this.
        211 *
        212 * @param {string|!goog.events.EventId<EVENTOBJ>} type The type of the
        213 * listeners to fire.
        214 * @param {boolean} capture The capture mode of the listeners to fire.
        215 * @param {EVENTOBJ} eventObject The event object to fire.
        216 * @return {boolean} Whether all listeners succeeded without
        217 * attempting to prevent default behavior. If any listener returns
        218 * false or called goog.events.Event#preventDefault, this returns
        219 * false.
        220 * @template EVENTOBJ
        221 */
        222goog.events.Listenable.prototype.fireListeners;
        223
        224
        225/**
        226 * Gets all listeners in this listenable for the given type and
        227 * capture mode.
        228 *
        229 * @param {string|!goog.events.EventId} type The type of the listeners to fire.
        230 * @param {boolean} capture The capture mode of the listeners to fire.
        231 * @return {!Array<goog.events.ListenableKey>} An array of registered
        232 * listeners.
        233 * @template EVENTOBJ
        234 */
        235goog.events.Listenable.prototype.getListeners;
        236
        237
        238/**
        239 * Gets the goog.events.ListenableKey for the event or null if no such
        240 * listener is in use.
        241 *
        242 * @param {string|!goog.events.EventId<EVENTOBJ>} type The name of the event
        243 * without the 'on' prefix.
        244 * @param {function(this:SCOPE, EVENTOBJ):(boolean|undefined)} listener The
        245 * listener function to get.
        246 * @param {boolean} capture Whether the listener is a capturing listener.
        247 * @param {SCOPE=} opt_listenerScope Object in whose scope to call the
        248 * listener.
        249 * @return {goog.events.ListenableKey} the found listener or null if not found.
        250 * @template SCOPE,EVENTOBJ
        251 */
        252goog.events.Listenable.prototype.getListener;
        253
        254
        255/**
        256 * Whether there is any active listeners matching the specified
        257 * signature. If either the type or capture parameters are
        258 * unspecified, the function will match on the remaining criteria.
        259 *
        260 * @param {string|!goog.events.EventId<EVENTOBJ>=} opt_type Event type.
        261 * @param {boolean=} opt_capture Whether to check for capture or bubble
        262 * listeners.
        263 * @return {boolean} Whether there is any active listeners matching
        264 * the requested type and/or capture phase.
        265 * @template EVENTOBJ
        266 */
        267goog.events.Listenable.prototype.hasListener;
        268
        269
        270
        271/**
        272 * An interface that describes a single registered listener.
        273 * @interface
        274 */
        275goog.events.ListenableKey = function() {};
        276
        277
        278/**
        279 * Counter used to create a unique key
        280 * @type {number}
        281 * @private
        282 */
        283goog.events.ListenableKey.counter_ = 0;
        284
        285
        286/**
        287 * Reserves a key to be used for ListenableKey#key field.
        288 * @return {number} A number to be used to fill ListenableKey#key
        289 * field.
        290 */
        291goog.events.ListenableKey.reserveKey = function() {
        292 return ++goog.events.ListenableKey.counter_;
        293};
        294
        295
        296/**
        297 * The source event target.
        298 * @type {!(Object|goog.events.Listenable|goog.events.EventTarget)}
        299 */
        300goog.events.ListenableKey.prototype.src;
        301
        302
        303/**
        304 * The event type the listener is listening to.
        305 * @type {string}
        306 */
        307goog.events.ListenableKey.prototype.type;
        308
        309
        310/**
        311 * The listener function.
        312 * @type {function(?):?|{handleEvent:function(?):?}|null}
        313 */
        314goog.events.ListenableKey.prototype.listener;
        315
        316
        317/**
        318 * Whether the listener works on capture phase.
        319 * @type {boolean}
        320 */
        321goog.events.ListenableKey.prototype.capture;
        322
        323
        324/**
        325 * The 'this' object for the listener function's scope.
        326 * @type {Object}
        327 */
        328goog.events.ListenableKey.prototype.handler;
        329
        330
        331/**
        332 * A globally unique number to identify the key.
        333 * @type {number}
        334 */
        335goog.events.ListenableKey.prototype.key;
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/listener.js.src.html b/docs/source/lib/goog/events/listener.js.src.html new file mode 100644 index 0000000..22d0c1e --- /dev/null +++ b/docs/source/lib/goog/events/listener.js.src.html @@ -0,0 +1 @@ +listener.js

        lib/goog/events/listener.js

        1// Copyright 2005 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Listener object.
        17 * @see ../demos/events.html
        18 */
        19
        20goog.provide('goog.events.Listener');
        21
        22goog.require('goog.events.ListenableKey');
        23
        24
        25
        26/**
        27 * Simple class that stores information about a listener
        28 * @param {!Function} listener Callback function.
        29 * @param {Function} proxy Wrapper for the listener that patches the event.
        30 * @param {EventTarget|goog.events.Listenable} src Source object for
        31 * the event.
        32 * @param {string} type Event type.
        33 * @param {boolean} capture Whether in capture or bubble phase.
        34 * @param {Object=} opt_handler Object in whose context to execute the callback.
        35 * @implements {goog.events.ListenableKey}
        36 * @constructor
        37 */
        38goog.events.Listener = function(
        39 listener, proxy, src, type, capture, opt_handler) {
        40 if (goog.events.Listener.ENABLE_MONITORING) {
        41 this.creationStack = new Error().stack;
        42 }
        43
        44 /**
        45 * Callback function.
        46 * @type {Function}
        47 */
        48 this.listener = listener;
        49
        50 /**
        51 * A wrapper over the original listener. This is used solely to
        52 * handle native browser events (it is used to simulate the capture
        53 * phase and to patch the event object).
        54 * @type {Function}
        55 */
        56 this.proxy = proxy;
        57
        58 /**
        59 * Object or node that callback is listening to
        60 * @type {EventTarget|goog.events.Listenable}
        61 */
        62 this.src = src;
        63
        64 /**
        65 * The event type.
        66 * @const {string}
        67 */
        68 this.type = type;
        69
        70 /**
        71 * Whether the listener is being called in the capture or bubble phase
        72 * @const {boolean}
        73 */
        74 this.capture = !!capture;
        75
        76 /**
        77 * Optional object whose context to execute the listener in
        78 * @type {Object|undefined}
        79 */
        80 this.handler = opt_handler;
        81
        82 /**
        83 * The key of the listener.
        84 * @const {number}
        85 * @override
        86 */
        87 this.key = goog.events.ListenableKey.reserveKey();
        88
        89 /**
        90 * Whether to remove the listener after it has been called.
        91 * @type {boolean}
        92 */
        93 this.callOnce = false;
        94
        95 /**
        96 * Whether the listener has been removed.
        97 * @type {boolean}
        98 */
        99 this.removed = false;
        100};
        101
        102
        103/**
        104 * @define {boolean} Whether to enable the monitoring of the
        105 * goog.events.Listener instances. Switching on the monitoring is only
        106 * recommended for debugging because it has a significant impact on
        107 * performance and memory usage. If switched off, the monitoring code
        108 * compiles down to 0 bytes.
        109 */
        110goog.define('goog.events.Listener.ENABLE_MONITORING', false);
        111
        112
        113/**
        114 * If monitoring the goog.events.Listener instances is enabled, stores the
        115 * creation stack trace of the Disposable instance.
        116 * @type {string}
        117 */
        118goog.events.Listener.prototype.creationStack;
        119
        120
        121/**
        122 * Marks this listener as removed. This also remove references held by
        123 * this listener object (such as listener and event source).
        124 */
        125goog.events.Listener.prototype.markAsRemoved = function() {
        126 this.removed = true;
        127 this.listener = null;
        128 this.proxy = null;
        129 this.src = null;
        130 this.handler = null;
        131};
        \ No newline at end of file diff --git a/docs/source/lib/goog/events/listenermap.js.src.html b/docs/source/lib/goog/events/listenermap.js.src.html new file mode 100644 index 0000000..67b711e --- /dev/null +++ b/docs/source/lib/goog/events/listenermap.js.src.html @@ -0,0 +1 @@ +listenermap.js

        lib/goog/events/listenermap.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A map of listeners that provides utility functions to
        17 * deal with listeners on an event target. Used by
        18 * {@code goog.events.EventTarget}.
        19 *
        20 * WARNING: Do not use this class from outside goog.events package.
        21 *
        22 * @visibility {//closure/goog/bin/sizetests:__pkg__}
        23 * @visibility {//closure/goog/events:__pkg__}
        24 * @visibility {//closure/goog/labs/events:__pkg__}
        25 */
        26
        27goog.provide('goog.events.ListenerMap');
        28
        29goog.require('goog.array');
        30goog.require('goog.events.Listener');
        31goog.require('goog.object');
        32
        33
        34
        35/**
        36 * Creates a new listener map.
        37 * @param {EventTarget|goog.events.Listenable} src The src object.
        38 * @constructor
        39 * @final
        40 */
        41goog.events.ListenerMap = function(src) {
        42 /** @type {EventTarget|goog.events.Listenable} */
        43 this.src = src;
        44
        45 /**
        46 * Maps of event type to an array of listeners.
        47 * @type {Object<string, !Array<!goog.events.Listener>>}
        48 */
        49 this.listeners = {};
        50
        51 /**
        52 * The count of types in this map that have registered listeners.
        53 * @private {number}
        54 */
        55 this.typeCount_ = 0;
        56};
        57
        58
        59/**
        60 * @return {number} The count of event types in this map that actually
        61 * have registered listeners.
        62 */
        63goog.events.ListenerMap.prototype.getTypeCount = function() {
        64 return this.typeCount_;
        65};
        66
        67
        68/**
        69 * @return {number} Total number of registered listeners.
        70 */
        71goog.events.ListenerMap.prototype.getListenerCount = function() {
        72 var count = 0;
        73 for (var type in this.listeners) {
        74 count += this.listeners[type].length;
        75 }
        76 return count;
        77};
        78
        79
        80/**
        81 * Adds an event listener. A listener can only be added once to an
        82 * object and if it is added again the key for the listener is
        83 * returned.
        84 *
        85 * Note that a one-off listener will not change an existing listener,
        86 * if any. On the other hand a normal listener will change existing
        87 * one-off listener to become a normal listener.
        88 *
        89 * @param {string|!goog.events.EventId} type The listener event type.
        90 * @param {!Function} listener This listener callback method.
        91 * @param {boolean} callOnce Whether the listener is a one-off
        92 * listener.
        93 * @param {boolean=} opt_useCapture The capture mode of the listener.
        94 * @param {Object=} opt_listenerScope Object in whose scope to call the
        95 * listener.
        96 * @return {goog.events.ListenableKey} Unique key for the listener.
        97 */
        98goog.events.ListenerMap.prototype.add = function(
        99 type, listener, callOnce, opt_useCapture, opt_listenerScope) {
        100 var typeStr = type.toString();
        101 var listenerArray = this.listeners[typeStr];
        102 if (!listenerArray) {
        103 listenerArray = this.listeners[typeStr] = [];
        104 this.typeCount_++;
        105 }
        106
        107 var listenerObj;
        108 var index = goog.events.ListenerMap.findListenerIndex_(
        109 listenerArray, listener, opt_useCapture, opt_listenerScope);
        110 if (index > -1) {
        111 listenerObj = listenerArray[index];
        112 if (!callOnce) {
        113 // Ensure that, if there is an existing callOnce listener, it is no
        114 // longer a callOnce listener.
        115 listenerObj.callOnce = false;
        116 }
        117 } else {
        118 listenerObj = new goog.events.Listener(
        119 listener, null, this.src, typeStr, !!opt_useCapture, opt_listenerScope);
        120 listenerObj.callOnce = callOnce;
        121 listenerArray.push(listenerObj);
        122 }
        123 return listenerObj;
        124};
        125
        126
        127/**
        128 * Removes a matching listener.
        129 * @param {string|!goog.events.EventId} type The listener event type.
        130 * @param {!Function} listener This listener callback method.
        131 * @param {boolean=} opt_useCapture The capture mode of the listener.
        132 * @param {Object=} opt_listenerScope Object in whose scope to call the
        133 * listener.
        134 * @return {boolean} Whether any listener was removed.
        135 */
        136goog.events.ListenerMap.prototype.remove = function(
        137 type, listener, opt_useCapture, opt_listenerScope) {
        138 var typeStr = type.toString();
        139 if (!(typeStr in this.listeners)) {
        140 return false;
        141 }
        142
        143 var listenerArray = this.listeners[typeStr];
        144 var index = goog.events.ListenerMap.findListenerIndex_(
        145 listenerArray, listener, opt_useCapture, opt_listenerScope);
        146 if (index > -1) {
        147 var listenerObj = listenerArray[index];
        148 listenerObj.markAsRemoved();
        149 goog.array.removeAt(listenerArray, index);
        150 if (listenerArray.length == 0) {
        151 delete this.listeners[typeStr];
        152 this.typeCount_--;
        153 }
        154 return true;
        155 }
        156 return false;
        157};
        158
        159
        160/**
        161 * Removes the given listener object.
        162 * @param {goog.events.ListenableKey} listener The listener to remove.
        163 * @return {boolean} Whether the listener is removed.
        164 */
        165goog.events.ListenerMap.prototype.removeByKey = function(listener) {
        166 var type = listener.type;
        167 if (!(type in this.listeners)) {
        168 return false;
        169 }
        170
        171 var removed = goog.array.remove(this.listeners[type], listener);
        172 if (removed) {
        173 listener.markAsRemoved();
        174 if (this.listeners[type].length == 0) {
        175 delete this.listeners[type];
        176 this.typeCount_--;
        177 }
        178 }
        179 return removed;
        180};
        181
        182
        183/**
        184 * Removes all listeners from this map. If opt_type is provided, only
        185 * listeners that match the given type are removed.
        186 * @param {string|!goog.events.EventId=} opt_type Type of event to remove.
        187 * @return {number} Number of listeners removed.
        188 */
        189goog.events.ListenerMap.prototype.removeAll = function(opt_type) {
        190 var typeStr = opt_type && opt_type.toString();
        191 var count = 0;
        192 for (var type in this.listeners) {
        193 if (!typeStr || type == typeStr) {
        194 var listenerArray = this.listeners[type];
        195 for (var i = 0; i < listenerArray.length; i++) {
        196 ++count;
        197 listenerArray[i].markAsRemoved();
        198 }
        199 delete this.listeners[type];
        200 this.typeCount_--;
        201 }
        202 }
        203 return count;
        204};
        205
        206
        207/**
        208 * Gets all listeners that match the given type and capture mode. The
        209 * returned array is a copy (but the listener objects are not).
        210 * @param {string|!goog.events.EventId} type The type of the listeners
        211 * to retrieve.
        212 * @param {boolean} capture The capture mode of the listeners to retrieve.
        213 * @return {!Array<goog.events.ListenableKey>} An array of matching
        214 * listeners.
        215 */
        216goog.events.ListenerMap.prototype.getListeners = function(type, capture) {
        217 var listenerArray = this.listeners[type.toString()];
        218 var rv = [];
        219 if (listenerArray) {
        220 for (var i = 0; i < listenerArray.length; ++i) {
        221 var listenerObj = listenerArray[i];
        222 if (listenerObj.capture == capture) {
        223 rv.push(listenerObj);
        224 }
        225 }
        226 }
        227 return rv;
        228};
        229
        230
        231/**
        232 * Gets the goog.events.ListenableKey for the event or null if no such
        233 * listener is in use.
        234 *
        235 * @param {string|!goog.events.EventId} type The type of the listener
        236 * to retrieve.
        237 * @param {!Function} listener The listener function to get.
        238 * @param {boolean} capture Whether the listener is a capturing listener.
        239 * @param {Object=} opt_listenerScope Object in whose scope to call the
        240 * listener.
        241 * @return {goog.events.ListenableKey} the found listener or null if not found.
        242 */
        243goog.events.ListenerMap.prototype.getListener = function(
        244 type, listener, capture, opt_listenerScope) {
        245 var listenerArray = this.listeners[type.toString()];
        246 var i = -1;
        247 if (listenerArray) {
        248 i = goog.events.ListenerMap.findListenerIndex_(
        249 listenerArray, listener, capture, opt_listenerScope);
        250 }
        251 return i > -1 ? listenerArray[i] : null;
        252};
        253
        254
        255/**
        256 * Whether there is a matching listener. If either the type or capture
        257 * parameters are unspecified, the function will match on the
        258 * remaining criteria.
        259 *
        260 * @param {string|!goog.events.EventId=} opt_type The type of the listener.
        261 * @param {boolean=} opt_capture The capture mode of the listener.
        262 * @return {boolean} Whether there is an active listener matching
        263 * the requested type and/or capture phase.
        264 */
        265goog.events.ListenerMap.prototype.hasListener = function(
        266 opt_type, opt_capture) {
        267 var hasType = goog.isDef(opt_type);
        268 var typeStr = hasType ? opt_type.toString() : '';
        269 var hasCapture = goog.isDef(opt_capture);
        270
        271 return goog.object.some(
        272 this.listeners, function(listenerArray, type) {
        273 for (var i = 0; i < listenerArray.length; ++i) {
        274 if ((!hasType || listenerArray[i].type == typeStr) &&
        275 (!hasCapture || listenerArray[i].capture == opt_capture)) {
        276 return true;
        277 }
        278 }
        279
        280 return false;
        281 });
        282};
        283
        284
        285/**
        286 * Finds the index of a matching goog.events.Listener in the given
        287 * listenerArray.
        288 * @param {!Array<!goog.events.Listener>} listenerArray Array of listener.
        289 * @param {!Function} listener The listener function.
        290 * @param {boolean=} opt_useCapture The capture flag for the listener.
        291 * @param {Object=} opt_listenerScope The listener scope.
        292 * @return {number} The index of the matching listener within the
        293 * listenerArray.
        294 * @private
        295 */
        296goog.events.ListenerMap.findListenerIndex_ = function(
        297 listenerArray, listener, opt_useCapture, opt_listenerScope) {
        298 for (var i = 0; i < listenerArray.length; ++i) {
        299 var listenerObj = listenerArray[i];
        300 if (!listenerObj.removed &&
        301 listenerObj.listener == listener &&
        302 listenerObj.capture == !!opt_useCapture &&
        303 listenerObj.handler == opt_listenerScope) {
        304 return i;
        305 }
        306 }
        307 return -1;
        308};
        \ No newline at end of file diff --git a/docs/source/lib/goog/fs/url.js.src.html b/docs/source/lib/goog/fs/url.js.src.html new file mode 100644 index 0000000..53fc875 --- /dev/null +++ b/docs/source/lib/goog/fs/url.js.src.html @@ -0,0 +1 @@ +url.js

        lib/goog/fs/url.js

        1// Copyright 2015 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Wrapper for URL and its createObjectUrl and revokeObjectUrl
        17 * methods that are part of the HTML5 File API.
        18 */
        19
        20goog.provide('goog.fs.url');
        21
        22
        23/**
        24 * Creates a blob URL for a blob object.
        25 * Throws an error if the browser does not support Object Urls.
        26 *
        27 * @param {!Blob} blob The object for which to create the URL.
        28 * @return {string} The URL for the object.
        29 */
        30goog.fs.url.createObjectUrl = function(blob) {
        31 return goog.fs.url.getUrlObject_().createObjectURL(blob);
        32};
        33
        34
        35/**
        36 * Revokes a URL created by {@link goog.fs.url.createObjectUrl}.
        37 * Throws an error if the browser does not support Object Urls.
        38 *
        39 * @param {string} url The URL to revoke.
        40 */
        41goog.fs.url.revokeObjectUrl = function(url) {
        42 goog.fs.url.getUrlObject_().revokeObjectURL(url);
        43};
        44
        45
        46/**
        47 * @typedef {{createObjectURL: (function(!Blob): string),
        48 * revokeObjectURL: function(string): void}}
        49 */
        50goog.fs.url.UrlObject_;
        51
        52
        53/**
        54 * Get the object that has the createObjectURL and revokeObjectURL functions for
        55 * this browser.
        56 *
        57 * @return {goog.fs.url.UrlObject_} The object for this browser.
        58 * @private
        59 */
        60goog.fs.url.getUrlObject_ = function() {
        61 var urlObject = goog.fs.url.findUrlObject_();
        62 if (urlObject != null) {
        63 return urlObject;
        64 } else {
        65 throw Error('This browser doesn\'t seem to support blob URLs');
        66 }
        67};
        68
        69
        70/**
        71 * Finds the object that has the createObjectURL and revokeObjectURL functions
        72 * for this browser.
        73 *
        74 * @return {?goog.fs.url.UrlObject_} The object for this browser or null if the
        75 * browser does not support Object Urls.
        76 * @private
        77 */
        78goog.fs.url.findUrlObject_ = function() {
        79 // This is what the spec says to do
        80 // http://dev.w3.org/2006/webapi/FileAPI/#dfn-createObjectURL
        81 if (goog.isDef(goog.global.URL) &&
        82 goog.isDef(goog.global.URL.createObjectURL)) {
        83 return /** @type {goog.fs.url.UrlObject_} */ (goog.global.URL);
        84 // This is what Chrome does (as of 10.0.648.6 dev)
        85 } else if (goog.isDef(goog.global.webkitURL) &&
        86 goog.isDef(goog.global.webkitURL.createObjectURL)) {
        87 return /** @type {goog.fs.url.UrlObject_} */ (goog.global.webkitURL);
        88 // This is what the spec used to say to do
        89 } else if (goog.isDef(goog.global.createObjectURL)) {
        90 return /** @type {goog.fs.url.UrlObject_} */ (goog.global);
        91 } else {
        92 return null;
        93 }
        94};
        95
        96
        97/**
        98 * Checks whether this browser supports Object Urls. If not, calls to
        99 * createObjectUrl and revokeObjectUrl will result in an error.
        100 *
        101 * @return {boolean} True if this browser supports Object Urls.
        102 */
        103goog.fs.url.browserSupportsObjectUrls = function() {
        104 return goog.fs.url.findUrlObject_() != null;
        105};
        \ No newline at end of file diff --git a/docs/source/lib/goog/functions/functions.js.src.html b/docs/source/lib/goog/functions/functions.js.src.html new file mode 100644 index 0000000..6adbc1f --- /dev/null +++ b/docs/source/lib/goog/functions/functions.js.src.html @@ -0,0 +1 @@ +functions.js

        lib/goog/functions/functions.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for creating functions. Loosely inspired by the
        17 * java classes: http://goo.gl/GM0Hmu and http://goo.gl/6k7nI8.
        18 *
        19 * @author nicksantos@google.com (Nick Santos)
        20 */
        21
        22
        23goog.provide('goog.functions');
        24
        25
        26/**
        27 * Creates a function that always returns the same value.
        28 * @param {T} retValue The value to return.
        29 * @return {function():T} The new function.
        30 * @template T
        31 */
        32goog.functions.constant = function(retValue) {
        33 return function() {
        34 return retValue;
        35 };
        36};
        37
        38
        39/**
        40 * Always returns false.
        41 * @type {function(...): boolean}
        42 */
        43goog.functions.FALSE = goog.functions.constant(false);
        44
        45
        46/**
        47 * Always returns true.
        48 * @type {function(...): boolean}
        49 */
        50goog.functions.TRUE = goog.functions.constant(true);
        51
        52
        53/**
        54 * Always returns NULL.
        55 * @type {function(...): null}
        56 */
        57goog.functions.NULL = goog.functions.constant(null);
        58
        59
        60/**
        61 * A simple function that returns the first argument of whatever is passed
        62 * into it.
        63 * @param {T=} opt_returnValue The single value that will be returned.
        64 * @param {...*} var_args Optional trailing arguments. These are ignored.
        65 * @return {T} The first argument passed in, or undefined if nothing was passed.
        66 * @template T
        67 */
        68goog.functions.identity = function(opt_returnValue, var_args) {
        69 return opt_returnValue;
        70};
        71
        72
        73/**
        74 * Creates a function that always throws an error with the given message.
        75 * @param {string} message The error message.
        76 * @return {!Function} The error-throwing function.
        77 */
        78goog.functions.error = function(message) {
        79 return function() {
        80 throw Error(message);
        81 };
        82};
        83
        84
        85/**
        86 * Creates a function that throws the given object.
        87 * @param {*} err An object to be thrown.
        88 * @return {!Function} The error-throwing function.
        89 */
        90goog.functions.fail = function(err) {
        91 return function() {
        92 throw err;
        93 }
        94};
        95
        96
        97/**
        98 * Given a function, create a function that keeps opt_numArgs arguments and
        99 * silently discards all additional arguments.
        100 * @param {Function} f The original function.
        101 * @param {number=} opt_numArgs The number of arguments to keep. Defaults to 0.
        102 * @return {!Function} A version of f that only keeps the first opt_numArgs
        103 * arguments.
        104 */
        105goog.functions.lock = function(f, opt_numArgs) {
        106 opt_numArgs = opt_numArgs || 0;
        107 return function() {
        108 return f.apply(this, Array.prototype.slice.call(arguments, 0, opt_numArgs));
        109 };
        110};
        111
        112
        113/**
        114 * Creates a function that returns its nth argument.
        115 * @param {number} n The position of the return argument.
        116 * @return {!Function} A new function.
        117 */
        118goog.functions.nth = function(n) {
        119 return function() {
        120 return arguments[n];
        121 };
        122};
        123
        124
        125/**
        126 * Given a function, create a new function that swallows its return value
        127 * and replaces it with a new one.
        128 * @param {Function} f A function.
        129 * @param {T} retValue A new return value.
        130 * @return {function(...?):T} A new function.
        131 * @template T
        132 */
        133goog.functions.withReturnValue = function(f, retValue) {
        134 return goog.functions.sequence(f, goog.functions.constant(retValue));
        135};
        136
        137
        138/**
        139 * Creates a function that returns whether its arguement equals the given value.
        140 *
        141 * Example:
        142 * var key = goog.object.findKey(obj, goog.functions.equalTo('needle'));
        143 *
        144 * @param {*} value The value to compare to.
        145 * @param {boolean=} opt_useLooseComparison Whether to use a loose (==)
        146 * comparison rather than a strict (===) one. Defaults to false.
        147 * @return {function(*):boolean} The new function.
        148 */
        149goog.functions.equalTo = function(value, opt_useLooseComparison) {
        150 return function(other) {
        151 return opt_useLooseComparison ? (value == other) : (value === other);
        152 };
        153};
        154
        155
        156/**
        157 * Creates the composition of the functions passed in.
        158 * For example, (goog.functions.compose(f, g))(a) is equivalent to f(g(a)).
        159 * @param {function(...?):T} fn The final function.
        160 * @param {...Function} var_args A list of functions.
        161 * @return {function(...?):T} The composition of all inputs.
        162 * @template T
        163 */
        164goog.functions.compose = function(fn, var_args) {
        165 var functions = arguments;
        166 var length = functions.length;
        167 return function() {
        168 var result;
        169 if (length) {
        170 result = functions[length - 1].apply(this, arguments);
        171 }
        172
        173 for (var i = length - 2; i >= 0; i--) {
        174 result = functions[i].call(this, result);
        175 }
        176 return result;
        177 };
        178};
        179
        180
        181/**
        182 * Creates a function that calls the functions passed in in sequence, and
        183 * returns the value of the last function. For example,
        184 * (goog.functions.sequence(f, g))(x) is equivalent to f(x),g(x).
        185 * @param {...Function} var_args A list of functions.
        186 * @return {!Function} A function that calls all inputs in sequence.
        187 */
        188goog.functions.sequence = function(var_args) {
        189 var functions = arguments;
        190 var length = functions.length;
        191 return function() {
        192 var result;
        193 for (var i = 0; i < length; i++) {
        194 result = functions[i].apply(this, arguments);
        195 }
        196 return result;
        197 };
        198};
        199
        200
        201/**
        202 * Creates a function that returns true if each of its components evaluates
        203 * to true. The components are evaluated in order, and the evaluation will be
        204 * short-circuited as soon as a function returns false.
        205 * For example, (goog.functions.and(f, g))(x) is equivalent to f(x) && g(x).
        206 * @param {...Function} var_args A list of functions.
        207 * @return {function(...?):boolean} A function that ANDs its component
        208 * functions.
        209 */
        210goog.functions.and = function(var_args) {
        211 var functions = arguments;
        212 var length = functions.length;
        213 return function() {
        214 for (var i = 0; i < length; i++) {
        215 if (!functions[i].apply(this, arguments)) {
        216 return false;
        217 }
        218 }
        219 return true;
        220 };
        221};
        222
        223
        224/**
        225 * Creates a function that returns true if any of its components evaluates
        226 * to true. The components are evaluated in order, and the evaluation will be
        227 * short-circuited as soon as a function returns true.
        228 * For example, (goog.functions.or(f, g))(x) is equivalent to f(x) || g(x).
        229 * @param {...Function} var_args A list of functions.
        230 * @return {function(...?):boolean} A function that ORs its component
        231 * functions.
        232 */
        233goog.functions.or = function(var_args) {
        234 var functions = arguments;
        235 var length = functions.length;
        236 return function() {
        237 for (var i = 0; i < length; i++) {
        238 if (functions[i].apply(this, arguments)) {
        239 return true;
        240 }
        241 }
        242 return false;
        243 };
        244};
        245
        246
        247/**
        248 * Creates a function that returns the Boolean opposite of a provided function.
        249 * For example, (goog.functions.not(f))(x) is equivalent to !f(x).
        250 * @param {!Function} f The original function.
        251 * @return {function(...?):boolean} A function that delegates to f and returns
        252 * opposite.
        253 */
        254goog.functions.not = function(f) {
        255 return function() {
        256 return !f.apply(this, arguments);
        257 };
        258};
        259
        260
        261/**
        262 * Generic factory function to construct an object given the constructor
        263 * and the arguments. Intended to be bound to create object factories.
        264 *
        265 * Example:
        266 *
        267 * var factory = goog.partial(goog.functions.create, Class);
        268 *
        269 * @param {function(new:T, ...)} constructor The constructor for the Object.
        270 * @param {...*} var_args The arguments to be passed to the constructor.
        271 * @return {T} A new instance of the class given in {@code constructor}.
        272 * @template T
        273 */
        274goog.functions.create = function(constructor, var_args) {
        275 /**
        276 * @constructor
        277 * @final
        278 */
        279 var temp = function() {};
        280 temp.prototype = constructor.prototype;
        281
        282 // obj will have constructor's prototype in its chain and
        283 // 'obj instanceof constructor' will be true.
        284 var obj = new temp();
        285
        286 // obj is initialized by constructor.
        287 // arguments is only array-like so lacks shift(), but can be used with
        288 // the Array prototype function.
        289 constructor.apply(obj, Array.prototype.slice.call(arguments, 1));
        290 return obj;
        291};
        292
        293
        294/**
        295 * @define {boolean} Whether the return value cache should be used.
        296 * This should only be used to disable caches when testing.
        297 */
        298goog.define('goog.functions.CACHE_RETURN_VALUE', true);
        299
        300
        301/**
        302 * Gives a wrapper function that caches the return value of a parameterless
        303 * function when first called.
        304 *
        305 * When called for the first time, the given function is called and its
        306 * return value is cached (thus this is only appropriate for idempotent
        307 * functions). Subsequent calls will return the cached return value. This
        308 * allows the evaluation of expensive functions to be delayed until first used.
        309 *
        310 * To cache the return values of functions with parameters, see goog.memoize.
        311 *
        312 * @param {!function():T} fn A function to lazily evaluate.
        313 * @return {!function():T} A wrapped version the function.
        314 * @template T
        315 */
        316goog.functions.cacheReturnValue = function(fn) {
        317 var called = false;
        318 var value;
        319
        320 return function() {
        321 if (!goog.functions.CACHE_RETURN_VALUE) {
        322 return fn();
        323 }
        324
        325 if (!called) {
        326 value = fn();
        327 called = true;
        328 }
        329
        330 return value;
        331 }
        332};
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/safehtml.js.src.html b/docs/source/lib/goog/html/safehtml.js.src.html new file mode 100644 index 0000000..f4b4c2a --- /dev/null +++ b/docs/source/lib/goog/html/safehtml.js.src.html @@ -0,0 +1 @@ +safehtml.js

        lib/goog/html/safehtml.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15
        16/**
        17 * @fileoverview The SafeHtml type and its builders.
        18 *
        19 * TODO(user): Link to document stating type contract.
        20 */
        21
        22goog.provide('goog.html.SafeHtml');
        23
        24goog.require('goog.array');
        25goog.require('goog.asserts');
        26goog.require('goog.dom.TagName');
        27goog.require('goog.dom.tags');
        28goog.require('goog.html.SafeStyle');
        29goog.require('goog.html.SafeStyleSheet');
        30goog.require('goog.html.SafeUrl');
        31goog.require('goog.html.TrustedResourceUrl');
        32goog.require('goog.i18n.bidi.Dir');
        33goog.require('goog.i18n.bidi.DirectionalString');
        34goog.require('goog.object');
        35goog.require('goog.string');
        36goog.require('goog.string.Const');
        37goog.require('goog.string.TypedString');
        38
        39
        40
        41/**
        42 * A string that is safe to use in HTML context in DOM APIs and HTML documents.
        43 *
        44 * A SafeHtml is a string-like object that carries the security type contract
        45 * that its value as a string will not cause untrusted script execution when
        46 * evaluated as HTML in a browser.
        47 *
        48 * Values of this type are guaranteed to be safe to use in HTML contexts,
        49 * such as, assignment to the innerHTML DOM property, or interpolation into
        50 * a HTML template in HTML PC_DATA context, in the sense that the use will not
        51 * result in a Cross-Site-Scripting vulnerability.
        52 *
        53 * Instances of this type must be created via the factory methods
        54 * ({@code goog.html.SafeHtml.create}, {@code goog.html.SafeHtml.htmlEscape}),
        55 * etc and not by invoking its constructor. The constructor intentionally
        56 * takes no parameters and the type is immutable; hence only a default instance
        57 * corresponding to the empty string can be obtained via constructor invocation.
        58 *
        59 * @see goog.html.SafeHtml#create
        60 * @see goog.html.SafeHtml#htmlEscape
        61 * @constructor
        62 * @final
        63 * @struct
        64 * @implements {goog.i18n.bidi.DirectionalString}
        65 * @implements {goog.string.TypedString}
        66 */
        67goog.html.SafeHtml = function() {
        68 /**
        69 * The contained value of this SafeHtml. The field has a purposely ugly
        70 * name to make (non-compiled) code that attempts to directly access this
        71 * field stand out.
        72 * @private {string}
        73 */
        74 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
        75
        76 /**
        77 * A type marker used to implement additional run-time type checking.
        78 * @see goog.html.SafeHtml#unwrap
        79 * @const
        80 * @private
        81 */
        82 this.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        83 goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        84
        85 /**
        86 * This SafeHtml's directionality, or null if unknown.
        87 * @private {?goog.i18n.bidi.Dir}
        88 */
        89 this.dir_ = null;
        90};
        91
        92
        93/**
        94 * @override
        95 * @const
        96 */
        97goog.html.SafeHtml.prototype.implementsGoogI18nBidiDirectionalString = true;
        98
        99
        100/** @override */
        101goog.html.SafeHtml.prototype.getDirection = function() {
        102 return this.dir_;
        103};
        104
        105
        106/**
        107 * @override
        108 * @const
        109 */
        110goog.html.SafeHtml.prototype.implementsGoogStringTypedString = true;
        111
        112
        113/**
        114 * Returns this SafeHtml's value a string.
        115 *
        116 * IMPORTANT: In code where it is security relevant that an object's type is
        117 * indeed {@code SafeHtml}, use {@code goog.html.SafeHtml.unwrap} instead of
        118 * this method. If in doubt, assume that it's security relevant. In particular,
        119 * note that goog.html functions which return a goog.html type do not guarantee
        120 * that the returned instance is of the right type. For example:
        121 *
        122 * <pre>
        123 * var fakeSafeHtml = new String('fake');
        124 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        125 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        126 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        127 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
        128 * // instanceof goog.html.SafeHtml.
        129 * </pre>
        130 *
        131 * @see goog.html.SafeHtml#unwrap
        132 * @override
        133 */
        134goog.html.SafeHtml.prototype.getTypedStringValue = function() {
        135 return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
        136};
        137
        138
        139if (goog.DEBUG) {
        140 /**
        141 * Returns a debug string-representation of this value.
        142 *
        143 * To obtain the actual string value wrapped in a SafeHtml, use
        144 * {@code goog.html.SafeHtml.unwrap}.
        145 *
        146 * @see goog.html.SafeHtml#unwrap
        147 * @override
        148 */
        149 goog.html.SafeHtml.prototype.toString = function() {
        150 return 'SafeHtml{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
        151 '}';
        152 };
        153}
        154
        155
        156/**
        157 * Performs a runtime check that the provided object is indeed a SafeHtml
        158 * object, and returns its value.
        159 * @param {!goog.html.SafeHtml} safeHtml The object to extract from.
        160 * @return {string} The SafeHtml object's contained string, unless the run-time
        161 * type check fails. In that case, {@code unwrap} returns an innocuous
        162 * string, or, if assertions are enabled, throws
        163 * {@code goog.asserts.AssertionError}.
        164 */
        165goog.html.SafeHtml.unwrap = function(safeHtml) {
        166 // Perform additional run-time type-checking to ensure that safeHtml is indeed
        167 // an instance of the expected type. This provides some additional protection
        168 // against security bugs due to application code that disables type checks.
        169 // Specifically, the following checks are performed:
        170 // 1. The object is an instance of the expected type.
        171 // 2. The object is not an instance of a subclass.
        172 // 3. The object carries a type marker for the expected type. "Faking" an
        173 // object requires a reference to the type marker, which has names intended
        174 // to stand out in code reviews.
        175 if (safeHtml instanceof goog.html.SafeHtml &&
        176 safeHtml.constructor === goog.html.SafeHtml &&
        177 safeHtml.SAFE_HTML_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        178 goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        179 return safeHtml.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
        180 } else {
        181 goog.asserts.fail('expected object of type SafeHtml, got \'' +
        182 safeHtml + '\'');
        183 return 'type_error:SafeHtml';
        184 }
        185};
        186
        187
        188/**
        189 * Shorthand for union of types that can sensibly be converted to strings
        190 * or might already be SafeHtml (as SafeHtml is a goog.string.TypedString).
        191 * @private
        192 * @typedef {string|number|boolean|!goog.string.TypedString|
        193 * !goog.i18n.bidi.DirectionalString}
        194 */
        195goog.html.SafeHtml.TextOrHtml_;
        196
        197
        198/**
        199 * Returns HTML-escaped text as a SafeHtml object.
        200 *
        201 * If text is of a type that implements
        202 * {@code goog.i18n.bidi.DirectionalString}, the directionality of the new
        203 * {@code SafeHtml} object is set to {@code text}'s directionality, if known.
        204 * Otherwise, the directionality of the resulting SafeHtml is unknown (i.e.,
        205 * {@code null}).
        206 *
        207 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
        208 * the parameter is of type SafeHtml it is returned directly (no escaping
        209 * is done).
        210 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
        211 */
        212goog.html.SafeHtml.htmlEscape = function(textOrHtml) {
        213 if (textOrHtml instanceof goog.html.SafeHtml) {
        214 return textOrHtml;
        215 }
        216 var dir = null;
        217 if (textOrHtml.implementsGoogI18nBidiDirectionalString) {
        218 dir = textOrHtml.getDirection();
        219 }
        220 var textAsString;
        221 if (textOrHtml.implementsGoogStringTypedString) {
        222 textAsString = textOrHtml.getTypedStringValue();
        223 } else {
        224 textAsString = String(textOrHtml);
        225 }
        226 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        227 goog.string.htmlEscape(textAsString), dir);
        228};
        229
        230
        231/**
        232 * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
        233 * &lt;br&gt;.
        234 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
        235 * the parameter is of type SafeHtml it is returned directly (no escaping
        236 * is done).
        237 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
        238 */
        239goog.html.SafeHtml.htmlEscapePreservingNewlines = function(textOrHtml) {
        240 if (textOrHtml instanceof goog.html.SafeHtml) {
        241 return textOrHtml;
        242 }
        243 var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
        244 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        245 goog.string.newLineToBr(goog.html.SafeHtml.unwrap(html)),
        246 html.getDirection());
        247};
        248
        249
        250/**
        251 * Returns HTML-escaped text as a SafeHtml object, with newlines changed to
        252 * &lt;br&gt; and escaping whitespace to preserve spatial formatting. Character
        253 * entity #160 is used to make it safer for XML.
        254 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text to escape. If
        255 * the parameter is of type SafeHtml it is returned directly (no escaping
        256 * is done).
        257 * @return {!goog.html.SafeHtml} The escaped text, wrapped as a SafeHtml.
        258 */
        259goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces = function(
        260 textOrHtml) {
        261 if (textOrHtml instanceof goog.html.SafeHtml) {
        262 return textOrHtml;
        263 }
        264 var html = goog.html.SafeHtml.htmlEscape(textOrHtml);
        265 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        266 goog.string.whitespaceEscape(goog.html.SafeHtml.unwrap(html)),
        267 html.getDirection());
        268};
        269
        270
        271/**
        272 * Coerces an arbitrary object into a SafeHtml object.
        273 *
        274 * If {@code textOrHtml} is already of type {@code goog.html.SafeHtml}, the same
        275 * object is returned. Otherwise, {@code textOrHtml} is coerced to string, and
        276 * HTML-escaped. If {@code textOrHtml} is of a type that implements
        277 * {@code goog.i18n.bidi.DirectionalString}, its directionality, if known, is
        278 * preserved.
        279 *
        280 * @param {!goog.html.SafeHtml.TextOrHtml_} textOrHtml The text or SafeHtml to
        281 * coerce.
        282 * @return {!goog.html.SafeHtml} The resulting SafeHtml object.
        283 * @deprecated Use goog.html.SafeHtml.htmlEscape.
        284 */
        285goog.html.SafeHtml.from = goog.html.SafeHtml.htmlEscape;
        286
        287
        288/**
        289 * @const
        290 * @private
        291 */
        292goog.html.SafeHtml.VALID_NAMES_IN_TAG_ = /^[a-zA-Z0-9-]+$/;
        293
        294
        295/**
        296 * Set of attributes containing URL as defined at
        297 * http://www.w3.org/TR/html5/index.html#attributes-1.
        298 * @private @const {!Object<string,boolean>}
        299 */
        300goog.html.SafeHtml.URL_ATTRIBUTES_ = goog.object.createSet('action', 'cite',
        301 'data', 'formaction', 'href', 'manifest', 'poster', 'src');
        302
        303
        304/**
        305 * Tags which are unsupported via create(). They might be supported via a
        306 * tag-specific create method. These are tags which might require a
        307 * TrustedResourceUrl in one of their attributes or a restricted type for
        308 * their content.
        309 * @private @const {!Object<string,boolean>}
        310 */
        311goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_ = goog.object.createSet(
        312 goog.dom.TagName.EMBED, goog.dom.TagName.IFRAME, goog.dom.TagName.LINK,
        313 goog.dom.TagName.OBJECT, goog.dom.TagName.SCRIPT, goog.dom.TagName.STYLE,
        314 goog.dom.TagName.TEMPLATE);
        315
        316
        317/**
        318 * @typedef {string|number|goog.string.TypedString|
        319 * goog.html.SafeStyle.PropertyMap}
        320 * @private
        321 */
        322goog.html.SafeHtml.AttributeValue_;
        323
        324
        325/**
        326 * Creates a SafeHtml content consisting of a tag with optional attributes and
        327 * optional content.
        328 *
        329 * For convenience tag names and attribute names are accepted as regular
        330 * strings, instead of goog.string.Const. Nevertheless, you should not pass
        331 * user-controlled values to these parameters. Note that these parameters are
        332 * syntactically validated at runtime, and invalid values will result in
        333 * an exception.
        334 *
        335 * Example usage:
        336 *
        337 * goog.html.SafeHtml.create('br');
        338 * goog.html.SafeHtml.create('div', {'class': 'a'});
        339 * goog.html.SafeHtml.create('p', {}, 'a');
        340 * goog.html.SafeHtml.create('p', {}, goog.html.SafeHtml.create('br'));
        341 *
        342 * goog.html.SafeHtml.create('span', {
        343 * 'style': {'margin': '0'}
        344 * });
        345 *
        346 * To guarantee SafeHtml's type contract is upheld there are restrictions on
        347 * attribute values and tag names.
        348 *
        349 * - For attributes which contain script code (on*), a goog.string.Const is
        350 * required.
        351 * - For attributes which contain style (style), a goog.html.SafeStyle or a
        352 * goog.html.SafeStyle.PropertyMap is required.
        353 * - For attributes which are interpreted as URLs (e.g. src, href) a
        354 * goog.html.SafeUrl or goog.string.Const is required.
        355 * - For tags which can load code, more specific goog.html.SafeHtml.create*()
        356 * functions must be used. Tags which can load code and are not supported by
        357 * this function are embed, iframe, link, object, script, style, and template.
        358 *
        359 * @param {string} tagName The name of the tag. Only tag names consisting of
        360 * [a-zA-Z0-9-] are allowed. Tag names documented above are disallowed.
        361 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
        362 * opt_attributes Mapping from attribute names to their values. Only
        363 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
        364 * undefined causes the attribute to be omitted.
        365 * @param {!goog.html.SafeHtml.TextOrHtml_|
        366 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
        367 * HTML-escape and put inside the tag. This must be empty for void tags
        368 * like <br>. Array elements are concatenated.
        369 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
        370 * @throws {Error} If invalid tag name, attribute name, or attribute value is
        371 * provided.
        372 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
        373 */
        374goog.html.SafeHtml.create = function(tagName, opt_attributes, opt_content) {
        375 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(tagName)) {
        376 throw Error('Invalid tag name <' + tagName + '>.');
        377 }
        378 if (tagName.toUpperCase() in goog.html.SafeHtml.NOT_ALLOWED_TAG_NAMES_) {
        379 throw Error('Tag name <' + tagName + '> is not allowed for SafeHtml.');
        380 }
        381 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
        382 tagName, opt_attributes, opt_content);
        383};
        384
        385
        386/**
        387 * Creates a SafeHtml representing an iframe tag.
        388 *
        389 * By default the sandbox attribute is set to an empty value, which is the most
        390 * secure option, as it confers the iframe the least privileges. If this
        391 * is too restrictive then granting individual privileges is the preferable
        392 * option. Unsetting the attribute entirely is the least secure option and
        393 * should never be done unless it's stricly necessary.
        394 *
        395 * @param {goog.html.TrustedResourceUrl=} opt_src The value of the src
        396 * attribute. If null or undefined src will not be set.
        397 * @param {goog.html.SafeHtml=} opt_srcdoc The value of the srcdoc attribute.
        398 * If null or undefined srcdoc will not be set.
        399 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
        400 * opt_attributes Mapping from attribute names to their values. Only
        401 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
        402 * undefined causes the attribute to be omitted.
        403 * @param {!goog.html.SafeHtml.TextOrHtml_|
        404 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content Content to
        405 * HTML-escape and put inside the tag. Array elements are concatenated.
        406 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
        407 * @throws {Error} If invalid tag name, attribute name, or attribute value is
        408 * provided. If opt_attributes contains the src or srcdoc attributes.
        409 */
        410goog.html.SafeHtml.createIframe = function(
        411 opt_src, opt_srcdoc, opt_attributes, opt_content) {
        412 var fixedAttributes = {};
        413 fixedAttributes['src'] = opt_src || null;
        414 fixedAttributes['srcdoc'] = opt_srcdoc || null;
        415 var defaultAttributes = {'sandbox': ''};
        416 var attributes = goog.html.SafeHtml.combineAttributes(
        417 fixedAttributes, defaultAttributes, opt_attributes);
        418 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
        419 'iframe', attributes, opt_content);
        420};
        421
        422
        423/**
        424 * Creates a SafeHtml representing a style tag. The type attribute is set
        425 * to "text/css".
        426 * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
        427 * styleSheet Content to put inside the tag. Array elements are
        428 * concatenated.
        429 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
        430 * opt_attributes Mapping from attribute names to their values. Only
        431 * attribute names consisting of [a-zA-Z0-9-] are allowed. Value of null or
        432 * undefined causes the attribute to be omitted.
        433 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
        434 * @throws {Error} If invalid attribute name or attribute value is provided. If
        435 * opt_attributes contains the type attribute.
        436 */
        437goog.html.SafeHtml.createStyle = function(styleSheet, opt_attributes) {
        438 var fixedAttributes = {'type': 'text/css'};
        439 var defaultAttributes = {};
        440 var attributes = goog.html.SafeHtml.combineAttributes(
        441 fixedAttributes, defaultAttributes, opt_attributes);
        442
        443 var content = '';
        444 styleSheet = goog.array.concat(styleSheet);
        445 for (var i = 0; i < styleSheet.length; i++) {
        446 content += goog.html.SafeStyleSheet.unwrap(styleSheet[i]);
        447 }
        448 // Convert to SafeHtml so that it's not HTML-escaped.
        449 var htmlContent = goog.html.SafeHtml
        450 .createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        451 content, goog.i18n.bidi.Dir.NEUTRAL);
        452 return goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse(
        453 'style', attributes, htmlContent);
        454};
        455
        456
        457/**
        458 * @param {string} tagName The tag name.
        459 * @param {string} name The attribute name.
        460 * @param {!goog.html.SafeHtml.AttributeValue_} value The attribute value.
        461 * @return {string} A "name=value" string.
        462 * @throws {Error} If attribute value is unsafe for the given tag and attribute.
        463 * @private
        464 */
        465goog.html.SafeHtml.getAttrNameAndValue_ = function(tagName, name, value) {
        466 // If it's goog.string.Const, allow any valid attribute name.
        467 if (value instanceof goog.string.Const) {
        468 value = goog.string.Const.unwrap(value);
        469 } else if (name.toLowerCase() == 'style') {
        470 value = goog.html.SafeHtml.getStyleValue_(value);
        471 } else if (/^on/i.test(name)) {
        472 // TODO(jakubvrana): Disallow more attributes with a special meaning.
        473 throw Error('Attribute "' + name +
        474 '" requires goog.string.Const value, "' + value + '" given.');
        475 // URL attributes handled differently accroding to tag.
        476 } else if (name.toLowerCase() in goog.html.SafeHtml.URL_ATTRIBUTES_) {
        477 if (value instanceof goog.html.TrustedResourceUrl) {
        478 value = goog.html.TrustedResourceUrl.unwrap(value);
        479 } else if (value instanceof goog.html.SafeUrl) {
        480 value = goog.html.SafeUrl.unwrap(value);
        481 } else {
        482 // TODO(user): Allow strings and sanitize them automatically,
        483 // so that it's consistent with accepting a map directly for "style".
        484 throw Error('Attribute "' + name + '" on tag "' + tagName +
        485 '" requires goog.html.SafeUrl or goog.string.Const value, "' +
        486 value + '" given.');
        487 }
        488 }
        489
        490 // Accept SafeUrl, TrustedResourceUrl, etc. for attributes which only require
        491 // HTML-escaping.
        492 if (value.implementsGoogStringTypedString) {
        493 // Ok to call getTypedStringValue() since there's no reliance on the type
        494 // contract for security here.
        495 value = value.getTypedStringValue();
        496 }
        497
        498 goog.asserts.assert(goog.isString(value) || goog.isNumber(value),
        499 'String or number value expected, got ' +
        500 (typeof value) + ' with value: ' + value);
        501 return name + '="' + goog.string.htmlEscape(String(value)) + '"';
        502};
        503
        504
        505/**
        506 * Gets value allowed in "style" attribute.
        507 * @param {goog.html.SafeHtml.AttributeValue_} value It could be SafeStyle or a
        508 * map which will be passed to goog.html.SafeStyle.create.
        509 * @return {string} Unwrapped value.
        510 * @throws {Error} If string value is given.
        511 * @private
        512 */
        513goog.html.SafeHtml.getStyleValue_ = function(value) {
        514 if (!goog.isObject(value)) {
        515 throw Error('The "style" attribute requires goog.html.SafeStyle or map ' +
        516 'of style properties, ' + (typeof value) + ' given: ' + value);
        517 }
        518 if (!(value instanceof goog.html.SafeStyle)) {
        519 // Process the property bag into a style object.
        520 value = goog.html.SafeStyle.create(value);
        521 }
        522 return goog.html.SafeStyle.unwrap(value);
        523};
        524
        525
        526/**
        527 * Creates a SafeHtml content with known directionality consisting of a tag with
        528 * optional attributes and optional content.
        529 * @param {!goog.i18n.bidi.Dir} dir Directionality.
        530 * @param {string} tagName
        531 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
        532 * @param {!goog.html.SafeHtml.TextOrHtml_|
        533 * !Array<!goog.html.SafeHtml.TextOrHtml_>=} opt_content
        534 * @return {!goog.html.SafeHtml} The SafeHtml content with the tag.
        535 */
        536goog.html.SafeHtml.createWithDir = function(dir, tagName, opt_attributes,
        537 opt_content) {
        538 var html = goog.html.SafeHtml.create(tagName, opt_attributes, opt_content);
        539 html.dir_ = dir;
        540 return html;
        541};
        542
        543
        544/**
        545 * Creates a new SafeHtml object by concatenating values.
        546 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
        547 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Values to concatenate.
        548 * @return {!goog.html.SafeHtml}
        549 */
        550goog.html.SafeHtml.concat = function(var_args) {
        551 var dir = goog.i18n.bidi.Dir.NEUTRAL;
        552 var content = '';
        553
        554 /**
        555 * @param {!goog.html.SafeHtml.TextOrHtml_|
        556 * !Array<!goog.html.SafeHtml.TextOrHtml_>} argument
        557 */
        558 var addArgument = function(argument) {
        559 if (goog.isArray(argument)) {
        560 goog.array.forEach(argument, addArgument);
        561 } else {
        562 var html = goog.html.SafeHtml.htmlEscape(argument);
        563 content += goog.html.SafeHtml.unwrap(html);
        564 var htmlDir = html.getDirection();
        565 if (dir == goog.i18n.bidi.Dir.NEUTRAL) {
        566 dir = htmlDir;
        567 } else if (htmlDir != goog.i18n.bidi.Dir.NEUTRAL && dir != htmlDir) {
        568 dir = null;
        569 }
        570 }
        571 };
        572
        573 goog.array.forEach(arguments, addArgument);
        574 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        575 content, dir);
        576};
        577
        578
        579/**
        580 * Creates a new SafeHtml object with known directionality by concatenating the
        581 * values.
        582 * @param {!goog.i18n.bidi.Dir} dir Directionality.
        583 * @param {...(!goog.html.SafeHtml.TextOrHtml_|
        584 * !Array<!goog.html.SafeHtml.TextOrHtml_>)} var_args Elements of array
        585 * arguments would be processed recursively.
        586 * @return {!goog.html.SafeHtml}
        587 */
        588goog.html.SafeHtml.concatWithDir = function(dir, var_args) {
        589 var html = goog.html.SafeHtml.concat(goog.array.slice(arguments, 1));
        590 html.dir_ = dir;
        591 return html;
        592};
        593
        594
        595/**
        596 * Type marker for the SafeHtml type, used to implement additional run-time
        597 * type checking.
        598 * @const
        599 * @private
        600 */
        601goog.html.SafeHtml.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        602
        603
        604/**
        605 * Package-internal utility method to create SafeHtml instances.
        606 *
        607 * @param {string} html The string to initialize the SafeHtml object with.
        608 * @param {?goog.i18n.bidi.Dir} dir The directionality of the SafeHtml to be
        609 * constructed, or null if unknown.
        610 * @return {!goog.html.SafeHtml} The initialized SafeHtml object.
        611 * @package
        612 */
        613goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse = function(
        614 html, dir) {
        615 return new goog.html.SafeHtml().initSecurityPrivateDoNotAccessOrElse_(
        616 html, dir);
        617};
        618
        619
        620/**
        621 * Called from createSafeHtmlSecurityPrivateDoNotAccessOrElse(). This
        622 * method exists only so that the compiler can dead code eliminate static
        623 * fields (like EMPTY) when they're not accessed.
        624 * @param {string} html
        625 * @param {?goog.i18n.bidi.Dir} dir
        626 * @return {!goog.html.SafeHtml}
        627 * @private
        628 */
        629goog.html.SafeHtml.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
        630 html, dir) {
        631 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = html;
        632 this.dir_ = dir;
        633 return this;
        634};
        635
        636
        637/**
        638 * Like create() but does not restrict which tags can be constructed.
        639 *
        640 * @param {string} tagName Tag name. Set or validated by caller.
        641 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=} opt_attributes
        642 * @param {(!goog.html.SafeHtml.TextOrHtml_|
        643 * !Array<!goog.html.SafeHtml.TextOrHtml_>)=} opt_content
        644 * @return {!goog.html.SafeHtml}
        645 * @throws {Error} If invalid or unsafe attribute name or value is provided.
        646 * @throws {goog.asserts.AssertionError} If content for void tag is provided.
        647 * @package
        648 */
        649goog.html.SafeHtml.createSafeHtmlTagSecurityPrivateDoNotAccessOrElse =
        650 function(tagName, opt_attributes, opt_content) {
        651 var dir = null;
        652 var result = '<' + tagName;
        653
        654 if (opt_attributes) {
        655 for (var name in opt_attributes) {
        656 if (!goog.html.SafeHtml.VALID_NAMES_IN_TAG_.test(name)) {
        657 throw Error('Invalid attribute name "' + name + '".');
        658 }
        659 var value = opt_attributes[name];
        660 if (!goog.isDefAndNotNull(value)) {
        661 continue;
        662 }
        663 result += ' ' +
        664 goog.html.SafeHtml.getAttrNameAndValue_(tagName, name, value);
        665 }
        666 }
        667
        668 var content = opt_content;
        669 if (!goog.isDefAndNotNull(content)) {
        670 content = [];
        671 } else if (!goog.isArray(content)) {
        672 content = [content];
        673 }
        674
        675 if (goog.dom.tags.isVoidTag(tagName.toLowerCase())) {
        676 goog.asserts.assert(!content.length,
        677 'Void tag <' + tagName + '> does not allow content.');
        678 result += '>';
        679 } else {
        680 var html = goog.html.SafeHtml.concat(content);
        681 result += '>' + goog.html.SafeHtml.unwrap(html) + '</' + tagName + '>';
        682 dir = html.getDirection();
        683 }
        684
        685 var dirAttribute = opt_attributes && opt_attributes['dir'];
        686 if (dirAttribute) {
        687 if (/^(ltr|rtl|auto)$/i.test(dirAttribute)) {
        688 // If the tag has the "dir" attribute specified then its direction is
        689 // neutral because it can be safely used in any context.
        690 dir = goog.i18n.bidi.Dir.NEUTRAL;
        691 } else {
        692 dir = null;
        693 }
        694 }
        695
        696 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        697 result, dir);
        698};
        699
        700
        701/**
        702 * @param {!Object<string, string>} fixedAttributes
        703 * @param {!Object<string, string>} defaultAttributes
        704 * @param {!Object<string, goog.html.SafeHtml.AttributeValue_>=}
        705 * opt_attributes Optional attributes passed to create*().
        706 * @return {!Object<string, goog.html.SafeHtml.AttributeValue_>}
        707 * @throws {Error} If opt_attributes contains an attribute with the same name
        708 * as an attribute in fixedAttributes.
        709 * @package
        710 */
        711goog.html.SafeHtml.combineAttributes = function(
        712 fixedAttributes, defaultAttributes, opt_attributes) {
        713 var combinedAttributes = {};
        714 var name;
        715
        716 for (name in fixedAttributes) {
        717 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
        718 combinedAttributes[name] = fixedAttributes[name];
        719 }
        720 for (name in defaultAttributes) {
        721 goog.asserts.assert(name.toLowerCase() == name, 'Must be lower case');
        722 combinedAttributes[name] = defaultAttributes[name];
        723 }
        724
        725 for (name in opt_attributes) {
        726 var nameLower = name.toLowerCase();
        727 if (nameLower in fixedAttributes) {
        728 throw Error('Cannot override "' + nameLower + '" attribute, got "' +
        729 name + '" with value "' + opt_attributes[name] + '"');
        730 }
        731 if (nameLower in defaultAttributes) {
        732 delete combinedAttributes[nameLower];
        733 }
        734 combinedAttributes[name] = opt_attributes[name];
        735 }
        736
        737 return combinedAttributes;
        738};
        739
        740
        741/**
        742 * A SafeHtml instance corresponding to the HTML doctype: "<!DOCTYPE html>".
        743 * @const {!goog.html.SafeHtml}
        744 */
        745goog.html.SafeHtml.DOCTYPE_HTML =
        746 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        747 '<!DOCTYPE html>', goog.i18n.bidi.Dir.NEUTRAL);
        748
        749
        750/**
        751 * A SafeHtml instance corresponding to the empty string.
        752 * @const {!goog.html.SafeHtml}
        753 */
        754goog.html.SafeHtml.EMPTY =
        755 goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        756 '', goog.i18n.bidi.Dir.NEUTRAL);
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/safescript.js.src.html b/docs/source/lib/goog/html/safescript.js.src.html new file mode 100644 index 0000000..bf0aff1 --- /dev/null +++ b/docs/source/lib/goog/html/safescript.js.src.html @@ -0,0 +1 @@ +safescript.js

        lib/goog/html/safescript.js

        1// Copyright 2014 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The SafeScript type and its builders.
        17 *
        18 * TODO(user): Link to document stating type contract.
        19 */
        20
        21goog.provide('goog.html.SafeScript');
        22
        23goog.require('goog.asserts');
        24goog.require('goog.string.Const');
        25goog.require('goog.string.TypedString');
        26
        27
        28
        29/**
        30 * A string-like object which represents JavaScript code and that carries the
        31 * security type contract that its value, as a string, will not cause execution
        32 * of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
        33 * in a browser.
        34 *
        35 * Instances of this type must be created via the factory method
        36 * {@code goog.html.SafeScript.fromConstant} and not by invoking its
        37 * constructor. The constructor intentionally takes no parameters and the type
        38 * is immutable; hence only a default instance corresponding to the empty string
        39 * can be obtained via constructor invocation.
        40 *
        41 * A SafeScript's string representation can safely be interpolated as the
        42 * content of a script element within HTML. The SafeScript string should not be
        43 * escaped before interpolation.
        44 *
        45 * Note that the SafeScript might contain text that is attacker-controlled but
        46 * that text should have been interpolated with appropriate escaping,
        47 * sanitization and/or validation into the right location in the script, such
        48 * that it is highly constrained in its effect (for example, it had to match a
        49 * set of whitelisted words).
        50 *
        51 * A SafeScript can be constructed via security-reviewed unchecked
        52 * conversions. In this case producers of SafeScript must ensure themselves that
        53 * the SafeScript does not contain unsafe script. Note in particular that
        54 * {@code &lt;} is dangerous, even when inside JavaScript strings, and so should
        55 * always be forbidden or JavaScript escaped in user controlled input. For
        56 * example, if {@code &lt;/script&gt;&lt;script&gt;evil&lt;/script&gt;"} were
        57 * interpolated inside a JavaScript string, it would break out of the context
        58 * of the original script element and {@code evil} would execute. Also note
        59 * that within an HTML script (raw text) element, HTML character references,
        60 * such as "&lt;" are not allowed. See
        61 * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements.
        62 *
        63 * @see goog.html.SafeScript#fromConstant
        64 * @constructor
        65 * @final
        66 * @struct
        67 * @implements {goog.string.TypedString}
        68 */
        69goog.html.SafeScript = function() {
        70 /**
        71 * The contained value of this SafeScript. The field has a purposely
        72 * ugly name to make (non-compiled) code that attempts to directly access this
        73 * field stand out.
        74 * @private {string}
        75 */
        76 this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = '';
        77
        78 /**
        79 * A type marker used to implement additional run-time type checking.
        80 * @see goog.html.SafeScript#unwrap
        81 * @const
        82 * @private
        83 */
        84 this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        85 goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        86};
        87
        88
        89/**
        90 * @override
        91 * @const
        92 */
        93goog.html.SafeScript.prototype.implementsGoogStringTypedString = true;
        94
        95
        96/**
        97 * Type marker for the SafeScript type, used to implement additional
        98 * run-time type checking.
        99 * @const
        100 * @private
        101 */
        102goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        103
        104
        105/**
        106 * Creates a SafeScript object from a compile-time constant string.
        107 *
        108 * @param {!goog.string.Const} script A compile-time-constant string from which
        109 * to create a SafeScript.
        110 * @return {!goog.html.SafeScript} A SafeScript object initialized to
        111 * {@code script}.
        112 */
        113goog.html.SafeScript.fromConstant = function(script) {
        114 var scriptString = goog.string.Const.unwrap(script);
        115 if (scriptString.length === 0) {
        116 return goog.html.SafeScript.EMPTY;
        117 }
        118 return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
        119 scriptString);
        120};
        121
        122
        123/**
        124 * Returns this SafeScript's value as a string.
        125 *
        126 * IMPORTANT: In code where it is security relevant that an object's type is
        127 * indeed {@code SafeScript}, use {@code goog.html.SafeScript.unwrap} instead of
        128 * this method. If in doubt, assume that it's security relevant. In particular,
        129 * note that goog.html functions which return a goog.html type do not guarantee
        130 * the returned instance is of the right type. For example:
        131 *
        132 * <pre>
        133 * var fakeSafeHtml = new String('fake');
        134 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        135 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        136 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        137 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
        138 * // instanceof goog.html.SafeHtml.
        139 * </pre>
        140 *
        141 * @see goog.html.SafeScript#unwrap
        142 * @override
        143 */
        144goog.html.SafeScript.prototype.getTypedStringValue = function() {
        145 return this.privateDoNotAccessOrElseSafeScriptWrappedValue_;
        146};
        147
        148
        149if (goog.DEBUG) {
        150 /**
        151 * Returns a debug string-representation of this value.
        152 *
        153 * To obtain the actual string value wrapped in a SafeScript, use
        154 * {@code goog.html.SafeScript.unwrap}.
        155 *
        156 * @see goog.html.SafeScript#unwrap
        157 * @override
        158 */
        159 goog.html.SafeScript.prototype.toString = function() {
        160 return 'SafeScript{' +
        161 this.privateDoNotAccessOrElseSafeScriptWrappedValue_ + '}';
        162 };
        163}
        164
        165
        166/**
        167 * Performs a runtime check that the provided object is indeed a
        168 * SafeScript object, and returns its value.
        169 *
        170 * @param {!goog.html.SafeScript} safeScript The object to extract from.
        171 * @return {string} The safeScript object's contained string, unless
        172 * the run-time type check fails. In that case, {@code unwrap} returns an
        173 * innocuous string, or, if assertions are enabled, throws
        174 * {@code goog.asserts.AssertionError}.
        175 */
        176goog.html.SafeScript.unwrap = function(safeScript) {
        177 // Perform additional Run-time type-checking to ensure that
        178 // safeScript is indeed an instance of the expected type. This
        179 // provides some additional protection against security bugs due to
        180 // application code that disables type checks.
        181 // Specifically, the following checks are performed:
        182 // 1. The object is an instance of the expected type.
        183 // 2. The object is not an instance of a subclass.
        184 // 3. The object carries a type marker for the expected type. "Faking" an
        185 // object requires a reference to the type marker, which has names intended
        186 // to stand out in code reviews.
        187 if (safeScript instanceof goog.html.SafeScript &&
        188 safeScript.constructor === goog.html.SafeScript &&
        189 safeScript.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        190 goog.html.SafeScript.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        191 return safeScript.privateDoNotAccessOrElseSafeScriptWrappedValue_;
        192 } else {
        193 goog.asserts.fail(
        194 'expected object of type SafeScript, got \'' + safeScript + '\'');
        195 return 'type_error:SafeScript';
        196 }
        197};
        198
        199
        200/**
        201 * Package-internal utility method to create SafeScript instances.
        202 *
        203 * @param {string} script The string to initialize the SafeScript object with.
        204 * @return {!goog.html.SafeScript} The initialized SafeScript object.
        205 * @package
        206 */
        207goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse =
        208 function(script) {
        209 return new goog.html.SafeScript().initSecurityPrivateDoNotAccessOrElse_(
        210 script);
        211};
        212
        213
        214/**
        215 * Called from createSafeScriptSecurityPrivateDoNotAccessOrElse(). This
        216 * method exists only so that the compiler can dead code eliminate static
        217 * fields (like EMPTY) when they're not accessed.
        218 * @param {string} script
        219 * @return {!goog.html.SafeScript}
        220 * @private
        221 */
        222goog.html.SafeScript.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
        223 script) {
        224 this.privateDoNotAccessOrElseSafeScriptWrappedValue_ = script;
        225 return this;
        226};
        227
        228
        229/**
        230 * A SafeScript instance corresponding to the empty string.
        231 * @const {!goog.html.SafeScript}
        232 */
        233goog.html.SafeScript.EMPTY =
        234 goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse('');
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/safestyle.js.src.html b/docs/source/lib/goog/html/safestyle.js.src.html new file mode 100644 index 0000000..e97a899 --- /dev/null +++ b/docs/source/lib/goog/html/safestyle.js.src.html @@ -0,0 +1 @@ +safestyle.js

        lib/goog/html/safestyle.js

        1// Copyright 2014 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The SafeStyle type and its builders.
        17 *
        18 * TODO(user): Link to document stating type contract.
        19 */
        20
        21goog.provide('goog.html.SafeStyle');
        22
        23goog.require('goog.array');
        24goog.require('goog.asserts');
        25goog.require('goog.string');
        26goog.require('goog.string.Const');
        27goog.require('goog.string.TypedString');
        28
        29
        30
        31/**
        32 * A string-like object which represents a sequence of CSS declarations
        33 * ({@code propertyName1: propertyvalue1; propertyName2: propertyValue2; ...})
        34 * and that carries the security type contract that its value, as a string,
        35 * will not cause untrusted script execution (XSS) when evaluated as CSS in a
        36 * browser.
        37 *
        38 * Instances of this type must be created via the factory methods
        39 * ({@code goog.html.SafeStyle.create} or
        40 * {@code goog.html.SafeStyle.fromConstant}) and not by invoking its
        41 * constructor. The constructor intentionally takes no parameters and the type
        42 * is immutable; hence only a default instance corresponding to the empty string
        43 * can be obtained via constructor invocation.
        44 *
        45 * A SafeStyle's string representation ({@link #getSafeStyleString()}) can
        46 * safely:
        47 * <ul>
        48 * <li>Be interpolated as the entire content of a *quoted* HTML style
        49 * attribute, or before already existing properties. The SafeStyle string
        50 * *must be HTML-attribute-escaped* (where " and ' are escaped) before
        51 * interpolation.
        52 * <li>Be interpolated as the entire content of a {}-wrapped block within a
        53 * stylesheet, or before already existing properties. The SafeStyle string
        54 * should not be escaped before interpolation. SafeStyle's contract also
        55 * guarantees that the string will not be able to introduce new properties
        56 * or elide existing ones.
        57 * <li>Be assigned to the style property of a DOM node. The SafeStyle string
        58 * should not be escaped before being assigned to the property.
        59 * </ul>
        60 *
        61 * A SafeStyle may never contain literal angle brackets. Otherwise, it could
        62 * be unsafe to place a SafeStyle into a &lt;style&gt; tag (where it can't
        63 * be HTML escaped). For example, if the SafeStyle containing
        64 * "{@code font: 'foo &lt;style/&gt;&lt;script&gt;evil&lt;/script&gt;'}" were
        65 * interpolated within a &lt;style&gt; tag, this would then break out of the
        66 * style context into HTML.
        67 *
        68 * A SafeStyle may contain literal single or double quotes, and as such the
        69 * entire style string must be escaped when used in a style attribute (if
        70 * this were not the case, the string could contain a matching quote that
        71 * would escape from the style attribute).
        72 *
        73 * Values of this type must be composable, i.e. for any two values
        74 * {@code style1} and {@code style2} of this type,
        75 * {@code goog.html.SafeStyle.unwrap(style1) +
        76 * goog.html.SafeStyle.unwrap(style2)} must itself be a value that satisfies
        77 * the SafeStyle type constraint. This requirement implies that for any value
        78 * {@code style} of this type, {@code goog.html.SafeStyle.unwrap(style)} must
        79 * not end in a "property value" or "property name" context. For example,
        80 * a value of {@code background:url("} or {@code font-} would not satisfy the
        81 * SafeStyle contract. This is because concatenating such strings with a
        82 * second value that itself does not contain unsafe CSS can result in an
        83 * overall string that does. For example, if {@code javascript:evil())"} is
        84 * appended to {@code background:url("}, the resulting string may result in
        85 * the execution of a malicious script.
        86 *
        87 * TODO(user): Consider whether we should implement UTF-8 interchange
        88 * validity checks and blacklisting of newlines (including Unicode ones) and
        89 * other whitespace characters (\t, \f). Document here if so and also update
        90 * SafeStyle.fromConstant().
        91 *
        92 * The following example values comply with this type's contract:
        93 * <ul>
        94 * <li><pre>width: 1em;</pre>
        95 * <li><pre>height:1em;</pre>
        96 * <li><pre>width: 1em;height: 1em;</pre>
        97 * <li><pre>background:url('http://url');</pre>
        98 * </ul>
        99 * In addition, the empty string is safe for use in a CSS attribute.
        100 *
        101 * The following example values do NOT comply with this type's contract:
        102 * <ul>
        103 * <li><pre>background: red</pre> (missing a trailing semi-colon)
        104 * <li><pre>background:</pre> (missing a value and a trailing semi-colon)
        105 * <li><pre>1em</pre> (missing an attribute name, which provides context for
        106 * the value)
        107 * </ul>
        108 *
        109 * @see goog.html.SafeStyle#create
        110 * @see goog.html.SafeStyle#fromConstant
        111 * @see http://www.w3.org/TR/css3-syntax/
        112 * @constructor
        113 * @final
        114 * @struct
        115 * @implements {goog.string.TypedString}
        116 */
        117goog.html.SafeStyle = function() {
        118 /**
        119 * The contained value of this SafeStyle. The field has a purposely
        120 * ugly name to make (non-compiled) code that attempts to directly access this
        121 * field stand out.
        122 * @private {string}
        123 */
        124 this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = '';
        125
        126 /**
        127 * A type marker used to implement additional run-time type checking.
        128 * @see goog.html.SafeStyle#unwrap
        129 * @const
        130 * @private
        131 */
        132 this.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        133 goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        134};
        135
        136
        137/**
        138 * @override
        139 * @const
        140 */
        141goog.html.SafeStyle.prototype.implementsGoogStringTypedString = true;
        142
        143
        144/**
        145 * Type marker for the SafeStyle type, used to implement additional
        146 * run-time type checking.
        147 * @const
        148 * @private
        149 */
        150goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        151
        152
        153/**
        154 * Creates a SafeStyle object from a compile-time constant string.
        155 *
        156 * {@code style} should be in the format
        157 * {@code name: value; [name: value; ...]} and must not have any < or >
        158 * characters in it. This is so that SafeStyle's contract is preserved,
        159 * allowing the SafeStyle to correctly be interpreted as a sequence of CSS
        160 * declarations and without affecting the syntactic structure of any
        161 * surrounding CSS and HTML.
        162 *
        163 * This method performs basic sanity checks on the format of {@code style}
        164 * but does not constrain the format of {@code name} and {@code value}, except
        165 * for disallowing tag characters.
        166 *
        167 * @param {!goog.string.Const} style A compile-time-constant string from which
        168 * to create a SafeStyle.
        169 * @return {!goog.html.SafeStyle} A SafeStyle object initialized to
        170 * {@code style}.
        171 */
        172goog.html.SafeStyle.fromConstant = function(style) {
        173 var styleString = goog.string.Const.unwrap(style);
        174 if (styleString.length === 0) {
        175 return goog.html.SafeStyle.EMPTY;
        176 }
        177 goog.html.SafeStyle.checkStyle_(styleString);
        178 goog.asserts.assert(goog.string.endsWith(styleString, ';'),
        179 'Last character of style string is not \';\': ' + styleString);
        180 goog.asserts.assert(goog.string.contains(styleString, ':'),
        181 'Style string must contain at least one \':\', to ' +
        182 'specify a "name: value" pair: ' + styleString);
        183 return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
        184 styleString);
        185};
        186
        187
        188/**
        189 * Checks if the style definition is valid.
        190 * @param {string} style
        191 * @private
        192 */
        193goog.html.SafeStyle.checkStyle_ = function(style) {
        194 goog.asserts.assert(!/[<>]/.test(style),
        195 'Forbidden characters in style string: ' + style);
        196};
        197
        198
        199/**
        200 * Returns this SafeStyle's value as a string.
        201 *
        202 * IMPORTANT: In code where it is security relevant that an object's type is
        203 * indeed {@code SafeStyle}, use {@code goog.html.SafeStyle.unwrap} instead of
        204 * this method. If in doubt, assume that it's security relevant. In particular,
        205 * note that goog.html functions which return a goog.html type do not guarantee
        206 * the returned instance is of the right type. For example:
        207 *
        208 * <pre>
        209 * var fakeSafeHtml = new String('fake');
        210 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        211 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        212 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        213 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
        214 * // instanceof goog.html.SafeHtml.
        215 * </pre>
        216 *
        217 * @see goog.html.SafeStyle#unwrap
        218 * @override
        219 */
        220goog.html.SafeStyle.prototype.getTypedStringValue = function() {
        221 return this.privateDoNotAccessOrElseSafeStyleWrappedValue_;
        222};
        223
        224
        225if (goog.DEBUG) {
        226 /**
        227 * Returns a debug string-representation of this value.
        228 *
        229 * To obtain the actual string value wrapped in a SafeStyle, use
        230 * {@code goog.html.SafeStyle.unwrap}.
        231 *
        232 * @see goog.html.SafeStyle#unwrap
        233 * @override
        234 */
        235 goog.html.SafeStyle.prototype.toString = function() {
        236 return 'SafeStyle{' +
        237 this.privateDoNotAccessOrElseSafeStyleWrappedValue_ + '}';
        238 };
        239}
        240
        241
        242/**
        243 * Performs a runtime check that the provided object is indeed a
        244 * SafeStyle object, and returns its value.
        245 *
        246 * @param {!goog.html.SafeStyle} safeStyle The object to extract from.
        247 * @return {string} The safeStyle object's contained string, unless
        248 * the run-time type check fails. In that case, {@code unwrap} returns an
        249 * innocuous string, or, if assertions are enabled, throws
        250 * {@code goog.asserts.AssertionError}.
        251 */
        252goog.html.SafeStyle.unwrap = function(safeStyle) {
        253 // Perform additional Run-time type-checking to ensure that
        254 // safeStyle is indeed an instance of the expected type. This
        255 // provides some additional protection against security bugs due to
        256 // application code that disables type checks.
        257 // Specifically, the following checks are performed:
        258 // 1. The object is an instance of the expected type.
        259 // 2. The object is not an instance of a subclass.
        260 // 3. The object carries a type marker for the expected type. "Faking" an
        261 // object requires a reference to the type marker, which has names intended
        262 // to stand out in code reviews.
        263 if (safeStyle instanceof goog.html.SafeStyle &&
        264 safeStyle.constructor === goog.html.SafeStyle &&
        265 safeStyle.SAFE_STYLE_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        266 goog.html.SafeStyle.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        267 return safeStyle.privateDoNotAccessOrElseSafeStyleWrappedValue_;
        268 } else {
        269 goog.asserts.fail(
        270 'expected object of type SafeStyle, got \'' + safeStyle + '\'');
        271 return 'type_error:SafeStyle';
        272 }
        273};
        274
        275
        276/**
        277 * Package-internal utility method to create SafeStyle instances.
        278 *
        279 * @param {string} style The string to initialize the SafeStyle object with.
        280 * @return {!goog.html.SafeStyle} The initialized SafeStyle object.
        281 * @package
        282 */
        283goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse =
        284 function(style) {
        285 return new goog.html.SafeStyle().initSecurityPrivateDoNotAccessOrElse_(style);
        286};
        287
        288
        289/**
        290 * Called from createSafeStyleSecurityPrivateDoNotAccessOrElse(). This
        291 * method exists only so that the compiler can dead code eliminate static
        292 * fields (like EMPTY) when they're not accessed.
        293 * @param {string} style
        294 * @return {!goog.html.SafeStyle}
        295 * @private
        296 */
        297goog.html.SafeStyle.prototype.initSecurityPrivateDoNotAccessOrElse_ = function(
        298 style) {
        299 this.privateDoNotAccessOrElseSafeStyleWrappedValue_ = style;
        300 return this;
        301};
        302
        303
        304/**
        305 * A SafeStyle instance corresponding to the empty string.
        306 * @const {!goog.html.SafeStyle}
        307 */
        308goog.html.SafeStyle.EMPTY =
        309 goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse('');
        310
        311
        312/**
        313 * The innocuous string generated by goog.html.SafeUrl.create when passed
        314 * an unsafe value.
        315 * @const {string}
        316 */
        317goog.html.SafeStyle.INNOCUOUS_STRING = 'zClosurez';
        318
        319
        320/**
        321 * Mapping of property names to their values.
        322 * @typedef {!Object<string, goog.string.Const|string>}
        323 */
        324goog.html.SafeStyle.PropertyMap;
        325
        326
        327/**
        328 * Creates a new SafeStyle object from the properties specified in the map.
        329 * @param {goog.html.SafeStyle.PropertyMap} map Mapping of property names to
        330 * their values, for example {'margin': '1px'}. Names must consist of
        331 * [-_a-zA-Z0-9]. Values might be strings consisting of
        332 * [-,.'"%_!# a-zA-Z0-9], where " and ' must be properly balanced.
        333 * Other values must be wrapped in goog.string.Const. Null value causes
        334 * skipping the property.
        335 * @return {!goog.html.SafeStyle}
        336 * @throws {Error} If invalid name is provided.
        337 * @throws {goog.asserts.AssertionError} If invalid value is provided. With
        338 * disabled assertions, invalid value is replaced by
        339 * goog.html.SafeStyle.INNOCUOUS_STRING.
        340 */
        341goog.html.SafeStyle.create = function(map) {
        342 var style = '';
        343 for (var name in map) {
        344 if (!/^[-_a-zA-Z0-9]+$/.test(name)) {
        345 throw Error('Name allows only [-_a-zA-Z0-9], got: ' + name);
        346 }
        347 var value = map[name];
        348 if (value == null) {
        349 continue;
        350 }
        351 if (value instanceof goog.string.Const) {
        352 value = goog.string.Const.unwrap(value);
        353 // These characters can be used to change context and we don't want that
        354 // even with const values.
        355 goog.asserts.assert(!/[{;}]/.test(value), 'Value does not allow [{;}].');
        356 } else if (!goog.html.SafeStyle.VALUE_RE_.test(value)) {
        357 goog.asserts.fail(
        358 'String value allows only [-,."\'%_!# a-zA-Z0-9], got: ' + value);
        359 value = goog.html.SafeStyle.INNOCUOUS_STRING;
        360 } else if (!goog.html.SafeStyle.hasBalancedQuotes_(value)) {
        361 goog.asserts.fail('String value requires balanced quotes, got: ' + value);
        362 value = goog.html.SafeStyle.INNOCUOUS_STRING;
        363 }
        364 style += name + ':' + value + ';';
        365 }
        366 if (!style) {
        367 return goog.html.SafeStyle.EMPTY;
        368 }
        369 goog.html.SafeStyle.checkStyle_(style);
        370 return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
        371 style);
        372};
        373
        374
        375/**
        376 * Checks that quotes (" and ') are properly balanced inside a string. Assumes
        377 * that neither escape (\) nor any other character that could result in
        378 * breaking out of a string parsing context are allowed;
        379 * see http://www.w3.org/TR/css3-syntax/#string-token-diagram.
        380 * @param {string} value Untrusted CSS property value.
        381 * @return {boolean} True if property value is safe with respect to quote
        382 * balancedness.
        383 * @private
        384 */
        385goog.html.SafeStyle.hasBalancedQuotes_ = function(value) {
        386 var outsideSingle = true;
        387 var outsideDouble = true;
        388 for (var i = 0; i < value.length; i++) {
        389 var c = value.charAt(i);
        390 if (c == "'" && outsideDouble) {
        391 outsideSingle = !outsideSingle;
        392 } else if (c == '"' && outsideSingle) {
        393 outsideDouble = !outsideDouble;
        394 }
        395 }
        396 return outsideSingle && outsideDouble;
        397};
        398
        399
        400// Keep in sync with the error string in create().
        401/**
        402 * Regular expression for safe values.
        403 *
        404 * Quotes (" and ') are allowed, but a check must be done elsewhere to ensure
        405 * they're balanced.
        406 *
        407 * ',' allows multiple values to be assigned to the same property
        408 * (e.g. background-attachment or font-family) and hence could allow
        409 * multiple values to get injected, but that should pose no risk of XSS.
        410 * @const {!RegExp}
        411 * @private
        412 */
        413goog.html.SafeStyle.VALUE_RE_ = /^[-,."'%_!# a-zA-Z0-9]+$/;
        414
        415
        416/**
        417 * Creates a new SafeStyle object by concatenating the values.
        418 * @param {...(!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>)} var_args
        419 * SafeStyles to concatenate.
        420 * @return {!goog.html.SafeStyle}
        421 */
        422goog.html.SafeStyle.concat = function(var_args) {
        423 var style = '';
        424
        425 /**
        426 * @param {!goog.html.SafeStyle|!Array<!goog.html.SafeStyle>} argument
        427 */
        428 var addArgument = function(argument) {
        429 if (goog.isArray(argument)) {
        430 goog.array.forEach(argument, addArgument);
        431 } else {
        432 style += goog.html.SafeStyle.unwrap(argument);
        433 }
        434 };
        435
        436 goog.array.forEach(arguments, addArgument);
        437 if (!style) {
        438 return goog.html.SafeStyle.EMPTY;
        439 }
        440 return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
        441 style);
        442};
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/safestylesheet.js.src.html b/docs/source/lib/goog/html/safestylesheet.js.src.html new file mode 100644 index 0000000..cd99ed1 --- /dev/null +++ b/docs/source/lib/goog/html/safestylesheet.js.src.html @@ -0,0 +1 @@ +safestylesheet.js

        lib/goog/html/safestylesheet.js

        1// Copyright 2014 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The SafeStyleSheet type and its builders.
        17 *
        18 * TODO(user): Link to document stating type contract.
        19 */
        20
        21goog.provide('goog.html.SafeStyleSheet');
        22
        23goog.require('goog.array');
        24goog.require('goog.asserts');
        25goog.require('goog.string');
        26goog.require('goog.string.Const');
        27goog.require('goog.string.TypedString');
        28
        29
        30
        31/**
        32 * A string-like object which represents a CSS style sheet and that carries the
        33 * security type contract that its value, as a string, will not cause untrusted
        34 * script execution (XSS) when evaluated as CSS in a browser.
        35 *
        36 * Instances of this type must be created via the factory method
        37 * {@code goog.html.SafeStyleSheet.fromConstant} and not by invoking its
        38 * constructor. The constructor intentionally takes no parameters and the type
        39 * is immutable; hence only a default instance corresponding to the empty string
        40 * can be obtained via constructor invocation.
        41 *
        42 * A SafeStyleSheet's string representation can safely be interpolated as the
        43 * content of a style element within HTML. The SafeStyleSheet string should
        44 * not be escaped before interpolation.
        45 *
        46 * Values of this type must be composable, i.e. for any two values
        47 * {@code styleSheet1} and {@code styleSheet2} of this type,
        48 * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1) +
        49 * goog.html.SafeStyleSheet.unwrap(styleSheet2)} must itself be a value that
        50 * satisfies the SafeStyleSheet type constraint. This requirement implies that
        51 * for any value {@code styleSheet} of this type,
        52 * {@code goog.html.SafeStyleSheet.unwrap(styleSheet1)} must end in
        53 * "beginning of rule" context.
        54
        55 * A SafeStyleSheet can be constructed via security-reviewed unchecked
        56 * conversions. In this case producers of SafeStyleSheet must ensure themselves
        57 * that the SafeStyleSheet does not contain unsafe script. Note in particular
        58 * that {@code &lt;} is dangerous, even when inside CSS strings, and so should
        59 * always be forbidden or CSS-escaped in user controlled input. For example, if
        60 * {@code &lt;/style&gt;&lt;script&gt;evil&lt;/script&gt;"} were interpolated
        61 * inside a CSS string, it would break out of the context of the original
        62 * style element and {@code evil} would execute. Also note that within an HTML
        63 * style (raw text) element, HTML character references, such as
        64 * {@code &amp;lt;}, are not allowed. See
        65 * http://www.w3.org/TR/html5/scripting-1.html#restrictions-for-contents-of-script-elements
        66 * (similar considerations apply to the style element).
        67 *
        68 * @see goog.html.SafeStyleSheet#fromConstant
        69 * @constructor
        70 * @final
        71 * @struct
        72 * @implements {goog.string.TypedString}
        73 */
        74goog.html.SafeStyleSheet = function() {
        75 /**
        76 * The contained value of this SafeStyleSheet. The field has a purposely
        77 * ugly name to make (non-compiled) code that attempts to directly access this
        78 * field stand out.
        79 * @private {string}
        80 */
        81 this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = '';
        82
        83 /**
        84 * A type marker used to implement additional run-time type checking.
        85 * @see goog.html.SafeStyleSheet#unwrap
        86 * @const
        87 * @private
        88 */
        89 this.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        90 goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        91};
        92
        93
        94/**
        95 * @override
        96 * @const
        97 */
        98goog.html.SafeStyleSheet.prototype.implementsGoogStringTypedString = true;
        99
        100
        101/**
        102 * Type marker for the SafeStyleSheet type, used to implement additional
        103 * run-time type checking.
        104 * @const
        105 * @private
        106 */
        107goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        108
        109
        110/**
        111 * Creates a new SafeStyleSheet object by concatenating values.
        112 * @param {...(!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>)}
        113 * var_args Values to concatenate.
        114 * @return {!goog.html.SafeStyleSheet}
        115 */
        116goog.html.SafeStyleSheet.concat = function(var_args) {
        117 var result = '';
        118
        119 /**
        120 * @param {!goog.html.SafeStyleSheet|!Array<!goog.html.SafeStyleSheet>}
        121 * argument
        122 */
        123 var addArgument = function(argument) {
        124 if (goog.isArray(argument)) {
        125 goog.array.forEach(argument, addArgument);
        126 } else {
        127 result += goog.html.SafeStyleSheet.unwrap(argument);
        128 }
        129 };
        130
        131 goog.array.forEach(arguments, addArgument);
        132 return goog.html.SafeStyleSheet
        133 .createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(result);
        134};
        135
        136
        137/**
        138 * Creates a SafeStyleSheet object from a compile-time constant string.
        139 *
        140 * {@code styleSheet} must not have any &lt; characters in it, so that
        141 * the syntactic structure of the surrounding HTML is not affected.
        142 *
        143 * @param {!goog.string.Const} styleSheet A compile-time-constant string from
        144 * which to create a SafeStyleSheet.
        145 * @return {!goog.html.SafeStyleSheet} A SafeStyleSheet object initialized to
        146 * {@code styleSheet}.
        147 */
        148goog.html.SafeStyleSheet.fromConstant = function(styleSheet) {
        149 var styleSheetString = goog.string.Const.unwrap(styleSheet);
        150 if (styleSheetString.length === 0) {
        151 return goog.html.SafeStyleSheet.EMPTY;
        152 }
        153 // > is a valid character in CSS selectors and there's no strict need to
        154 // block it if we already block <.
        155 goog.asserts.assert(!goog.string.contains(styleSheetString, '<'),
        156 "Forbidden '<' character in style sheet string: " + styleSheetString);
        157 return goog.html.SafeStyleSheet.
        158 createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheetString);
        159};
        160
        161
        162/**
        163 * Returns this SafeStyleSheet's value as a string.
        164 *
        165 * IMPORTANT: In code where it is security relevant that an object's type is
        166 * indeed {@code SafeStyleSheet}, use {@code goog.html.SafeStyleSheet.unwrap}
        167 * instead of this method. If in doubt, assume that it's security relevant. In
        168 * particular, note that goog.html functions which return a goog.html type do
        169 * not guarantee the returned instance is of the right type. For example:
        170 *
        171 * <pre>
        172 * var fakeSafeHtml = new String('fake');
        173 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        174 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        175 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        176 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml
        177 * // instanceof goog.html.SafeHtml.
        178 * </pre>
        179 *
        180 * @see goog.html.SafeStyleSheet#unwrap
        181 * @override
        182 */
        183goog.html.SafeStyleSheet.prototype.getTypedStringValue = function() {
        184 return this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
        185};
        186
        187
        188if (goog.DEBUG) {
        189 /**
        190 * Returns a debug string-representation of this value.
        191 *
        192 * To obtain the actual string value wrapped in a SafeStyleSheet, use
        193 * {@code goog.html.SafeStyleSheet.unwrap}.
        194 *
        195 * @see goog.html.SafeStyleSheet#unwrap
        196 * @override
        197 */
        198 goog.html.SafeStyleSheet.prototype.toString = function() {
        199 return 'SafeStyleSheet{' +
        200 this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ + '}';
        201 };
        202}
        203
        204
        205/**
        206 * Performs a runtime check that the provided object is indeed a
        207 * SafeStyleSheet object, and returns its value.
        208 *
        209 * @param {!goog.html.SafeStyleSheet} safeStyleSheet The object to extract from.
        210 * @return {string} The safeStyleSheet object's contained string, unless
        211 * the run-time type check fails. In that case, {@code unwrap} returns an
        212 * innocuous string, or, if assertions are enabled, throws
        213 * {@code goog.asserts.AssertionError}.
        214 */
        215goog.html.SafeStyleSheet.unwrap = function(safeStyleSheet) {
        216 // Perform additional Run-time type-checking to ensure that
        217 // safeStyleSheet is indeed an instance of the expected type. This
        218 // provides some additional protection against security bugs due to
        219 // application code that disables type checks.
        220 // Specifically, the following checks are performed:
        221 // 1. The object is an instance of the expected type.
        222 // 2. The object is not an instance of a subclass.
        223 // 3. The object carries a type marker for the expected type. "Faking" an
        224 // object requires a reference to the type marker, which has names intended
        225 // to stand out in code reviews.
        226 if (safeStyleSheet instanceof goog.html.SafeStyleSheet &&
        227 safeStyleSheet.constructor === goog.html.SafeStyleSheet &&
        228 safeStyleSheet.SAFE_SCRIPT_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        229 goog.html.SafeStyleSheet.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        230 return safeStyleSheet.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_;
        231 } else {
        232 goog.asserts.fail(
        233 "expected object of type SafeStyleSheet, got '" + safeStyleSheet +
        234 "'");
        235 return 'type_error:SafeStyleSheet';
        236 }
        237};
        238
        239
        240/**
        241 * Package-internal utility method to create SafeStyleSheet instances.
        242 *
        243 * @param {string} styleSheet The string to initialize the SafeStyleSheet
        244 * object with.
        245 * @return {!goog.html.SafeStyleSheet} The initialized SafeStyleSheet object.
        246 * @package
        247 */
        248goog.html.SafeStyleSheet.createSafeStyleSheetSecurityPrivateDoNotAccessOrElse =
        249 function(styleSheet) {
        250 return new goog.html.SafeStyleSheet().initSecurityPrivateDoNotAccessOrElse_(
        251 styleSheet);
        252};
        253
        254
        255/**
        256 * Called from createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(). This
        257 * method exists only so that the compiler can dead code eliminate static
        258 * fields (like EMPTY) when they're not accessed.
        259 * @param {string} styleSheet
        260 * @return {!goog.html.SafeStyleSheet}
        261 * @private
        262 */
        263goog.html.SafeStyleSheet.prototype.initSecurityPrivateDoNotAccessOrElse_ =
        264 function(styleSheet) {
        265 this.privateDoNotAccessOrElseSafeStyleSheetWrappedValue_ = styleSheet;
        266 return this;
        267};
        268
        269
        270/**
        271 * A SafeStyleSheet instance corresponding to the empty string.
        272 * @const {!goog.html.SafeStyleSheet}
        273 */
        274goog.html.SafeStyleSheet.EMPTY =
        275 goog.html.SafeStyleSheet.
        276 createSafeStyleSheetSecurityPrivateDoNotAccessOrElse('');
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/safeurl.js.src.html b/docs/source/lib/goog/html/safeurl.js.src.html new file mode 100644 index 0000000..732f40c --- /dev/null +++ b/docs/source/lib/goog/html/safeurl.js.src.html @@ -0,0 +1 @@ +safeurl.js

        lib/goog/html/safeurl.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The SafeUrl type and its builders.
        17 *
        18 * TODO(user): Link to document stating type contract.
        19 */
        20
        21goog.provide('goog.html.SafeUrl');
        22
        23goog.require('goog.asserts');
        24goog.require('goog.fs.url');
        25goog.require('goog.i18n.bidi.Dir');
        26goog.require('goog.i18n.bidi.DirectionalString');
        27goog.require('goog.string.Const');
        28goog.require('goog.string.TypedString');
        29
        30
        31
        32/**
        33 * A string that is safe to use in URL context in DOM APIs and HTML documents.
        34 *
        35 * A SafeUrl is a string-like object that carries the security type contract
        36 * that its value as a string will not cause untrusted script execution
        37 * when evaluated as a hyperlink URL in a browser.
        38 *
        39 * Values of this type are guaranteed to be safe to use in URL/hyperlink
        40 * contexts, such as, assignment to URL-valued DOM properties, or
        41 * interpolation into a HTML template in URL context (e.g., inside a href
        42 * attribute), in the sense that the use will not result in a
        43 * Cross-Site-Scripting vulnerability.
        44 *
        45 * Note that, as documented in {@code goog.html.SafeUrl.unwrap}, this type's
        46 * contract does not guarantee that instances are safe to interpolate into HTML
        47 * without appropriate escaping.
        48 *
        49 * Note also that this type's contract does not imply any guarantees regarding
        50 * the resource the URL refers to. In particular, SafeUrls are <b>not</b>
        51 * safe to use in a context where the referred-to resource is interpreted as
        52 * trusted code, e.g., as the src of a script tag.
        53 *
        54 * Instances of this type must be created via the factory methods
        55 * ({@code goog.html.SafeUrl.fromConstant}, {@code goog.html.SafeUrl.sanitize}),
        56 * etc and not by invoking its constructor. The constructor intentionally
        57 * takes no parameters and the type is immutable; hence only a default instance
        58 * corresponding to the empty string can be obtained via constructor invocation.
        59 *
        60 * @see goog.html.SafeUrl#fromConstant
        61 * @see goog.html.SafeUrl#from
        62 * @see goog.html.SafeUrl#sanitize
        63 * @constructor
        64 * @final
        65 * @struct
        66 * @implements {goog.i18n.bidi.DirectionalString}
        67 * @implements {goog.string.TypedString}
        68 */
        69goog.html.SafeUrl = function() {
        70 /**
        71 * The contained value of this SafeUrl. The field has a purposely ugly
        72 * name to make (non-compiled) code that attempts to directly access this
        73 * field stand out.
        74 * @private {string}
        75 */
        76 this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = '';
        77
        78 /**
        79 * A type marker used to implement additional run-time type checking.
        80 * @see goog.html.SafeUrl#unwrap
        81 * @const
        82 * @private
        83 */
        84 this.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        85 goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        86};
        87
        88
        89/**
        90 * The innocuous string generated by goog.html.SafeUrl.sanitize when passed
        91 * an unsafe URL.
        92 *
        93 * about:invalid is registered in
        94 * http://www.w3.org/TR/css3-values/#about-invalid.
        95 * http://tools.ietf.org/html/rfc6694#section-2.2.1 permits about URLs to
        96 * contain a fragment, which is not to be considered when determining if an
        97 * about URL is well-known.
        98 *
        99 * Using about:invalid seems preferable to using a fixed data URL, since
        100 * browsers might choose to not report CSP violations on it, as legitimate
        101 * CSS function calls to attr() can result in this URL being produced. It is
        102 * also a standard URL which matches exactly the semantics we need:
        103 * "The about:invalid URI references a non-existent document with a generic
        104 * error condition. It can be used when a URI is necessary, but the default
        105 * value shouldn't be resolveable as any type of document".
        106 *
        107 * @const {string}
        108 */
        109goog.html.SafeUrl.INNOCUOUS_STRING = 'about:invalid#zClosurez';
        110
        111
        112/**
        113 * @override
        114 * @const
        115 */
        116goog.html.SafeUrl.prototype.implementsGoogStringTypedString = true;
        117
        118
        119/**
        120 * Returns this SafeUrl's value a string.
        121 *
        122 * IMPORTANT: In code where it is security relevant that an object's type is
        123 * indeed {@code SafeUrl}, use {@code goog.html.SafeUrl.unwrap} instead of this
        124 * method. If in doubt, assume that it's security relevant. In particular, note
        125 * that goog.html functions which return a goog.html type do not guarantee that
        126 * the returned instance is of the right type. For example:
        127 *
        128 * <pre>
        129 * var fakeSafeHtml = new String('fake');
        130 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        131 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        132 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        133 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
        134 * // goog.html.SafeHtml.
        135 * </pre>
        136 *
        137 * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
        138 * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
        139 * be appropriately escaped before embedding in a HTML document. Note that the
        140 * required escaping is context-sensitive (e.g. a different escaping is
        141 * required for embedding a URL in a style property within a style
        142 * attribute, as opposed to embedding in a href attribute).
        143 *
        144 * @see goog.html.SafeUrl#unwrap
        145 * @override
        146 */
        147goog.html.SafeUrl.prototype.getTypedStringValue = function() {
        148 return this.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
        149};
        150
        151
        152/**
        153 * @override
        154 * @const
        155 */
        156goog.html.SafeUrl.prototype.implementsGoogI18nBidiDirectionalString = true;
        157
        158
        159/**
        160 * Returns this URLs directionality, which is always {@code LTR}.
        161 * @override
        162 */
        163goog.html.SafeUrl.prototype.getDirection = function() {
        164 return goog.i18n.bidi.Dir.LTR;
        165};
        166
        167
        168if (goog.DEBUG) {
        169 /**
        170 * Returns a debug string-representation of this value.
        171 *
        172 * To obtain the actual string value wrapped in a SafeUrl, use
        173 * {@code goog.html.SafeUrl.unwrap}.
        174 *
        175 * @see goog.html.SafeUrl#unwrap
        176 * @override
        177 */
        178 goog.html.SafeUrl.prototype.toString = function() {
        179 return 'SafeUrl{' + this.privateDoNotAccessOrElseSafeHtmlWrappedValue_ +
        180 '}';
        181 };
        182}
        183
        184
        185/**
        186 * Performs a runtime check that the provided object is indeed a SafeUrl
        187 * object, and returns its value.
        188 *
        189 * IMPORTANT: The guarantees of the SafeUrl type contract only extend to the
        190 * behavior of browsers when interpreting URLs. Values of SafeUrl objects MUST
        191 * be appropriately escaped before embedding in a HTML document. Note that the
        192 * required escaping is context-sensitive (e.g. a different escaping is
        193 * required for embedding a URL in a style property within a style
        194 * attribute, as opposed to embedding in a href attribute).
        195 *
        196 * Note that the returned value does not necessarily correspond to the string
        197 * with which the SafeUrl was constructed, since goog.html.SafeUrl.sanitize
        198 * will percent-encode many characters.
        199 *
        200 * @param {!goog.html.SafeUrl} safeUrl The object to extract from.
        201 * @return {string} The SafeUrl object's contained string, unless the run-time
        202 * type check fails. In that case, {@code unwrap} returns an innocuous
        203 * string, or, if assertions are enabled, throws
        204 * {@code goog.asserts.AssertionError}.
        205 */
        206goog.html.SafeUrl.unwrap = function(safeUrl) {
        207 // Perform additional Run-time type-checking to ensure that safeUrl is indeed
        208 // an instance of the expected type. This provides some additional protection
        209 // against security bugs due to application code that disables type checks.
        210 // Specifically, the following checks are performed:
        211 // 1. The object is an instance of the expected type.
        212 // 2. The object is not an instance of a subclass.
        213 // 3. The object carries a type marker for the expected type. "Faking" an
        214 // object requires a reference to the type marker, which has names intended
        215 // to stand out in code reviews.
        216 if (safeUrl instanceof goog.html.SafeUrl &&
        217 safeUrl.constructor === goog.html.SafeUrl &&
        218 safeUrl.SAFE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        219 goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        220 return safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_;
        221 } else {
        222 goog.asserts.fail('expected object of type SafeUrl, got \'' +
        223 safeUrl + '\'');
        224 return 'type_error:SafeUrl';
        225
        226 }
        227};
        228
        229
        230/**
        231 * Creates a SafeUrl object from a compile-time constant string.
        232 *
        233 * Compile-time constant strings are inherently program-controlled and hence
        234 * trusted.
        235 *
        236 * @param {!goog.string.Const} url A compile-time-constant string from which to
        237 * create a SafeUrl.
        238 * @return {!goog.html.SafeUrl} A SafeUrl object initialized to {@code url}.
        239 */
        240goog.html.SafeUrl.fromConstant = function(url) {
        241 return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(
        242 goog.string.Const.unwrap(url));
        243};
        244
        245
        246/**
        247 * A pattern that matches Blob types that can have SafeUrls created from
        248 * URL.createObjectURL(blob). Only matches image types, currently.
        249 * @const
        250 * @private
        251 */
        252goog.html.SAFE_BLOB_TYPE_PATTERN_ =
        253 /^image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)$/i;
        254
        255
        256/**
        257 * Creates a SafeUrl wrapping a blob URL for the given {@code blob}. The
        258 * blob URL is created with {@code URL.createObjectURL}. If the MIME type
        259 * for {@code blob} is not of a known safe image MIME type, then the
        260 * SafeUrl will wrap {@link #INNOCUOUS_STRING}.
        261 * @see http://www.w3.org/TR/FileAPI/#url
        262 * @param {!Blob} blob
        263 * @return {!goog.html.SafeUrl} The blob URL, or an innocuous string wrapped
        264 * as a SafeUrl.
        265 */
        266goog.html.SafeUrl.fromBlob = function(blob) {
        267 var url = goog.html.SAFE_BLOB_TYPE_PATTERN_.test(blob.type) ?
        268 goog.fs.url.createObjectUrl(blob) : goog.html.SafeUrl.INNOCUOUS_STRING;
        269 return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
        270};
        271
        272
        273/**
        274 * A pattern that recognizes a commonly useful subset of URLs that satisfy
        275 * the SafeUrl contract.
        276 *
        277 * This regular expression matches a subset of URLs that will not cause script
        278 * execution if used in URL context within a HTML document. Specifically, this
        279 * regular expression matches if (comment from here on and regex copied from
        280 * Soy's EscapingConventions):
        281 * (1) Either a protocol in a whitelist (http, https, mailto or ftp).
        282 * (2) or no protocol. A protocol must be followed by a colon. The below
        283 * allows that by allowing colons only after one of the characters [/?#].
        284 * A colon after a hash (#) must be in the fragment.
        285 * Otherwise, a colon after a (?) must be in a query.
        286 * Otherwise, a colon after a single solidus (/) must be in a path.
        287 * Otherwise, a colon after a double solidus (//) must be in the authority
        288 * (before port).
        289 *
        290 * The pattern disallows &, used in HTML entity declarations before
        291 * one of the characters in [/?#]. This disallows HTML entities used in the
        292 * protocol name, which should never happen, e.g. "h&#116;tp" for "http".
        293 * It also disallows HTML entities in the first path part of a relative path,
        294 * e.g. "foo&lt;bar/baz". Our existing escaping functions should not produce
        295 * that. More importantly, it disallows masking of a colon,
        296 * e.g. "javascript&#58;...".
        297 *
        298 * @private
        299 * @const {!RegExp}
        300 */
        301goog.html.SAFE_URL_PATTERN_ =
        302 /^(?:(?:https?|mailto|ftp):|[^&:/?#]*(?:[/?#]|$))/i;
        303
        304
        305/**
        306 * Creates a SafeUrl object from {@code url}. If {@code url} is a
        307 * goog.html.SafeUrl then it is simply returned. Otherwise the input string is
        308 * validated to match a pattern of commonly used safe URLs. The string is
        309 * converted to UTF-8 and non-whitelisted characters are percent-encoded. The
        310 * string wrapped by the created SafeUrl will thus contain only ASCII printable
        311 * characters.
        312 *
        313 * {@code url} may be a URL with the http, https, mailto or ftp scheme,
        314 * or a relative URL (i.e., a URL without a scheme; specifically, a
        315 * scheme-relative, absolute-path-relative, or path-relative URL).
        316 *
        317 * {@code url} is converted to UTF-8 and non-whitelisted characters are
        318 * percent-encoded. Whitelisted characters are '%' and, from RFC 3986,
        319 * unreserved characters and reserved characters, with the exception of '\'',
        320 * '(' and ')'. This ensures the the SafeUrl contains only ASCII-printable
        321 * characters and reduces the chance of security bugs were it to be
        322 * interpolated into a specific context without the necessary escaping.
        323 *
        324 * If {@code url} fails validation or does not UTF-16 decode correctly
        325 * (JavaScript strings are UTF-16 encoded), this function returns a SafeUrl
        326 * object containing an innocuous string, goog.html.SafeUrl.INNOCUOUS_STRING.
        327 *
        328 * @see http://url.spec.whatwg.org/#concept-relative-url
        329 * @param {string|!goog.string.TypedString} url The URL to validate.
        330 * @return {!goog.html.SafeUrl} The validated URL, wrapped as a SafeUrl.
        331 */
        332goog.html.SafeUrl.sanitize = function(url) {
        333 if (url instanceof goog.html.SafeUrl) {
        334 return url;
        335 }
        336 else if (url.implementsGoogStringTypedString) {
        337 url = url.getTypedStringValue();
        338 } else {
        339 url = String(url);
        340 }
        341 if (!goog.html.SAFE_URL_PATTERN_.test(url)) {
        342 url = goog.html.SafeUrl.INNOCUOUS_STRING;
        343 } else {
        344 url = goog.html.SafeUrl.normalize_(url);
        345 }
        346 return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
        347};
        348
        349
        350/**
        351 * Normalizes {@code url} the UTF-8 encoding of url, using a whitelist of
        352 * characters. Whitelisted characters are not percent-encoded.
        353 * @param {string} url The URL to normalize.
        354 * @return {string} The normalized URL.
        355 * @private
        356 */
        357goog.html.SafeUrl.normalize_ = function(url) {
        358 try {
        359 var normalized = encodeURI(url);
        360 } catch (e) { // Happens if url contains invalid surrogate sequences.
        361 return goog.html.SafeUrl.INNOCUOUS_STRING;
        362 }
        363
        364 return normalized.replace(
        365 goog.html.SafeUrl.NORMALIZE_MATCHER_,
        366 function(match) {
        367 return goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_[match];
        368 });
        369};
        370
        371
        372/**
        373 * Matches characters and strings which need to be replaced in the string
        374 * generated by encodeURI. Specifically:
        375 *
        376 * - '\'', '(' and ')' are not encoded. They are part of the reserved
        377 * characters group in RFC 3986 but only appear in the obsolete mark
        378 * production in Appendix D.2 of RFC 3986, so they can be encoded without
        379 * changing semantics.
        380 * - '[' and ']' are encoded by encodeURI, despite being reserved characters
        381 * which can be used to represent IPv6 addresses. So they need to be decoded.
        382 * - '%' is encoded by encodeURI. However, encoding '%' characters that are
        383 * already part of a valid percent-encoded sequence changes the semantics of a
        384 * URL, and hence we need to preserve them. Note that this may allow
        385 * non-encoded '%' characters to remain in the URL (i.e., occurrences of '%'
        386 * that are not part of a valid percent-encoded sequence, for example,
        387 * 'ab%xy').
        388 *
        389 * @const {!RegExp}
        390 * @private
        391 */
        392goog.html.SafeUrl.NORMALIZE_MATCHER_ = /[()']|%5B|%5D|%25/g;
        393
        394
        395/**
        396 * Map of replacements to be done in string generated by encodeURI.
        397 * @const {!Object<string, string>}
        398 * @private
        399 */
        400goog.html.SafeUrl.NORMALIZE_REPLACER_MAP_ = {
        401 '\'': '%27',
        402 '(': '%28',
        403 ')': '%29',
        404 '%5B': '[',
        405 '%5D': ']',
        406 '%25': '%'
        407};
        408
        409
        410/**
        411 * Type marker for the SafeUrl type, used to implement additional run-time
        412 * type checking.
        413 * @const
        414 * @private
        415 */
        416goog.html.SafeUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        417
        418
        419/**
        420 * Package-internal utility method to create SafeUrl instances.
        421 *
        422 * @param {string} url The string to initialize the SafeUrl object with.
        423 * @return {!goog.html.SafeUrl} The initialized SafeUrl object.
        424 * @package
        425 */
        426goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse = function(
        427 url) {
        428 var safeUrl = new goog.html.SafeUrl();
        429 safeUrl.privateDoNotAccessOrElseSafeHtmlWrappedValue_ = url;
        430 return safeUrl;
        431};
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/trustedresourceurl.js.src.html b/docs/source/lib/goog/html/trustedresourceurl.js.src.html new file mode 100644 index 0000000..2ee9254 --- /dev/null +++ b/docs/source/lib/goog/html/trustedresourceurl.js.src.html @@ -0,0 +1 @@ +trustedresourceurl.js

        lib/goog/html/trustedresourceurl.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The TrustedResourceUrl type and its builders.
        17 *
        18 * TODO(user): Link to document stating type contract.
        19 */
        20
        21goog.provide('goog.html.TrustedResourceUrl');
        22
        23goog.require('goog.asserts');
        24goog.require('goog.i18n.bidi.Dir');
        25goog.require('goog.i18n.bidi.DirectionalString');
        26goog.require('goog.string.Const');
        27goog.require('goog.string.TypedString');
        28
        29
        30
        31/**
        32 * A URL which is under application control and from which script, CSS, and
        33 * other resources that represent executable code, can be fetched.
        34 *
        35 * Given that the URL can only be constructed from strings under application
        36 * control and is used to load resources, bugs resulting in a malformed URL
        37 * should not have a security impact and are likely to be easily detectable
        38 * during testing. Given the wide number of non-RFC compliant URLs in use,
        39 * stricter validation could prevent some applications from being able to use
        40 * this type.
        41 *
        42 * Instances of this type must be created via the factory method,
        43 * ({@code goog.html.TrustedResourceUrl.fromConstant}), and not by invoking its
        44 * constructor. The constructor intentionally takes no parameters and the type
        45 * is immutable; hence only a default instance corresponding to the empty
        46 * string can be obtained via constructor invocation.
        47 *
        48 * @see goog.html.TrustedResourceUrl#fromConstant
        49 * @constructor
        50 * @final
        51 * @struct
        52 * @implements {goog.i18n.bidi.DirectionalString}
        53 * @implements {goog.string.TypedString}
        54 */
        55goog.html.TrustedResourceUrl = function() {
        56 /**
        57 * The contained value of this TrustedResourceUrl. The field has a purposely
        58 * ugly name to make (non-compiled) code that attempts to directly access this
        59 * field stand out.
        60 * @private {string}
        61 */
        62 this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ = '';
        63
        64 /**
        65 * A type marker used to implement additional run-time type checking.
        66 * @see goog.html.TrustedResourceUrl#unwrap
        67 * @const
        68 * @private
        69 */
        70 this.TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ =
        71 goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_;
        72};
        73
        74
        75/**
        76 * @override
        77 * @const
        78 */
        79goog.html.TrustedResourceUrl.prototype.implementsGoogStringTypedString = true;
        80
        81
        82/**
        83 * Returns this TrustedResourceUrl's value as a string.
        84 *
        85 * IMPORTANT: In code where it is security relevant that an object's type is
        86 * indeed {@code TrustedResourceUrl}, use
        87 * {@code goog.html.TrustedResourceUrl.unwrap} instead of this method. If in
        88 * doubt, assume that it's security relevant. In particular, note that
        89 * goog.html functions which return a goog.html type do not guarantee that
        90 * the returned instance is of the right type. For example:
        91 *
        92 * <pre>
        93 * var fakeSafeHtml = new String('fake');
        94 * fakeSafeHtml.__proto__ = goog.html.SafeHtml.prototype;
        95 * var newSafeHtml = goog.html.SafeHtml.htmlEscape(fakeSafeHtml);
        96 * // newSafeHtml is just an alias for fakeSafeHtml, it's passed through by
        97 * // goog.html.SafeHtml.htmlEscape() as fakeSafeHtml instanceof
        98 * // goog.html.SafeHtml.
        99 * </pre>
        100 *
        101 * @see goog.html.TrustedResourceUrl#unwrap
        102 * @override
        103 */
        104goog.html.TrustedResourceUrl.prototype.getTypedStringValue = function() {
        105 return this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
        106};
        107
        108
        109/**
        110 * @override
        111 * @const
        112 */
        113goog.html.TrustedResourceUrl.prototype.implementsGoogI18nBidiDirectionalString =
        114 true;
        115
        116
        117/**
        118 * Returns this URLs directionality, which is always {@code LTR}.
        119 * @override
        120 */
        121goog.html.TrustedResourceUrl.prototype.getDirection = function() {
        122 return goog.i18n.bidi.Dir.LTR;
        123};
        124
        125
        126if (goog.DEBUG) {
        127 /**
        128 * Returns a debug string-representation of this value.
        129 *
        130 * To obtain the actual string value wrapped in a TrustedResourceUrl, use
        131 * {@code goog.html.TrustedResourceUrl.unwrap}.
        132 *
        133 * @see goog.html.TrustedResourceUrl#unwrap
        134 * @override
        135 */
        136 goog.html.TrustedResourceUrl.prototype.toString = function() {
        137 return 'TrustedResourceUrl{' +
        138 this.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ + '}';
        139 };
        140}
        141
        142
        143/**
        144 * Performs a runtime check that the provided object is indeed a
        145 * TrustedResourceUrl object, and returns its value.
        146 *
        147 * @param {!goog.html.TrustedResourceUrl} trustedResourceUrl The object to
        148 * extract from.
        149 * @return {string} The trustedResourceUrl object's contained string, unless
        150 * the run-time type check fails. In that case, {@code unwrap} returns an
        151 * innocuous string, or, if assertions are enabled, throws
        152 * {@code goog.asserts.AssertionError}.
        153 */
        154goog.html.TrustedResourceUrl.unwrap = function(trustedResourceUrl) {
        155 // Perform additional Run-time type-checking to ensure that
        156 // trustedResourceUrl is indeed an instance of the expected type. This
        157 // provides some additional protection against security bugs due to
        158 // application code that disables type checks.
        159 // Specifically, the following checks are performed:
        160 // 1. The object is an instance of the expected type.
        161 // 2. The object is not an instance of a subclass.
        162 // 3. The object carries a type marker for the expected type. "Faking" an
        163 // object requires a reference to the type marker, which has names intended
        164 // to stand out in code reviews.
        165 if (trustedResourceUrl instanceof goog.html.TrustedResourceUrl &&
        166 trustedResourceUrl.constructor === goog.html.TrustedResourceUrl &&
        167 trustedResourceUrl
        168 .TRUSTED_RESOURCE_URL_TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ ===
        169 goog.html.TrustedResourceUrl
        170 .TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_) {
        171 return trustedResourceUrl
        172 .privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_;
        173 } else {
        174 goog.asserts.fail('expected object of type TrustedResourceUrl, got \'' +
        175 trustedResourceUrl + '\'');
        176 return 'type_error:TrustedResourceUrl';
        177
        178 }
        179};
        180
        181
        182/**
        183 * Creates a TrustedResourceUrl object from a compile-time constant string.
        184 *
        185 * Compile-time constant strings are inherently program-controlled and hence
        186 * trusted.
        187 *
        188 * @param {!goog.string.Const} url A compile-time-constant string from which to
        189 * create a TrustedResourceUrl.
        190 * @return {!goog.html.TrustedResourceUrl} A TrustedResourceUrl object
        191 * initialized to {@code url}.
        192 */
        193goog.html.TrustedResourceUrl.fromConstant = function(url) {
        194 return goog.html.TrustedResourceUrl
        195 .createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(
        196 goog.string.Const.unwrap(url));
        197};
        198
        199
        200/**
        201 * Type marker for the TrustedResourceUrl type, used to implement additional
        202 * run-time type checking.
        203 * @const
        204 * @private
        205 */
        206goog.html.TrustedResourceUrl.TYPE_MARKER_GOOG_HTML_SECURITY_PRIVATE_ = {};
        207
        208
        209/**
        210 * Package-internal utility method to create TrustedResourceUrl instances.
        211 *
        212 * @param {string} url The string to initialize the TrustedResourceUrl object
        213 * with.
        214 * @return {!goog.html.TrustedResourceUrl} The initialized TrustedResourceUrl
        215 * object.
        216 * @package
        217 */
        218goog.html.TrustedResourceUrl.
        219 createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse = function(url) {
        220 var trustedResourceUrl = new goog.html.TrustedResourceUrl();
        221 trustedResourceUrl.privateDoNotAccessOrElseTrustedResourceUrlWrappedValue_ =
        222 url;
        223 return trustedResourceUrl;
        224};
        \ No newline at end of file diff --git a/docs/source/lib/goog/html/uncheckedconversions.js.src.html b/docs/source/lib/goog/html/uncheckedconversions.js.src.html new file mode 100644 index 0000000..e2b9ed7 --- /dev/null +++ b/docs/source/lib/goog/html/uncheckedconversions.js.src.html @@ -0,0 +1 @@ +uncheckedconversions.js

        lib/goog/html/uncheckedconversions.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Unchecked conversions to create values of goog.html types from
        17 * plain strings. Use of these functions could potentially result in instances
        18 * of goog.html types that violate their type contracts, and hence result in
        19 * security vulnerabilties.
        20 *
        21 * Therefore, all uses of the methods herein must be carefully security
        22 * reviewed. Avoid use of the methods in this file whenever possible; instead
        23 * prefer to create instances of goog.html types using inherently safe builders
        24 * or template systems.
        25 *
        26 *
        27 * @visibility {//closure/goog/html:approved_for_unchecked_conversion}
        28 * @visibility {//closure/goog/bin/sizetests:__pkg__}
        29 */
        30
        31
        32goog.provide('goog.html.uncheckedconversions');
        33
        34goog.require('goog.asserts');
        35goog.require('goog.html.SafeHtml');
        36goog.require('goog.html.SafeScript');
        37goog.require('goog.html.SafeStyle');
        38goog.require('goog.html.SafeStyleSheet');
        39goog.require('goog.html.SafeUrl');
        40goog.require('goog.html.TrustedResourceUrl');
        41goog.require('goog.string');
        42goog.require('goog.string.Const');
        43
        44
        45/**
        46 * Performs an "unchecked conversion" to SafeHtml from a plain string that is
        47 * known to satisfy the SafeHtml type contract.
        48 *
        49 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        50 * that the value of {@code html} satisfies the SafeHtml type contract in all
        51 * possible program states.
        52 *
        53 *
        54 * @param {!goog.string.Const} justification A constant string explaining why
        55 * this use of this method is safe. May include a security review ticket
        56 * number.
        57 * @param {string} html A string that is claimed to adhere to the SafeHtml
        58 * contract.
        59 * @param {?goog.i18n.bidi.Dir=} opt_dir The optional directionality of the
        60 * SafeHtml to be constructed. A null or undefined value signifies an
        61 * unknown directionality.
        62 * @return {!goog.html.SafeHtml} The value of html, wrapped in a SafeHtml
        63 * object.
        64 * @suppress {visibility} For access to SafeHtml.create... Note that this
        65 * use is appropriate since this method is intended to be "package private"
        66 * withing goog.html. DO NOT call SafeHtml.create... from outside this
        67 * package; use appropriate wrappers instead.
        68 */
        69goog.html.uncheckedconversions.safeHtmlFromStringKnownToSatisfyTypeContract =
        70 function(justification, html, opt_dir) {
        71 // unwrap() called inside an assert so that justification can be optimized
        72 // away in production code.
        73 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        74 'must provide justification');
        75 goog.asserts.assert(
        76 !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
        77 'must provide non-empty justification');
        78 return goog.html.SafeHtml.createSafeHtmlSecurityPrivateDoNotAccessOrElse(
        79 html, opt_dir || null);
        80};
        81
        82
        83/**
        84 * Performs an "unchecked conversion" to SafeScript from a plain string that is
        85 * known to satisfy the SafeScript type contract.
        86 *
        87 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        88 * that the value of {@code script} satisfies the SafeScript type contract in
        89 * all possible program states.
        90 *
        91 *
        92 * @param {!goog.string.Const} justification A constant string explaining why
        93 * this use of this method is safe. May include a security review ticket
        94 * number.
        95 * @param {string} script The string to wrap as a SafeScript.
        96 * @return {!goog.html.SafeScript} The value of {@code script}, wrapped in a
        97 * SafeScript object.
        98 */
        99goog.html.uncheckedconversions.safeScriptFromStringKnownToSatisfyTypeContract =
        100 function(justification, script) {
        101 // unwrap() called inside an assert so that justification can be optimized
        102 // away in production code.
        103 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        104 'must provide justification');
        105 goog.asserts.assert(
        106 !goog.string.isEmpty(goog.string.Const.unwrap(justification)),
        107 'must provide non-empty justification');
        108 return goog.html.SafeScript.createSafeScriptSecurityPrivateDoNotAccessOrElse(
        109 script);
        110};
        111
        112
        113/**
        114 * Performs an "unchecked conversion" to SafeStyle from a plain string that is
        115 * known to satisfy the SafeStyle type contract.
        116 *
        117 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        118 * that the value of {@code style} satisfies the SafeUrl type contract in all
        119 * possible program states.
        120 *
        121 *
        122 * @param {!goog.string.Const} justification A constant string explaining why
        123 * this use of this method is safe. May include a security review ticket
        124 * number.
        125 * @param {string} style The string to wrap as a SafeStyle.
        126 * @return {!goog.html.SafeStyle} The value of {@code style}, wrapped in a
        127 * SafeStyle object.
        128 */
        129goog.html.uncheckedconversions.safeStyleFromStringKnownToSatisfyTypeContract =
        130 function(justification, style) {
        131 // unwrap() called inside an assert so that justification can be optimized
        132 // away in production code.
        133 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        134 'must provide justification');
        135 goog.asserts.assert(
        136 !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
        137 'must provide non-empty justification');
        138 return goog.html.SafeStyle.createSafeStyleSecurityPrivateDoNotAccessOrElse(
        139 style);
        140};
        141
        142
        143/**
        144 * Performs an "unchecked conversion" to SafeStyleSheet from a plain string
        145 * that is known to satisfy the SafeStyleSheet type contract.
        146 *
        147 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        148 * that the value of {@code styleSheet} satisfies the SafeUrl type contract in
        149 * all possible program states.
        150 *
        151 *
        152 * @param {!goog.string.Const} justification A constant string explaining why
        153 * this use of this method is safe. May include a security review ticket
        154 * number.
        155 * @param {string} styleSheet The string to wrap as a SafeStyleSheet.
        156 * @return {!goog.html.SafeStyleSheet} The value of {@code styleSheet}, wrapped
        157 * in a SafeStyleSheet object.
        158 */
        159goog.html.uncheckedconversions.
        160 safeStyleSheetFromStringKnownToSatisfyTypeContract =
        161 function(justification, styleSheet) {
        162 // unwrap() called inside an assert so that justification can be optimized
        163 // away in production code.
        164 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        165 'must provide justification');
        166 goog.asserts.assert(
        167 !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
        168 'must provide non-empty justification');
        169 return goog.html.SafeStyleSheet.
        170 createSafeStyleSheetSecurityPrivateDoNotAccessOrElse(styleSheet);
        171};
        172
        173
        174/**
        175 * Performs an "unchecked conversion" to SafeUrl from a plain string that is
        176 * known to satisfy the SafeUrl type contract.
        177 *
        178 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        179 * that the value of {@code url} satisfies the SafeUrl type contract in all
        180 * possible program states.
        181 *
        182 *
        183 * @param {!goog.string.Const} justification A constant string explaining why
        184 * this use of this method is safe. May include a security review ticket
        185 * number.
        186 * @param {string} url The string to wrap as a SafeUrl.
        187 * @return {!goog.html.SafeUrl} The value of {@code url}, wrapped in a SafeUrl
        188 * object.
        189 */
        190goog.html.uncheckedconversions.safeUrlFromStringKnownToSatisfyTypeContract =
        191 function(justification, url) {
        192 // unwrap() called inside an assert so that justification can be optimized
        193 // away in production code.
        194 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        195 'must provide justification');
        196 goog.asserts.assert(
        197 !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
        198 'must provide non-empty justification');
        199 return goog.html.SafeUrl.createSafeUrlSecurityPrivateDoNotAccessOrElse(url);
        200};
        201
        202
        203/**
        204 * Performs an "unchecked conversion" to TrustedResourceUrl from a plain string
        205 * that is known to satisfy the TrustedResourceUrl type contract.
        206 *
        207 * IMPORTANT: Uses of this method must be carefully security-reviewed to ensure
        208 * that the value of {@code url} satisfies the TrustedResourceUrl type contract
        209 * in all possible program states.
        210 *
        211 *
        212 * @param {!goog.string.Const} justification A constant string explaining why
        213 * this use of this method is safe. May include a security review ticket
        214 * number.
        215 * @param {string} url The string to wrap as a TrustedResourceUrl.
        216 * @return {!goog.html.TrustedResourceUrl} The value of {@code url}, wrapped in
        217 * a TrustedResourceUrl object.
        218 */
        219goog.html.uncheckedconversions.
        220 trustedResourceUrlFromStringKnownToSatisfyTypeContract =
        221 function(justification, url) {
        222 // unwrap() called inside an assert so that justification can be optimized
        223 // away in production code.
        224 goog.asserts.assertString(goog.string.Const.unwrap(justification),
        225 'must provide justification');
        226 goog.asserts.assert(
        227 !goog.string.isEmptyOrWhitespace(goog.string.Const.unwrap(justification)),
        228 'must provide non-empty justification');
        229 return goog.html.TrustedResourceUrl.
        230 createTrustedResourceUrlSecurityPrivateDoNotAccessOrElse(url);
        231};
        \ No newline at end of file diff --git a/docs/source/lib/goog/i18n/bidi.js.src.html b/docs/source/lib/goog/i18n/bidi.js.src.html new file mode 100644 index 0000000..34b4bf6 --- /dev/null +++ b/docs/source/lib/goog/i18n/bidi.js.src.html @@ -0,0 +1 @@ +bidi.js

        lib/goog/i18n/bidi.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utility functions for supporting Bidi issues.
        17 */
        18
        19
        20/**
        21 * Namespace for bidi supporting functions.
        22 */
        23goog.provide('goog.i18n.bidi');
        24goog.provide('goog.i18n.bidi.Dir');
        25goog.provide('goog.i18n.bidi.DirectionalString');
        26goog.provide('goog.i18n.bidi.Format');
        27
        28
        29/**
        30 * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
        31 * to say that the current locale is a RTL locale. This should only be used
        32 * if you want to override the default behavior for deciding whether the
        33 * current locale is RTL or not.
        34 *
        35 * {@see goog.i18n.bidi.IS_RTL}
        36 */
        37goog.define('goog.i18n.bidi.FORCE_RTL', false);
        38
        39
        40/**
        41 * Constant that defines whether or not the current locale is a RTL locale.
        42 * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
        43 * to check that {@link goog.LOCALE} is one of a few major RTL locales.
        44 *
        45 * <p>This is designed to be a maximally efficient compile-time constant. For
        46 * example, for the default goog.LOCALE, compiling
        47 * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
        48 * is this design consideration that limits the implementation to only
        49 * supporting a few major RTL locales, as opposed to the broader repertoire of
        50 * something like goog.i18n.bidi.isRtlLanguage.
        51 *
        52 * <p>Since this constant refers to the directionality of the locale, it is up
        53 * to the caller to determine if this constant should also be used for the
        54 * direction of the UI.
        55 *
        56 * {@see goog.LOCALE}
        57 *
        58 * @type {boolean}
        59 *
        60 * TODO(user): write a test that checks that this is a compile-time constant.
        61 */
        62goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL ||
        63 (
        64 (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
        65 goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
        66 goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
        67 goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
        68 goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
        69 goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
        70 goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
        71 goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
        72 goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
        73 (goog.LOCALE.length == 2 ||
        74 goog.LOCALE.substring(2, 3) == '-' ||
        75 goog.LOCALE.substring(2, 3) == '_')
        76 ) || (
        77 goog.LOCALE.length >= 3 &&
        78 goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
        79 (goog.LOCALE.length == 3 ||
        80 goog.LOCALE.substring(3, 4) == '-' ||
        81 goog.LOCALE.substring(3, 4) == '_')
        82 );
        83
        84
        85/**
        86 * Unicode formatting characters and directionality string constants.
        87 * @enum {string}
        88 */
        89goog.i18n.bidi.Format = {
        90 /** Unicode "Left-To-Right Embedding" (LRE) character. */
        91 LRE: '\u202A',
        92 /** Unicode "Right-To-Left Embedding" (RLE) character. */
        93 RLE: '\u202B',
        94 /** Unicode "Pop Directional Formatting" (PDF) character. */
        95 PDF: '\u202C',
        96 /** Unicode "Left-To-Right Mark" (LRM) character. */
        97 LRM: '\u200E',
        98 /** Unicode "Right-To-Left Mark" (RLM) character. */
        99 RLM: '\u200F'
        100};
        101
        102
        103/**
        104 * Directionality enum.
        105 * @enum {number}
        106 */
        107goog.i18n.bidi.Dir = {
        108 /**
        109 * Left-to-right.
        110 */
        111 LTR: 1,
        112
        113 /**
        114 * Right-to-left.
        115 */
        116 RTL: -1,
        117
        118 /**
        119 * Neither left-to-right nor right-to-left.
        120 */
        121 NEUTRAL: 0
        122};
        123
        124
        125/**
        126 * 'right' string constant.
        127 * @type {string}
        128 */
        129goog.i18n.bidi.RIGHT = 'right';
        130
        131
        132/**
        133 * 'left' string constant.
        134 * @type {string}
        135 */
        136goog.i18n.bidi.LEFT = 'left';
        137
        138
        139/**
        140 * 'left' if locale is RTL, 'right' if not.
        141 * @type {string}
        142 */
        143goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT :
        144 goog.i18n.bidi.RIGHT;
        145
        146
        147/**
        148 * 'right' if locale is RTL, 'left' if not.
        149 * @type {string}
        150 */
        151goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT :
        152 goog.i18n.bidi.LEFT;
        153
        154
        155/**
        156 * Convert a directionality given in various formats to a goog.i18n.bidi.Dir
        157 * constant. Useful for interaction with different standards of directionality
        158 * representation.
        159 *
        160 * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
        161 * in one of the following formats:
        162 * 1. A goog.i18n.bidi.Dir constant.
        163 * 2. A number (positive = LTR, negative = RTL, 0 = neutral).
        164 * 3. A boolean (true = RTL, false = LTR).
        165 * 4. A null for unknown directionality.
        166 * @param {boolean=} opt_noNeutral Whether a givenDir of zero or
        167 * goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
        168 * order to preserve legacy behavior.
        169 * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
        170 * given directionality. If given null, returns null (i.e. unknown).
        171 */
        172goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
        173 if (typeof givenDir == 'number') {
        174 // This includes the non-null goog.i18n.bidi.Dir case.
        175 return givenDir > 0 ? goog.i18n.bidi.Dir.LTR :
        176 givenDir < 0 ? goog.i18n.bidi.Dir.RTL :
        177 opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
        178 } else if (givenDir == null) {
        179 return null;
        180 } else {
        181 // Must be typeof givenDir == 'boolean'.
        182 return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
        183 }
        184};
        185
        186
        187/**
        188 * A practical pattern to identify strong LTR characters. This pattern is not
        189 * theoretically correct according to the Unicode standard. It is simplified for
        190 * performance and small code size.
        191 * @type {string}
        192 * @private
        193 */
        194goog.i18n.bidi.ltrChars_ =
        195 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
        196 '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
        197
        198
        199/**
        200 * A practical pattern to identify strong RTL character. This pattern is not
        201 * theoretically correct according to the Unicode standard. It is simplified
        202 * for performance and small code size.
        203 * @type {string}
        204 * @private
        205 */
        206goog.i18n.bidi.rtlChars_ =
        207 '\u0591-\u06EF\u06FA-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
        208
        209
        210/**
        211 * Simplified regular expression for an HTML tag (opening or closing) or an HTML
        212 * escape. We might want to skip over such expressions when estimating the text
        213 * directionality.
        214 * @type {RegExp}
        215 * @private
        216 */
        217goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
        218
        219
        220/**
        221 * Returns the input text with spaces instead of HTML tags or HTML escapes, if
        222 * opt_isStripNeeded is true. Else returns the input as is.
        223 * Useful for text directionality estimation.
        224 * Note: the function should not be used in other contexts; it is not 100%
        225 * correct, but rather a good-enough implementation for directionality
        226 * estimation purposes.
        227 * @param {string} str The given string.
        228 * @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
        229 * Default: false (to retain consistency with calling functions).
        230 * @return {string} The given string cleaned of HTML tags / escapes.
        231 * @private
        232 */
        233goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
        234 return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') :
        235 str;
        236};
        237
        238
        239/**
        240 * Regular expression to check for RTL characters.
        241 * @type {RegExp}
        242 * @private
        243 */
        244goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
        245
        246
        247/**
        248 * Regular expression to check for LTR characters.
        249 * @type {RegExp}
        250 * @private
        251 */
        252goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
        253
        254
        255/**
        256 * Test whether the given string has any RTL characters in it.
        257 * @param {string} str The given string that need to be tested.
        258 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        259 * Default: false.
        260 * @return {boolean} Whether the string contains RTL characters.
        261 */
        262goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
        263 return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
        264 str, opt_isHtml));
        265};
        266
        267
        268/**
        269 * Test whether the given string has any RTL characters in it.
        270 * @param {string} str The given string that need to be tested.
        271 * @return {boolean} Whether the string contains RTL characters.
        272 * @deprecated Use hasAnyRtl.
        273 */
        274goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
        275
        276
        277/**
        278 * Test whether the given string has any LTR characters in it.
        279 * @param {string} str The given string that need to be tested.
        280 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        281 * Default: false.
        282 * @return {boolean} Whether the string contains LTR characters.
        283 */
        284goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
        285 return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
        286 str, opt_isHtml));
        287};
        288
        289
        290/**
        291 * Regular expression pattern to check if the first character in the string
        292 * is LTR.
        293 * @type {RegExp}
        294 * @private
        295 */
        296goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
        297
        298
        299/**
        300 * Regular expression pattern to check if the first character in the string
        301 * is RTL.
        302 * @type {RegExp}
        303 * @private
        304 */
        305goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
        306
        307
        308/**
        309 * Check if the first character in the string is RTL or not.
        310 * @param {string} str The given string that need to be tested.
        311 * @return {boolean} Whether the first character in str is an RTL char.
        312 */
        313goog.i18n.bidi.isRtlChar = function(str) {
        314 return goog.i18n.bidi.rtlRe_.test(str);
        315};
        316
        317
        318/**
        319 * Check if the first character in the string is LTR or not.
        320 * @param {string} str The given string that need to be tested.
        321 * @return {boolean} Whether the first character in str is an LTR char.
        322 */
        323goog.i18n.bidi.isLtrChar = function(str) {
        324 return goog.i18n.bidi.ltrRe_.test(str);
        325};
        326
        327
        328/**
        329 * Check if the first character in the string is neutral or not.
        330 * @param {string} str The given string that need to be tested.
        331 * @return {boolean} Whether the first character in str is a neutral char.
        332 */
        333goog.i18n.bidi.isNeutralChar = function(str) {
        334 return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
        335};
        336
        337
        338/**
        339 * Regular expressions to check if a piece of text is of LTR directionality
        340 * on first character with strong directionality.
        341 * @type {RegExp}
        342 * @private
        343 */
        344goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
        345 '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
        346
        347
        348/**
        349 * Regular expressions to check if a piece of text is of RTL directionality
        350 * on first character with strong directionality.
        351 * @type {RegExp}
        352 * @private
        353 */
        354goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
        355 '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
        356
        357
        358/**
        359 * Check whether the first strongly directional character (if any) is RTL.
        360 * @param {string} str String being checked.
        361 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        362 * Default: false.
        363 * @return {boolean} Whether RTL directionality is detected using the first
        364 * strongly-directional character method.
        365 */
        366goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
        367 return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
        368 str, opt_isHtml));
        369};
        370
        371
        372/**
        373 * Check whether the first strongly directional character (if any) is RTL.
        374 * @param {string} str String being checked.
        375 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        376 * Default: false.
        377 * @return {boolean} Whether RTL directionality is detected using the first
        378 * strongly-directional character method.
        379 * @deprecated Use startsWithRtl.
        380 */
        381goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
        382
        383
        384/**
        385 * Check whether the first strongly directional character (if any) is LTR.
        386 * @param {string} str String being checked.
        387 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        388 * Default: false.
        389 * @return {boolean} Whether LTR directionality is detected using the first
        390 * strongly-directional character method.
        391 */
        392goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
        393 return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
        394 str, opt_isHtml));
        395};
        396
        397
        398/**
        399 * Check whether the first strongly directional character (if any) is LTR.
        400 * @param {string} str String being checked.
        401 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        402 * Default: false.
        403 * @return {boolean} Whether LTR directionality is detected using the first
        404 * strongly-directional character method.
        405 * @deprecated Use startsWithLtr.
        406 */
        407goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
        408
        409
        410/**
        411 * Regular expression to check if a string looks like something that must
        412 * always be LTR even in RTL text, e.g. a URL. When estimating the
        413 * directionality of text containing these, we treat these as weakly LTR,
        414 * like numbers.
        415 * @type {RegExp}
        416 * @private
        417 */
        418goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
        419
        420
        421/**
        422 * Check whether the input string either contains no strongly directional
        423 * characters or looks like a url.
        424 * @param {string} str String being checked.
        425 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        426 * Default: false.
        427 * @return {boolean} Whether neutral directionality is detected.
        428 */
        429goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
        430 str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
        431 return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
        432 !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
        433};
        434
        435
        436/**
        437 * Regular expressions to check if the last strongly-directional character in a
        438 * piece of text is LTR.
        439 * @type {RegExp}
        440 * @private
        441 */
        442goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
        443 '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
        444
        445
        446/**
        447 * Regular expressions to check if the last strongly-directional character in a
        448 * piece of text is RTL.
        449 * @type {RegExp}
        450 * @private
        451 */
        452goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
        453 '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$');
        454
        455
        456/**
        457 * Check if the exit directionality a piece of text is LTR, i.e. if the last
        458 * strongly-directional character in the string is LTR.
        459 * @param {string} str String being checked.
        460 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        461 * Default: false.
        462 * @return {boolean} Whether LTR exit directionality was detected.
        463 */
        464goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
        465 return goog.i18n.bidi.ltrExitDirCheckRe_.test(
        466 goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
        467};
        468
        469
        470/**
        471 * Check if the exit directionality a piece of text is LTR, i.e. if the last
        472 * strongly-directional character in the string is LTR.
        473 * @param {string} str String being checked.
        474 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        475 * Default: false.
        476 * @return {boolean} Whether LTR exit directionality was detected.
        477 * @deprecated Use endsWithLtr.
        478 */
        479goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
        480
        481
        482/**
        483 * Check if the exit directionality a piece of text is RTL, i.e. if the last
        484 * strongly-directional character in the string is RTL.
        485 * @param {string} str String being checked.
        486 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        487 * Default: false.
        488 * @return {boolean} Whether RTL exit directionality was detected.
        489 */
        490goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
        491 return goog.i18n.bidi.rtlExitDirCheckRe_.test(
        492 goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
        493};
        494
        495
        496/**
        497 * Check if the exit directionality a piece of text is RTL, i.e. if the last
        498 * strongly-directional character in the string is RTL.
        499 * @param {string} str String being checked.
        500 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        501 * Default: false.
        502 * @return {boolean} Whether RTL exit directionality was detected.
        503 * @deprecated Use endsWithRtl.
        504 */
        505goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
        506
        507
        508/**
        509 * A regular expression for matching right-to-left language codes.
        510 * See {@link #isRtlLanguage} for the design.
        511 * @type {RegExp}
        512 * @private
        513 */
        514goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
        515 '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
        516 '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
        517 '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
        518 'i');
        519
        520
        521/**
        522 * Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
        523 * - a language code explicitly specifying one of the right-to-left scripts,
        524 * e.g. "az-Arab", or<p>
        525 * - a language code specifying one of the languages normally written in a
        526 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
        527 * Latin or Cyrillic script (which are the usual LTR alternatives).<p>
        528 * The list of right-to-left scripts appears in the 100-199 range in
        529 * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
        530 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
        531 * Tifinagh, which also have significant modern usage. The rest (Syriac,
        532 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
        533 * and are not recognized to save on code size.
        534 * The languages usually written in a right-to-left script are taken as those
        535 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in
        536 * http://www.iana.org/assignments/language-subtag-registry,
        537 * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
        538 * Other subtags of the language code, e.g. regions like EG (Egypt), are
        539 * ignored.
        540 * @param {string} lang BCP 47 (a.k.a III) language code.
        541 * @return {boolean} Whether the language code is an RTL language.
        542 */
        543goog.i18n.bidi.isRtlLanguage = function(lang) {
        544 return goog.i18n.bidi.rtlLocalesRe_.test(lang);
        545};
        546
        547
        548/**
        549 * Regular expression for bracket guard replacement in html.
        550 * @type {RegExp}
        551 * @private
        552 */
        553goog.i18n.bidi.bracketGuardHtmlRe_ =
        554 /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
        555
        556
        557/**
        558 * Regular expression for bracket guard replacement in text.
        559 * @type {RegExp}
        560 * @private
        561 */
        562goog.i18n.bidi.bracketGuardTextRe_ =
        563 /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
        564
        565
        566/**
        567 * Apply bracket guard using html span tag. This is to address the problem of
        568 * messy bracket display frequently happens in RTL layout.
        569 * @param {string} s The string that need to be processed.
        570 * @param {boolean=} opt_isRtlContext specifies default direction (usually
        571 * direction of the UI).
        572 * @return {string} The processed string, with all bracket guarded.
        573 */
        574goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) {
        575 var useRtl = opt_isRtlContext === undefined ?
        576 goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
        577 if (useRtl) {
        578 return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
        579 '<span dir=rtl>$&</span>');
        580 }
        581 return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
        582 '<span dir=ltr>$&</span>');
        583};
        584
        585
        586/**
        587 * Apply bracket guard using LRM and RLM. This is to address the problem of
        588 * messy bracket display frequently happens in RTL layout.
        589 * This version works for both plain text and html. But it does not work as
        590 * good as guardBracketInHtml in some cases.
        591 * @param {string} s The string that need to be processed.
        592 * @param {boolean=} opt_isRtlContext specifies default direction (usually
        593 * direction of the UI).
        594 * @return {string} The processed string, with all bracket guarded.
        595 */
        596goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
        597 var useRtl = opt_isRtlContext === undefined ?
        598 goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
        599 var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
        600 return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
        601};
        602
        603
        604/**
        605 * Enforce the html snippet in RTL directionality regardless overall context.
        606 * If the html piece was enclosed by tag, dir will be applied to existing
        607 * tag, otherwise a span tag will be added as wrapper. For this reason, if
        608 * html snippet start with with tag, this tag must enclose the whole piece. If
        609 * the tag already has a dir specified, this new one will override existing
        610 * one in behavior (tested on FF and IE).
        611 * @param {string} html The string that need to be processed.
        612 * @return {string} The processed string, with directionality enforced to RTL.
        613 */
        614goog.i18n.bidi.enforceRtlInHtml = function(html) {
        615 if (html.charAt(0) == '<') {
        616 return html.replace(/<\w+/, '$& dir=rtl');
        617 }
        618 // '\n' is important for FF so that it won't incorrectly merge span groups
        619 return '\n<span dir=rtl>' + html + '</span>';
        620};
        621
        622
        623/**
        624 * Enforce RTL on both end of the given text piece using unicode BiDi formatting
        625 * characters RLE and PDF.
        626 * @param {string} text The piece of text that need to be wrapped.
        627 * @return {string} The wrapped string after process.
        628 */
        629goog.i18n.bidi.enforceRtlInText = function(text) {
        630 return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
        631};
        632
        633
        634/**
        635 * Enforce the html snippet in RTL directionality regardless overall context.
        636 * If the html piece was enclosed by tag, dir will be applied to existing
        637 * tag, otherwise a span tag will be added as wrapper. For this reason, if
        638 * html snippet start with with tag, this tag must enclose the whole piece. If
        639 * the tag already has a dir specified, this new one will override existing
        640 * one in behavior (tested on FF and IE).
        641 * @param {string} html The string that need to be processed.
        642 * @return {string} The processed string, with directionality enforced to RTL.
        643 */
        644goog.i18n.bidi.enforceLtrInHtml = function(html) {
        645 if (html.charAt(0) == '<') {
        646 return html.replace(/<\w+/, '$& dir=ltr');
        647 }
        648 // '\n' is important for FF so that it won't incorrectly merge span groups
        649 return '\n<span dir=ltr>' + html + '</span>';
        650};
        651
        652
        653/**
        654 * Enforce LTR on both end of the given text piece using unicode BiDi formatting
        655 * characters LRE and PDF.
        656 * @param {string} text The piece of text that need to be wrapped.
        657 * @return {string} The wrapped string after process.
        658 */
        659goog.i18n.bidi.enforceLtrInText = function(text) {
        660 return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
        661};
        662
        663
        664/**
        665 * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
        666 * @type {RegExp}
        667 * @private
        668 */
        669goog.i18n.bidi.dimensionsRe_ =
        670 /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
        671
        672
        673/**
        674 * Regular expression for left.
        675 * @type {RegExp}
        676 * @private
        677 */
        678goog.i18n.bidi.leftRe_ = /left/gi;
        679
        680
        681/**
        682 * Regular expression for right.
        683 * @type {RegExp}
        684 * @private
        685 */
        686goog.i18n.bidi.rightRe_ = /right/gi;
        687
        688
        689/**
        690 * Placeholder regular expression for swapping.
        691 * @type {RegExp}
        692 * @private
        693 */
        694goog.i18n.bidi.tempRe_ = /%%%%/g;
        695
        696
        697/**
        698 * Swap location parameters and 'left'/'right' in CSS specification. The
        699 * processed string will be suited for RTL layout. Though this function can
        700 * cover most cases, there are always exceptions. It is suggested to put
        701 * those exceptions in separate group of CSS string.
        702 * @param {string} cssStr CSS spefication string.
        703 * @return {string} Processed CSS specification string.
        704 */
        705goog.i18n.bidi.mirrorCSS = function(cssStr) {
        706 return cssStr.
        707 // reverse dimensions
        708 replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2').
        709 replace(goog.i18n.bidi.leftRe_, '%%%%'). // swap left and right
        710 replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT).
        711 replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
        712};
        713
        714
        715/**
        716 * Regular expression for hebrew double quote substitution, finding quote
        717 * directly after hebrew characters.
        718 * @type {RegExp}
        719 * @private
        720 */
        721goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
        722
        723
        724/**
        725 * Regular expression for hebrew single quote substitution, finding quote
        726 * directly after hebrew characters.
        727 * @type {RegExp}
        728 * @private
        729 */
        730goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
        731
        732
        733/**
        734 * Replace the double and single quote directly after a Hebrew character with
        735 * GERESH and GERSHAYIM. In such case, most likely that's user intention.
        736 * @param {string} str String that need to be processed.
        737 * @return {string} Processed string with double/single quote replaced.
        738 */
        739goog.i18n.bidi.normalizeHebrewQuote = function(str) {
        740 return str.
        741 replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4').
        742 replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
        743};
        744
        745
        746/**
        747 * Regular expression to split a string into "words" for directionality
        748 * estimation based on relative word counts.
        749 * @type {RegExp}
        750 * @private
        751 */
        752goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
        753
        754
        755/**
        756 * Regular expression to check if a string contains any numerals. Used to
        757 * differentiate between completely neutral strings and those containing
        758 * numbers, which are weakly LTR.
        759 *
        760 * Native Arabic digits (\u0660 - \u0669) are not included because although they
        761 * do flow left-to-right inside a number, this is the case even if the overall
        762 * directionality is RTL, and a mathematical expression using these digits is
        763 * supposed to flow right-to-left overall, including unary plus and minus
        764 * appearing to the right of a number, and this does depend on the overall
        765 * directionality being RTL. The digits used in Farsi (\u06F0 - \u06F9), on the
        766 * other hand, are included, since Farsi math (including unary plus and minus)
        767 * does flow left-to-right.
        768 *
        769 * @type {RegExp}
        770 * @private
        771 */
        772goog.i18n.bidi.hasNumeralsRe_ = /[\d\u06f0-\u06f9]/;
        773
        774
        775/**
        776 * This constant controls threshold of RTL directionality.
        777 * @type {number}
        778 * @private
        779 */
        780goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
        781
        782
        783/**
        784 * Estimates the directionality of a string based on relative word counts.
        785 * If the number of RTL words is above a certain percentage of the total number
        786 * of strongly directional words, returns RTL.
        787 * Otherwise, if any words are strongly or weakly LTR, returns LTR.
        788 * Otherwise, returns UNKNOWN, which is used to mean "neutral".
        789 * Numbers are counted as weakly LTR.
        790 * @param {string} str The string to be checked.
        791 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        792 * Default: false.
        793 * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}.
        794 */
        795goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
        796 var rtlCount = 0;
        797 var totalCount = 0;
        798 var hasWeaklyLtr = false;
        799 var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml).
        800 split(goog.i18n.bidi.wordSeparatorRe_);
        801 for (var i = 0; i < tokens.length; i++) {
        802 var token = tokens[i];
        803 if (goog.i18n.bidi.startsWithRtl(token)) {
        804 rtlCount++;
        805 totalCount++;
        806 } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
        807 hasWeaklyLtr = true;
        808 } else if (goog.i18n.bidi.hasAnyLtr(token)) {
        809 totalCount++;
        810 } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
        811 hasWeaklyLtr = true;
        812 }
        813 }
        814
        815 return totalCount == 0 ?
        816 (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
        817 (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
        818 goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR);
        819};
        820
        821
        822/**
        823 * Check the directionality of a piece of text, return true if the piece of
        824 * text should be laid out in RTL direction.
        825 * @param {string} str The piece of text that need to be detected.
        826 * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
        827 * Default: false.
        828 * @return {boolean} Whether this piece of text should be laid out in RTL.
        829 */
        830goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
        831 return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
        832 goog.i18n.bidi.Dir.RTL;
        833};
        834
        835
        836/**
        837 * Sets text input element's directionality and text alignment based on a
        838 * given directionality. Does nothing if the given directionality is unknown or
        839 * neutral.
        840 * @param {Element} element Input field element to set directionality to.
        841 * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
        842 * given in one of the following formats:
        843 * 1. A goog.i18n.bidi.Dir constant.
        844 * 2. A number (positive = LRT, negative = RTL, 0 = neutral).
        845 * 3. A boolean (true = RTL, false = LTR).
        846 * 4. A null for unknown directionality.
        847 */
        848goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
        849 if (element) {
        850 dir = goog.i18n.bidi.toDir(dir);
        851 if (dir) {
        852 element.style.textAlign =
        853 dir == goog.i18n.bidi.Dir.RTL ?
        854 goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
        855 element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
        856 }
        857 }
        858};
        859
        860
        861/**
        862 * Sets element dir based on estimated directionality of the given text.
        863 * @param {!Element} element
        864 * @param {string} text
        865 */
        866goog.i18n.bidi.setElementDirByTextDirectionality = function(element, text) {
        867 switch (goog.i18n.bidi.estimateDirection(text)) {
        868 case (goog.i18n.bidi.Dir.LTR):
        869 element.dir = 'ltr';
        870 break;
        871 case (goog.i18n.bidi.Dir.RTL):
        872 element.dir = 'rtl';
        873 break;
        874 default:
        875 // Default for no direction, inherit from document.
        876 element.removeAttribute('dir');
        877 }
        878};
        879
        880
        881
        882/**
        883 * Strings that have an (optional) known direction.
        884 *
        885 * Implementations of this interface are string-like objects that carry an
        886 * attached direction, if known.
        887 * @interface
        888 */
        889goog.i18n.bidi.DirectionalString = function() {};
        890
        891
        892/**
        893 * Interface marker of the DirectionalString interface.
        894 *
        895 * This property can be used to determine at runtime whether or not an object
        896 * implements this interface. All implementations of this interface set this
        897 * property to {@code true}.
        898 * @type {boolean}
        899 */
        900goog.i18n.bidi.DirectionalString.prototype.
        901 implementsGoogI18nBidiDirectionalString;
        902
        903
        904/**
        905 * Retrieves this object's known direction (if any).
        906 * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
        907 */
        908goog.i18n.bidi.DirectionalString.prototype.getDirection;
        \ No newline at end of file diff --git a/docs/source/lib/goog/iter/iter.js.src.html b/docs/source/lib/goog/iter/iter.js.src.html index d75b9d2..b0446c4 100644 --- a/docs/source/lib/goog/iter/iter.js.src.html +++ b/docs/source/lib/goog/iter/iter.js.src.html @@ -1 +1 @@ -iter.js

        lib/goog/iter/iter.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Python style iteration utilities.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20
        21goog.provide('goog.iter');
        22goog.provide('goog.iter.Iterator');
        23goog.provide('goog.iter.StopIteration');
        24
        25goog.require('goog.array');
        26goog.require('goog.asserts');
        27
        28
        29// TODO(nnaze): Add more functions from Python's itertools.
        30// http://docs.python.org/library/itertools.html
        31
        32
        33/**
        34 * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
        35 */
        36goog.iter.Iterable;
        37
        38
        39// For script engines that already support iterators.
        40if ('StopIteration' in goog.global) {
        41 /**
        42 * Singleton Error object that is used to terminate iterations.
        43 * @type {Error}
        44 */
        45 goog.iter.StopIteration = goog.global['StopIteration'];
        46} else {
        47 /**
        48 * Singleton Error object that is used to terminate iterations.
        49 * @type {Error}
        50 * @suppress {duplicate}
        51 */
        52 goog.iter.StopIteration = Error('StopIteration');
        53}
        54
        55
        56
        57/**
        58 * Class/interface for iterators. An iterator needs to implement a {@code next}
        59 * method and it needs to throw a {@code goog.iter.StopIteration} when the
        60 * iteration passes beyond the end. Iterators have no {@code hasNext} method.
        61 * It is recommended to always use the helper functions to iterate over the
        62 * iterator or in case you are only targeting JavaScript 1.7 for in loops.
        63 * @constructor
        64 */
        65goog.iter.Iterator = function() {};
        66
        67
        68/**
        69 * Returns the next value of the iteration. This will throw the object
        70 * {@see goog.iter#StopIteration} when the iteration passes the end.
        71 * @return {*} Any object or value.
        72 */
        73goog.iter.Iterator.prototype.next = function() {
        74 throw goog.iter.StopIteration;
        75};
        76
        77
        78/**
        79 * Returns the {@code Iterator} object itself. This is used to implement
        80 * the iterator protocol in JavaScript 1.7
        81 * @param {boolean=} opt_keys Whether to return the keys or values. Default is
        82 * to only return the values. This is being used by the for-in loop (true)
        83 * and the for-each-in loop (false). Even though the param gives a hint
        84 * about what the iterator will return there is no guarantee that it will
        85 * return the keys when true is passed.
        86 * @return {!goog.iter.Iterator} The object itself.
        87 */
        88goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
        89 return this;
        90};
        91
        92
        93/**
        94 * Returns an iterator that knows how to iterate over the values in the object.
        95 * @param {goog.iter.Iterable} iterable If the object is an iterator it
        96 * will be returned as is. If the object has a {@code __iterator__} method
        97 * that will be called to get the value iterator. If the object is an
        98 * array-like object we create an iterator for that.
        99 * @return {!goog.iter.Iterator} An iterator that knows how to iterate over the
        100 * values in {@code iterable}.
        101 */
        102goog.iter.toIterator = function(iterable) {
        103 if (iterable instanceof goog.iter.Iterator) {
        104 return iterable;
        105 }
        106 if (typeof iterable.__iterator__ == 'function') {
        107 return iterable.__iterator__(false);
        108 }
        109 if (goog.isArrayLike(iterable)) {
        110 var i = 0;
        111 var newIter = new goog.iter.Iterator;
        112 newIter.next = function() {
        113 while (true) {
        114 if (i >= iterable.length) {
        115 throw goog.iter.StopIteration;
        116 }
        117 // Don't include deleted elements.
        118 if (!(i in iterable)) {
        119 i++;
        120 continue;
        121 }
        122 return iterable[i++];
        123 }
        124 };
        125 return newIter;
        126 }
        127
        128
        129 // TODO(arv): Should we fall back on goog.structs.getValues()?
        130 throw Error('Not implemented');
        131};
        132
        133
        134/**
        135 * Calls a function for each element in the iterator with the element of the
        136 * iterator passed as argument.
        137 *
        138 * @param {goog.iter.Iterable} iterable The iterator to iterate
        139 * over. If the iterable is an object {@code toIterator} will be called on
        140 * it.
        141* @param {function(this:T,?,?,?):?} f The function to call for every
        142 * element. This function
        143 * takes 3 arguments (the element, undefined, and the iterator) and the
        144 * return value is irrelevant. The reason for passing undefined as the
        145 * second argument is so that the same function can be used in
        146 * {@see goog.array#forEach} as well as others.
        147 * @param {T=} opt_obj The object to be used as the value of 'this' within
        148 * {@code f}.
        149 * @template T
        150 */
        151goog.iter.forEach = function(iterable, f, opt_obj) {
        152 if (goog.isArrayLike(iterable)) {
        153 /** @preserveTry */
        154 try {
        155 // NOTES: this passes the index number to the second parameter
        156 // of the callback contrary to the documentation above.
        157 goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
        158 opt_obj);
        159 } catch (ex) {
        160 if (ex !== goog.iter.StopIteration) {
        161 throw ex;
        162 }
        163 }
        164 } else {
        165 iterable = goog.iter.toIterator(iterable);
        166 /** @preserveTry */
        167 try {
        168 while (true) {
        169 f.call(opt_obj, iterable.next(), undefined, iterable);
        170 }
        171 } catch (ex) {
        172 if (ex !== goog.iter.StopIteration) {
        173 throw ex;
        174 }
        175 }
        176 }
        177};
        178
        179
        180/**
        181 * Calls a function for every element in the iterator, and if the function
        182 * returns true adds the element to a new iterator.
        183 *
        184 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
        185 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
        186 * every element. This function
        187 * takes 3 arguments (the element, undefined, and the iterator) and should
        188 * return a boolean. If the return value is true the element will be
        189 * included in the returned iteror. If it is false the element is not
        190 * included.
        191 * @param {T=} opt_obj The object to be used as the value of 'this' within
        192 * {@code f}.
        193 * @return {!goog.iter.Iterator} A new iterator in which only elements that
        194 * passed the test are present.
        195 * @template T
        196 */
        197goog.iter.filter = function(iterable, f, opt_obj) {
        198 var iterator = goog.iter.toIterator(iterable);
        199 var newIter = new goog.iter.Iterator;
        200 newIter.next = function() {
        201 while (true) {
        202 var val = iterator.next();
        203 if (f.call(opt_obj, val, undefined, iterator)) {
        204 return val;
        205 }
        206 }
        207 };
        208 return newIter;
        209};
        210
        211
        212/**
        213 * Creates a new iterator that returns the values in a range. This function
        214 * can take 1, 2 or 3 arguments:
        215 * <pre>
        216 * range(5) same as range(0, 5, 1)
        217 * range(2, 5) same as range(2, 5, 1)
        218 * </pre>
        219 *
        220 * @param {number} startOrStop The stop value if only one argument is provided.
        221 * The start value if 2 or more arguments are provided. If only one
        222 * argument is used the start value is 0.
        223 * @param {number=} opt_stop The stop value. If left out then the first
        224 * argument is used as the stop value.
        225 * @param {number=} opt_step The number to increment with between each call to
        226 * next. This can be negative.
        227 * @return {!goog.iter.Iterator} A new iterator that returns the values in the
        228 * range.
        229 */
        230goog.iter.range = function(startOrStop, opt_stop, opt_step) {
        231 var start = 0;
        232 var stop = startOrStop;
        233 var step = opt_step || 1;
        234 if (arguments.length > 1) {
        235 start = startOrStop;
        236 stop = opt_stop;
        237 }
        238 if (step == 0) {
        239 throw Error('Range step argument must not be zero');
        240 }
        241
        242 var newIter = new goog.iter.Iterator;
        243 newIter.next = function() {
        244 if (step > 0 && start >= stop || step < 0 && start <= stop) {
        245 throw goog.iter.StopIteration;
        246 }
        247 var rv = start;
        248 start += step;
        249 return rv;
        250 };
        251 return newIter;
        252};
        253
        254
        255/**
        256 * Joins the values in a iterator with a delimiter.
        257 * @param {goog.iter.Iterable} iterable The iterator to get the values from.
        258 * @param {string} deliminator The text to put between the values.
        259 * @return {string} The joined value string.
        260 */
        261goog.iter.join = function(iterable, deliminator) {
        262 return goog.iter.toArray(iterable).join(deliminator);
        263};
        264
        265
        266/**
        267 * For every element in the iterator call a function and return a new iterator
        268 * with that value.
        269 *
        270 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
        271 * @param {function(this:T,?,undefined,?):?} f The function to call for every
        272 * element. This function
        273 * takes 3 arguments (the element, undefined, and the iterator) and should
        274 * return a new value.
        275 * @param {T=} opt_obj The object to be used as the value of 'this' within
        276 * {@code f}.
        277 * @return {!goog.iter.Iterator} A new iterator that returns the results of
        278 * applying the function to each element in the original iterator.
        279 * @template T
        280 */
        281goog.iter.map = function(iterable, f, opt_obj) {
        282 var iterator = goog.iter.toIterator(iterable);
        283 var newIter = new goog.iter.Iterator;
        284 newIter.next = function() {
        285 while (true) {
        286 var val = iterator.next();
        287 return f.call(opt_obj, val, undefined, iterator);
        288 }
        289 };
        290 return newIter;
        291};
        292
        293
        294/**
        295 * Passes every element of an iterator into a function and accumulates the
        296 * result.
        297 *
        298 * @param {goog.iter.Iterable} iterable The iterator to iterate over.
        299 * @param {function(this:T,V,?):V} f The function to call for every
        300 * element. This function takes 2 arguments (the function's previous result
        301 * or the initial value, and the value of the current element).
        302 * function(previousValue, currentElement) : newValue.
        303 * @param {V} val The initial value to pass into the function on the first call.
        304 * @param {T=} opt_obj The object to be used as the value of 'this'
        305 * within f.
        306 * @return {V} Result of evaluating f repeatedly across the values of
        307 * the iterator.
        308 * @template T,V
        309 */
        310goog.iter.reduce = function(iterable, f, val, opt_obj) {
        311 var rval = val;
        312 goog.iter.forEach(iterable, function(val) {
        313 rval = f.call(opt_obj, rval, val);
        314 });
        315 return rval;
        316};
        317
        318
        319/**
        320 * Goes through the values in the iterator. Calls f for each these and if any of
        321 * them returns true, this returns true (without checking the rest). If all
        322 * return false this will return false.
        323 *
        324 * @param {goog.iter.Iterable} iterable The iterator object.
        325 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
        326 * every value. This function
        327 * takes 3 arguments (the value, undefined, and the iterator) and should
        328 * return a boolean.
        329 * @param {T=} opt_obj The object to be used as the value of 'this' within
        330 * {@code f}.
        331 * @return {boolean} true if any value passes the test.
        332 * @template T
        333 */
        334goog.iter.some = function(iterable, f, opt_obj) {
        335 iterable = goog.iter.toIterator(iterable);
        336 /** @preserveTry */
        337 try {
        338 while (true) {
        339 if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
        340 return true;
        341 }
        342 }
        343 } catch (ex) {
        344 if (ex !== goog.iter.StopIteration) {
        345 throw ex;
        346 }
        347 }
        348 return false;
        349};
        350
        351
        352/**
        353 * Goes through the values in the iterator. Calls f for each these and if any of
        354 * them returns false this returns false (without checking the rest). If all
        355 * return true this will return true.
        356 *
        357 * @param {goog.iter.Iterable} iterable The iterator object.
        358 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
        359 * every value. This function
        360 * takes 3 arguments (the value, undefined, and the iterator) and should
        361 * return a boolean.
        362 * @param {T=} opt_obj The object to be used as the value of 'this' within
        363 * {@code f}.
        364 * @return {boolean} true if every value passes the test.
        365 * @template T
        366 */
        367goog.iter.every = function(iterable, f, opt_obj) {
        368 iterable = goog.iter.toIterator(iterable);
        369 /** @preserveTry */
        370 try {
        371 while (true) {
        372 if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
        373 return false;
        374 }
        375 }
        376 } catch (ex) {
        377 if (ex !== goog.iter.StopIteration) {
        378 throw ex;
        379 }
        380 }
        381 return true;
        382};
        383
        384
        385/**
        386 * Takes zero or more iterators and returns one iterator that will iterate over
        387 * them in the order chained.
        388 * @param {...goog.iter.Iterator} var_args Any number of iterator objects.
        389 * @return {!goog.iter.Iterator} Returns a new iterator that will iterate over
        390 * all the given iterators' contents.
        391 */
        392goog.iter.chain = function(var_args) {
        393 var args = arguments;
        394 var length = args.length;
        395 var i = 0;
        396 var newIter = new goog.iter.Iterator;
        397
        398 /**
        399 * @return {*} The next item in the iteration.
        400 * @this {goog.iter.Iterator}
        401 */
        402 newIter.next = function() {
        403 /** @preserveTry */
        404 try {
        405 if (i >= length) {
        406 throw goog.iter.StopIteration;
        407 }
        408 var current = goog.iter.toIterator(args[i]);
        409 return current.next();
        410 } catch (ex) {
        411 if (ex !== goog.iter.StopIteration || i >= length) {
        412 throw ex;
        413 } else {
        414 // In case we got a StopIteration increment counter and try again.
        415 i++;
        416 return this.next();
        417 }
        418 }
        419 };
        420
        421 return newIter;
        422};
        423
        424
        425/**
        426 * Builds a new iterator that iterates over the original, but skips elements as
        427 * long as a supplied function returns true.
        428 * @param {goog.iter.Iterable} iterable The iterator object.
        429 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
        430 * every value. This function
        431 * takes 3 arguments (the value, undefined, and the iterator) and should
        432 * return a boolean.
        433 * @param {T=} opt_obj The object to be used as the value of 'this' within
        434 * {@code f}.
        435 * @return {!goog.iter.Iterator} A new iterator that drops elements from the
        436 * original iterator as long as {@code f} is true.
        437 * @template T
        438 */
        439goog.iter.dropWhile = function(iterable, f, opt_obj) {
        440 var iterator = goog.iter.toIterator(iterable);
        441 var newIter = new goog.iter.Iterator;
        442 var dropping = true;
        443 newIter.next = function() {
        444 while (true) {
        445 var val = iterator.next();
        446 if (dropping && f.call(opt_obj, val, undefined, iterator)) {
        447 continue;
        448 } else {
        449 dropping = false;
        450 }
        451 return val;
        452 }
        453 };
        454 return newIter;
        455};
        456
        457
        458/**
        459 * Builds a new iterator that iterates over the original, but only as long as a
        460 * supplied function returns true.
        461 * @param {goog.iter.Iterable} iterable The iterator object.
        462 * @param {function(this:T,?,undefined,?):boolean} f The function to call for
        463 * every value. This function
        464 * takes 3 arguments (the value, undefined, and the iterator) and should
        465 * return a boolean.
        466 * @param {T=} opt_obj This is used as the 'this' object in f when called.
        467 * @return {!goog.iter.Iterator} A new iterator that keeps elements in the
        468 * original iterator as long as the function is true.
        469 * @template T
        470 */
        471goog.iter.takeWhile = function(iterable, f, opt_obj) {
        472 var iterator = goog.iter.toIterator(iterable);
        473 var newIter = new goog.iter.Iterator;
        474 var taking = true;
        475 newIter.next = function() {
        476 while (true) {
        477 if (taking) {
        478 var val = iterator.next();
        479 if (f.call(opt_obj, val, undefined, iterator)) {
        480 return val;
        481 } else {
        482 taking = false;
        483 }
        484 } else {
        485 throw goog.iter.StopIteration;
        486 }
        487 }
        488 };
        489 return newIter;
        490};
        491
        492
        493/**
        494 * Converts the iterator to an array
        495 * @param {goog.iter.Iterable} iterable The iterator to convert to an array.
        496 * @return {!Array} An array of the elements the iterator iterates over.
        497 */
        498goog.iter.toArray = function(iterable) {
        499 // Fast path for array-like.
        500 if (goog.isArrayLike(iterable)) {
        501 return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
        502 }
        503 iterable = goog.iter.toIterator(iterable);
        504 var array = [];
        505 goog.iter.forEach(iterable, function(val) {
        506 array.push(val);
        507 });
        508 return array;
        509};
        510
        511
        512/**
        513 * Iterates over 2 iterators and returns true if they contain the same sequence
        514 * of elements and have the same length.
        515 * @param {goog.iter.Iterable} iterable1 The first iterable object.
        516 * @param {goog.iter.Iterable} iterable2 The second iterable object.
        517 * @return {boolean} true if the iterators contain the same sequence of
        518 * elements and have the same length.
        519 */
        520goog.iter.equals = function(iterable1, iterable2) {
        521 iterable1 = goog.iter.toIterator(iterable1);
        522 iterable2 = goog.iter.toIterator(iterable2);
        523 var b1, b2;
        524 /** @preserveTry */
        525 try {
        526 while (true) {
        527 b1 = b2 = false;
        528 var val1 = iterable1.next();
        529 b1 = true;
        530 var val2 = iterable2.next();
        531 b2 = true;
        532 if (val1 != val2) {
        533 return false;
        534 }
        535 }
        536 } catch (ex) {
        537 if (ex !== goog.iter.StopIteration) {
        538 throw ex;
        539 } else {
        540 if (b1 && !b2) {
        541 // iterable1 done but iterable2 is not done.
        542 return false;
        543 }
        544 if (!b2) {
        545 /** @preserveTry */
        546 try {
        547 // iterable2 not done?
        548 val2 = iterable2.next();
        549 // iterable2 not done but iterable1 is done
        550 return false;
        551 } catch (ex1) {
        552 if (ex1 !== goog.iter.StopIteration) {
        553 throw ex1;
        554 }
        555 // iterable2 done as well... They are equal
        556 return true;
        557 }
        558 }
        559 }
        560 }
        561 return false;
        562};
        563
        564
        565/**
        566 * Advances the iterator to the next position, returning the given default value
        567 * instead of throwing an exception if the iterator has no more entries.
        568 * @param {goog.iter.Iterable} iterable The iterable object.
        569 * @param {*} defaultValue The value to return if the iterator is empty.
        570 * @return {*} The next item in the iteration, or defaultValue if the iterator
        571 * was empty.
        572 */
        573goog.iter.nextOrValue = function(iterable, defaultValue) {
        574 try {
        575 return goog.iter.toIterator(iterable).next();
        576 } catch (e) {
        577 if (e != goog.iter.StopIteration) {
        578 throw e;
        579 }
        580 return defaultValue;
        581 }
        582};
        583
        584
        585/**
        586 * Cartesian product of zero or more sets. Gives an iterator that gives every
        587 * combination of one element chosen from each set. For example,
        588 * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
        589 * @see http://docs.python.org/library/itertools.html#itertools.product
        590 * @param {...!goog.array.ArrayLike.<*>} var_args Zero or more sets, as arrays.
        591 * @return {!goog.iter.Iterator} An iterator that gives each n-tuple (as an
        592 * array).
        593 */
        594goog.iter.product = function(var_args) {
        595 var someArrayEmpty = goog.array.some(arguments, function(arr) {
        596 return !arr.length;
        597 });
        598
        599 // An empty set in a cartesian product gives an empty set.
        600 if (someArrayEmpty || !arguments.length) {
        601 return new goog.iter.Iterator();
        602 }
        603
        604 var iter = new goog.iter.Iterator();
        605 var arrays = arguments;
        606
        607 // The first indicies are [0, 0, ...]
        608 var indicies = goog.array.repeat(0, arrays.length);
        609
        610 iter.next = function() {
        611
        612 if (indicies) {
        613 var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
        614 return arrays[arrayIndex][valueIndex];
        615 });
        616
        617 // Generate the next-largest indicies for the next call.
        618 // Increase the rightmost index. If it goes over, increase the next
        619 // rightmost (like carry-over addition).
        620 for (var i = indicies.length - 1; i >= 0; i--) {
        621 // Assertion prevents compiler warning below.
        622 goog.asserts.assert(indicies);
        623 if (indicies[i] < arrays[i].length - 1) {
        624 indicies[i]++;
        625 break;
        626 }
        627
        628 // We're at the last indicies (the last element of every array), so
        629 // the iteration is over on the next call.
        630 if (i == 0) {
        631 indicies = null;
        632 break;
        633 }
        634 // Reset the index in this column and loop back to increment the
        635 // next one.
        636 indicies[i] = 0;
        637 }
        638 return retVal;
        639 }
        640
        641 throw goog.iter.StopIteration;
        642 };
        643
        644 return iter;
        645};
        646
        647
        648/**
        649 * Create an iterator to cycle over the iterable's elements indefinitely.
        650 * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
        651 * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
        652 * @param {!goog.iter.Iterable} iterable The iterable object.
        653 * @return {!goog.iter.Iterator} An iterator that iterates indefinitely over
        654 * the values in {@code iterable}.
        655 */
        656goog.iter.cycle = function(iterable) {
        657
        658 var baseIterator = goog.iter.toIterator(iterable);
        659
        660 // We maintain a cache to store the iterable elements as we iterate
        661 // over them. The cache is used to return elements once we have
        662 // iterated over the iterable once.
        663 var cache = [];
        664 var cacheIndex = 0;
        665
        666 var iter = new goog.iter.Iterator();
        667
        668 // This flag is set after the iterable is iterated over once
        669 var useCache = false;
        670
        671 iter.next = function() {
        672 var returnElement = null;
        673
        674 // Pull elements off the original iterator if not using cache
        675 if (!useCache) {
        676 try {
        677 // Return the element from the iterable
        678 returnElement = baseIterator.next();
        679 cache.push(returnElement);
        680 return returnElement;
        681 } catch (e) {
        682 // If an exception other than StopIteration is thrown
        683 // or if there are no elements to iterate over (the iterable was empty)
        684 // throw an exception
        685 if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
        686 throw e;
        687 }
        688 // set useCache to true after we know that a 'StopIteration' exception
        689 // was thrown and the cache is not empty (to handle the 'empty iterable'
        690 // use case)
        691 useCache = true;
        692 }
        693 }
        694
        695 returnElement = cache[cacheIndex];
        696 cacheIndex = (cacheIndex + 1) % cache.length;
        697
        698 return returnElement;
        699 };
        700
        701 return iter;
        702};
        \ No newline at end of file +iter.js

        lib/goog/iter/iter.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Python style iteration utilities.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20
        21goog.provide('goog.iter');
        22goog.provide('goog.iter.Iterable');
        23goog.provide('goog.iter.Iterator');
        24goog.provide('goog.iter.StopIteration');
        25
        26goog.require('goog.array');
        27goog.require('goog.asserts');
        28goog.require('goog.functions');
        29goog.require('goog.math');
        30
        31
        32/**
        33 * @typedef {goog.iter.Iterator|{length:number}|{__iterator__}}
        34 */
        35goog.iter.Iterable;
        36
        37
        38/**
        39 * Singleton Error object that is used to terminate iterations.
        40 * @const {!Error}
        41 */
        42goog.iter.StopIteration = ('StopIteration' in goog.global) ?
        43 // For script engines that support legacy iterators.
        44 goog.global['StopIteration'] :
        45 { message: 'StopIteration', stack: ''};
        46
        47
        48
        49/**
        50 * Class/interface for iterators. An iterator needs to implement a {@code next}
        51 * method and it needs to throw a {@code goog.iter.StopIteration} when the
        52 * iteration passes beyond the end. Iterators have no {@code hasNext} method.
        53 * It is recommended to always use the helper functions to iterate over the
        54 * iterator or in case you are only targeting JavaScript 1.7 for in loops.
        55 * @constructor
        56 * @template VALUE
        57 */
        58goog.iter.Iterator = function() {};
        59
        60
        61/**
        62 * Returns the next value of the iteration. This will throw the object
        63 * {@see goog.iter#StopIteration} when the iteration passes the end.
        64 * @return {VALUE} Any object or value.
        65 */
        66goog.iter.Iterator.prototype.next = function() {
        67 throw goog.iter.StopIteration;
        68};
        69
        70
        71/**
        72 * Returns the {@code Iterator} object itself. This is used to implement
        73 * the iterator protocol in JavaScript 1.7
        74 * @param {boolean=} opt_keys Whether to return the keys or values. Default is
        75 * to only return the values. This is being used by the for-in loop (true)
        76 * and the for-each-in loop (false). Even though the param gives a hint
        77 * about what the iterator will return there is no guarantee that it will
        78 * return the keys when true is passed.
        79 * @return {!goog.iter.Iterator<VALUE>} The object itself.
        80 */
        81goog.iter.Iterator.prototype.__iterator__ = function(opt_keys) {
        82 return this;
        83};
        84
        85
        86/**
        87 * Returns an iterator that knows how to iterate over the values in the object.
        88 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable If the
        89 * object is an iterator it will be returned as is. If the object has an
        90 * {@code __iterator__} method that will be called to get the value
        91 * iterator. If the object is an array-like object we create an iterator
        92 * for that.
        93 * @return {!goog.iter.Iterator<VALUE>} An iterator that knows how to iterate
        94 * over the values in {@code iterable}.
        95 * @template VALUE
        96 */
        97goog.iter.toIterator = function(iterable) {
        98 if (iterable instanceof goog.iter.Iterator) {
        99 return iterable;
        100 }
        101 if (typeof iterable.__iterator__ == 'function') {
        102 return iterable.__iterator__(false);
        103 }
        104 if (goog.isArrayLike(iterable)) {
        105 var i = 0;
        106 var newIter = new goog.iter.Iterator;
        107 newIter.next = function() {
        108 while (true) {
        109 if (i >= iterable.length) {
        110 throw goog.iter.StopIteration;
        111 }
        112 // Don't include deleted elements.
        113 if (!(i in iterable)) {
        114 i++;
        115 continue;
        116 }
        117 return iterable[i++];
        118 }
        119 };
        120 return newIter;
        121 }
        122
        123
        124 // TODO(arv): Should we fall back on goog.structs.getValues()?
        125 throw Error('Not implemented');
        126};
        127
        128
        129/**
        130 * Calls a function for each element in the iterator with the element of the
        131 * iterator passed as argument.
        132 *
        133 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        134 * to iterate over. If the iterable is an object {@code toIterator} will be
        135 * called on it.
        136 * @param {function(this:THIS,VALUE,?,!goog.iter.Iterator<VALUE>)} f
        137 * The function to call for every element. This function takes 3 arguments
        138 * (the element, undefined, and the iterator) and the return value is
        139 * irrelevant. The reason for passing undefined as the second argument is
        140 * so that the same function can be used in {@see goog.array#forEach} as
        141 * well as others. The third parameter is of type "number" for
        142 * arraylike objects, undefined, otherwise.
        143 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        144 * {@code f}.
        145 * @template THIS, VALUE
        146 */
        147goog.iter.forEach = function(iterable, f, opt_obj) {
        148 if (goog.isArrayLike(iterable)) {
        149 /** @preserveTry */
        150 try {
        151 // NOTES: this passes the index number to the second parameter
        152 // of the callback contrary to the documentation above.
        153 goog.array.forEach(/** @type {goog.array.ArrayLike} */(iterable), f,
        154 opt_obj);
        155 } catch (ex) {
        156 if (ex !== goog.iter.StopIteration) {
        157 throw ex;
        158 }
        159 }
        160 } else {
        161 iterable = goog.iter.toIterator(iterable);
        162 /** @preserveTry */
        163 try {
        164 while (true) {
        165 f.call(opt_obj, iterable.next(), undefined, iterable);
        166 }
        167 } catch (ex) {
        168 if (ex !== goog.iter.StopIteration) {
        169 throw ex;
        170 }
        171 }
        172 }
        173};
        174
        175
        176/**
        177 * Calls a function for every element in the iterator, and if the function
        178 * returns true adds the element to a new iterator.
        179 *
        180 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        181 * to iterate over.
        182 * @param {
        183 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        184 * The function to call for every element. This function takes 3 arguments
        185 * (the element, undefined, and the iterator) and should return a boolean.
        186 * If the return value is true the element will be included in the returned
        187 * iterator. If it is false the element is not included.
        188 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        189 * {@code f}.
        190 * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements
        191 * that passed the test are present.
        192 * @template THIS, VALUE
        193 */
        194goog.iter.filter = function(iterable, f, opt_obj) {
        195 var iterator = goog.iter.toIterator(iterable);
        196 var newIter = new goog.iter.Iterator;
        197 newIter.next = function() {
        198 while (true) {
        199 var val = iterator.next();
        200 if (f.call(opt_obj, val, undefined, iterator)) {
        201 return val;
        202 }
        203 }
        204 };
        205 return newIter;
        206};
        207
        208
        209/**
        210 * Calls a function for every element in the iterator, and if the function
        211 * returns false adds the element to a new iterator.
        212 *
        213 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        214 * to iterate over.
        215 * @param {
        216 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        217 * The function to call for every element. This function takes 3 arguments
        218 * (the element, undefined, and the iterator) and should return a boolean.
        219 * If the return value is false the element will be included in the returned
        220 * iterator. If it is true the element is not included.
        221 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        222 * {@code f}.
        223 * @return {!goog.iter.Iterator<VALUE>} A new iterator in which only elements
        224 * that did not pass the test are present.
        225 * @template THIS, VALUE
        226 */
        227goog.iter.filterFalse = function(iterable, f, opt_obj) {
        228 return goog.iter.filter(iterable, goog.functions.not(f), opt_obj);
        229};
        230
        231
        232/**
        233 * Creates a new iterator that returns the values in a range. This function
        234 * can take 1, 2 or 3 arguments:
        235 * <pre>
        236 * range(5) same as range(0, 5, 1)
        237 * range(2, 5) same as range(2, 5, 1)
        238 * </pre>
        239 *
        240 * @param {number} startOrStop The stop value if only one argument is provided.
        241 * The start value if 2 or more arguments are provided. If only one
        242 * argument is used the start value is 0.
        243 * @param {number=} opt_stop The stop value. If left out then the first
        244 * argument is used as the stop value.
        245 * @param {number=} opt_step The number to increment with between each call to
        246 * next. This can be negative.
        247 * @return {!goog.iter.Iterator<number>} A new iterator that returns the values
        248 * in the range.
        249 */
        250goog.iter.range = function(startOrStop, opt_stop, opt_step) {
        251 var start = 0;
        252 var stop = startOrStop;
        253 var step = opt_step || 1;
        254 if (arguments.length > 1) {
        255 start = startOrStop;
        256 stop = opt_stop;
        257 }
        258 if (step == 0) {
        259 throw Error('Range step argument must not be zero');
        260 }
        261
        262 var newIter = new goog.iter.Iterator;
        263 newIter.next = function() {
        264 if (step > 0 && start >= stop || step < 0 && start <= stop) {
        265 throw goog.iter.StopIteration;
        266 }
        267 var rv = start;
        268 start += step;
        269 return rv;
        270 };
        271 return newIter;
        272};
        273
        274
        275/**
        276 * Joins the values in a iterator with a delimiter.
        277 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        278 * to get the values from.
        279 * @param {string} deliminator The text to put between the values.
        280 * @return {string} The joined value string.
        281 * @template VALUE
        282 */
        283goog.iter.join = function(iterable, deliminator) {
        284 return goog.iter.toArray(iterable).join(deliminator);
        285};
        286
        287
        288/**
        289 * For every element in the iterator call a function and return a new iterator
        290 * with that value.
        291 *
        292 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        293 * iterator to iterate over.
        294 * @param {
        295 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):RESULT} f
        296 * The function to call for every element. This function takes 3 arguments
        297 * (the element, undefined, and the iterator) and should return a new value.
        298 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        299 * {@code f}.
        300 * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the
        301 * results of applying the function to each element in the original
        302 * iterator.
        303 * @template THIS, VALUE, RESULT
        304 */
        305goog.iter.map = function(iterable, f, opt_obj) {
        306 var iterator = goog.iter.toIterator(iterable);
        307 var newIter = new goog.iter.Iterator;
        308 newIter.next = function() {
        309 var val = iterator.next();
        310 return f.call(opt_obj, val, undefined, iterator);
        311 };
        312 return newIter;
        313};
        314
        315
        316/**
        317 * Passes every element of an iterator into a function and accumulates the
        318 * result.
        319 *
        320 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        321 * to iterate over.
        322 * @param {function(this:THIS,VALUE,VALUE):VALUE} f The function to call for
        323 * every element. This function takes 2 arguments (the function's previous
        324 * result or the initial value, and the value of the current element).
        325 * function(previousValue, currentElement) : newValue.
        326 * @param {VALUE} val The initial value to pass into the function on the first
        327 * call.
        328 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        329 * f.
        330 * @return {VALUE} Result of evaluating f repeatedly across the values of
        331 * the iterator.
        332 * @template THIS, VALUE
        333 */
        334goog.iter.reduce = function(iterable, f, val, opt_obj) {
        335 var rval = val;
        336 goog.iter.forEach(iterable, function(val) {
        337 rval = f.call(opt_obj, rval, val);
        338 });
        339 return rval;
        340};
        341
        342
        343/**
        344 * Goes through the values in the iterator. Calls f for each of these, and if
        345 * any of them returns true, this returns true (without checking the rest). If
        346 * all return false this will return false.
        347 *
        348 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        349 * object.
        350 * @param {
        351 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        352 * The function to call for every value. This function takes 3 arguments
        353 * (the value, undefined, and the iterator) and should return a boolean.
        354 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        355 * {@code f}.
        356 * @return {boolean} true if any value passes the test.
        357 * @template THIS, VALUE
        358 */
        359goog.iter.some = function(iterable, f, opt_obj) {
        360 iterable = goog.iter.toIterator(iterable);
        361 /** @preserveTry */
        362 try {
        363 while (true) {
        364 if (f.call(opt_obj, iterable.next(), undefined, iterable)) {
        365 return true;
        366 }
        367 }
        368 } catch (ex) {
        369 if (ex !== goog.iter.StopIteration) {
        370 throw ex;
        371 }
        372 }
        373 return false;
        374};
        375
        376
        377/**
        378 * Goes through the values in the iterator. Calls f for each of these and if any
        379 * of them returns false this returns false (without checking the rest). If all
        380 * return true this will return true.
        381 *
        382 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        383 * object.
        384 * @param {
        385 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        386 * The function to call for every value. This function takes 3 arguments
        387 * (the value, undefined, and the iterator) and should return a boolean.
        388 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        389 * {@code f}.
        390 * @return {boolean} true if every value passes the test.
        391 * @template THIS, VALUE
        392 */
        393goog.iter.every = function(iterable, f, opt_obj) {
        394 iterable = goog.iter.toIterator(iterable);
        395 /** @preserveTry */
        396 try {
        397 while (true) {
        398 if (!f.call(opt_obj, iterable.next(), undefined, iterable)) {
        399 return false;
        400 }
        401 }
        402 } catch (ex) {
        403 if (ex !== goog.iter.StopIteration) {
        404 throw ex;
        405 }
        406 }
        407 return true;
        408};
        409
        410
        411/**
        412 * Takes zero or more iterables and returns one iterator that will iterate over
        413 * them in the order chained.
        414 * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
        415 * number of iterable objects.
        416 * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will
        417 * iterate over all the given iterables' contents.
        418 * @template VALUE
        419 */
        420goog.iter.chain = function(var_args) {
        421 return goog.iter.chainFromIterable(arguments);
        422};
        423
        424
        425/**
        426 * Takes a single iterable containing zero or more iterables and returns one
        427 * iterator that will iterate over each one in the order given.
        428 * @see http://docs.python.org/2/library/itertools.html#itertools.chain.from_iterable
        429 * @param {goog.iter.Iterable} iterable The iterable of iterables to chain.
        430 * @return {!goog.iter.Iterator<VALUE>} Returns a new iterator that will
        431 * iterate over all the contents of the iterables contained within
        432 * {@code iterable}.
        433 * @template VALUE
        434 */
        435goog.iter.chainFromIterable = function(iterable) {
        436 var iterator = goog.iter.toIterator(iterable);
        437 var iter = new goog.iter.Iterator();
        438 var current = null;
        439
        440 iter.next = function() {
        441 while (true) {
        442 if (current == null) {
        443 var it = iterator.next();
        444 current = goog.iter.toIterator(it);
        445 }
        446 try {
        447 return current.next();
        448 } catch (ex) {
        449 if (ex !== goog.iter.StopIteration) {
        450 throw ex;
        451 }
        452 current = null;
        453 }
        454 }
        455 };
        456
        457 return iter;
        458};
        459
        460
        461/**
        462 * Builds a new iterator that iterates over the original, but skips elements as
        463 * long as a supplied function returns true.
        464 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        465 * object.
        466 * @param {
        467 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        468 * The function to call for every value. This function takes 3 arguments
        469 * (the value, undefined, and the iterator) and should return a boolean.
        470 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        471 * {@code f}.
        472 * @return {!goog.iter.Iterator<VALUE>} A new iterator that drops elements from
        473 * the original iterator as long as {@code f} is true.
        474 * @template THIS, VALUE
        475 */
        476goog.iter.dropWhile = function(iterable, f, opt_obj) {
        477 var iterator = goog.iter.toIterator(iterable);
        478 var newIter = new goog.iter.Iterator;
        479 var dropping = true;
        480 newIter.next = function() {
        481 while (true) {
        482 var val = iterator.next();
        483 if (dropping && f.call(opt_obj, val, undefined, iterator)) {
        484 continue;
        485 } else {
        486 dropping = false;
        487 }
        488 return val;
        489 }
        490 };
        491 return newIter;
        492};
        493
        494
        495/**
        496 * Builds a new iterator that iterates over the original, but only as long as a
        497 * supplied function returns true.
        498 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        499 * object.
        500 * @param {
        501 * function(this:THIS,VALUE,undefined,!goog.iter.Iterator<VALUE>):boolean} f
        502 * The function to call for every value. This function takes 3 arguments
        503 * (the value, undefined, and the iterator) and should return a boolean.
        504 * @param {THIS=} opt_obj This is used as the 'this' object in f when called.
        505 * @return {!goog.iter.Iterator<VALUE>} A new iterator that keeps elements in
        506 * the original iterator as long as the function is true.
        507 * @template THIS, VALUE
        508 */
        509goog.iter.takeWhile = function(iterable, f, opt_obj) {
        510 var iterator = goog.iter.toIterator(iterable);
        511 var iter = new goog.iter.Iterator();
        512 iter.next = function() {
        513 var val = iterator.next();
        514 if (f.call(opt_obj, val, undefined, iterator)) {
        515 return val;
        516 }
        517 throw goog.iter.StopIteration;
        518 };
        519 return iter;
        520};
        521
        522
        523/**
        524 * Converts the iterator to an array
        525 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterator
        526 * to convert to an array.
        527 * @return {!Array<VALUE>} An array of the elements the iterator iterates over.
        528 * @template VALUE
        529 */
        530goog.iter.toArray = function(iterable) {
        531 // Fast path for array-like.
        532 if (goog.isArrayLike(iterable)) {
        533 return goog.array.toArray(/** @type {!goog.array.ArrayLike} */(iterable));
        534 }
        535 iterable = goog.iter.toIterator(iterable);
        536 var array = [];
        537 goog.iter.forEach(iterable, function(val) {
        538 array.push(val);
        539 });
        540 return array;
        541};
        542
        543
        544/**
        545 * Iterates over two iterables and returns true if they contain the same
        546 * sequence of elements and have the same length.
        547 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable1 The first
        548 * iterable object.
        549 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable2 The second
        550 * iterable object.
        551 * @param {function(VALUE,VALUE):boolean=} opt_equalsFn Optional comparison
        552 * function.
        553 * Should take two arguments to compare, and return true if the arguments
        554 * are equal. Defaults to {@link goog.array.defaultCompareEquality} which
        555 * compares the elements using the built-in '===' operator.
        556 * @return {boolean} true if the iterables contain the same sequence of elements
        557 * and have the same length.
        558 * @template VALUE
        559 */
        560goog.iter.equals = function(iterable1, iterable2, opt_equalsFn) {
        561 var fillValue = {};
        562 var pairs = goog.iter.zipLongest(fillValue, iterable1, iterable2);
        563 var equalsFn = opt_equalsFn || goog.array.defaultCompareEquality;
        564 return goog.iter.every(pairs, function(pair) {
        565 return equalsFn(pair[0], pair[1]);
        566 });
        567};
        568
        569
        570/**
        571 * Advances the iterator to the next position, returning the given default value
        572 * instead of throwing an exception if the iterator has no more entries.
        573 * @param {goog.iter.Iterator<VALUE>|goog.iter.Iterable} iterable The iterable
        574 * object.
        575 * @param {VALUE} defaultValue The value to return if the iterator is empty.
        576 * @return {VALUE} The next item in the iteration, or defaultValue if the
        577 * iterator was empty.
        578 * @template VALUE
        579 */
        580goog.iter.nextOrValue = function(iterable, defaultValue) {
        581 try {
        582 return goog.iter.toIterator(iterable).next();
        583 } catch (e) {
        584 if (e != goog.iter.StopIteration) {
        585 throw e;
        586 }
        587 return defaultValue;
        588 }
        589};
        590
        591
        592/**
        593 * Cartesian product of zero or more sets. Gives an iterator that gives every
        594 * combination of one element chosen from each set. For example,
        595 * ([1, 2], [3, 4]) gives ([1, 3], [1, 4], [2, 3], [2, 4]).
        596 * @see http://docs.python.org/library/itertools.html#itertools.product
        597 * @param {...!goog.array.ArrayLike<VALUE>} var_args Zero or more sets, as
        598 * arrays.
        599 * @return {!goog.iter.Iterator<!Array<VALUE>>} An iterator that gives each
        600 * n-tuple (as an array).
        601 * @template VALUE
        602 */
        603goog.iter.product = function(var_args) {
        604 var someArrayEmpty = goog.array.some(arguments, function(arr) {
        605 return !arr.length;
        606 });
        607
        608 // An empty set in a cartesian product gives an empty set.
        609 if (someArrayEmpty || !arguments.length) {
        610 return new goog.iter.Iterator();
        611 }
        612
        613 var iter = new goog.iter.Iterator();
        614 var arrays = arguments;
        615
        616 // The first indices are [0, 0, ...]
        617 var indicies = goog.array.repeat(0, arrays.length);
        618
        619 iter.next = function() {
        620
        621 if (indicies) {
        622 var retVal = goog.array.map(indicies, function(valueIndex, arrayIndex) {
        623 return arrays[arrayIndex][valueIndex];
        624 });
        625
        626 // Generate the next-largest indices for the next call.
        627 // Increase the rightmost index. If it goes over, increase the next
        628 // rightmost (like carry-over addition).
        629 for (var i = indicies.length - 1; i >= 0; i--) {
        630 // Assertion prevents compiler warning below.
        631 goog.asserts.assert(indicies);
        632 if (indicies[i] < arrays[i].length - 1) {
        633 indicies[i]++;
        634 break;
        635 }
        636
        637 // We're at the last indices (the last element of every array), so
        638 // the iteration is over on the next call.
        639 if (i == 0) {
        640 indicies = null;
        641 break;
        642 }
        643 // Reset the index in this column and loop back to increment the
        644 // next one.
        645 indicies[i] = 0;
        646 }
        647 return retVal;
        648 }
        649
        650 throw goog.iter.StopIteration;
        651 };
        652
        653 return iter;
        654};
        655
        656
        657/**
        658 * Create an iterator to cycle over the iterable's elements indefinitely.
        659 * For example, ([1, 2, 3]) would return : 1, 2, 3, 1, 2, 3, ...
        660 * @see: http://docs.python.org/library/itertools.html#itertools.cycle.
        661 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        662 * iterable object.
        663 * @return {!goog.iter.Iterator<VALUE>} An iterator that iterates indefinitely
        664 * over the values in {@code iterable}.
        665 * @template VALUE
        666 */
        667goog.iter.cycle = function(iterable) {
        668 var baseIterator = goog.iter.toIterator(iterable);
        669
        670 // We maintain a cache to store the iterable elements as we iterate
        671 // over them. The cache is used to return elements once we have
        672 // iterated over the iterable once.
        673 var cache = [];
        674 var cacheIndex = 0;
        675
        676 var iter = new goog.iter.Iterator();
        677
        678 // This flag is set after the iterable is iterated over once
        679 var useCache = false;
        680
        681 iter.next = function() {
        682 var returnElement = null;
        683
        684 // Pull elements off the original iterator if not using cache
        685 if (!useCache) {
        686 try {
        687 // Return the element from the iterable
        688 returnElement = baseIterator.next();
        689 cache.push(returnElement);
        690 return returnElement;
        691 } catch (e) {
        692 // If an exception other than StopIteration is thrown
        693 // or if there are no elements to iterate over (the iterable was empty)
        694 // throw an exception
        695 if (e != goog.iter.StopIteration || goog.array.isEmpty(cache)) {
        696 throw e;
        697 }
        698 // set useCache to true after we know that a 'StopIteration' exception
        699 // was thrown and the cache is not empty (to handle the 'empty iterable'
        700 // use case)
        701 useCache = true;
        702 }
        703 }
        704
        705 returnElement = cache[cacheIndex];
        706 cacheIndex = (cacheIndex + 1) % cache.length;
        707
        708 return returnElement;
        709 };
        710
        711 return iter;
        712};
        713
        714
        715/**
        716 * Creates an iterator that counts indefinitely from a starting value.
        717 * @see http://docs.python.org/2/library/itertools.html#itertools.count
        718 * @param {number=} opt_start The starting value. Default is 0.
        719 * @param {number=} opt_step The number to increment with between each call to
        720 * next. Negative and floating point numbers are allowed. Default is 1.
        721 * @return {!goog.iter.Iterator<number>} A new iterator that returns the values
        722 * in the series.
        723 */
        724goog.iter.count = function(opt_start, opt_step) {
        725 var counter = opt_start || 0;
        726 var step = goog.isDef(opt_step) ? opt_step : 1;
        727 var iter = new goog.iter.Iterator();
        728
        729 iter.next = function() {
        730 var returnValue = counter;
        731 counter += step;
        732 return returnValue;
        733 };
        734
        735 return iter;
        736};
        737
        738
        739/**
        740 * Creates an iterator that returns the same object or value repeatedly.
        741 * @param {VALUE} value Any object or value to repeat.
        742 * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the
        743 * repeated value.
        744 * @template VALUE
        745 */
        746goog.iter.repeat = function(value) {
        747 var iter = new goog.iter.Iterator();
        748
        749 iter.next = goog.functions.constant(value);
        750
        751 return iter;
        752};
        753
        754
        755/**
        756 * Creates an iterator that returns running totals from the numbers in
        757 * {@code iterable}. For example, the array {@code [1, 2, 3, 4, 5]} yields
        758 * {@code 1 -> 3 -> 6 -> 10 -> 15}.
        759 * @see http://docs.python.org/3.2/library/itertools.html#itertools.accumulate
        760 * @param {!goog.iter.Iterable<number>} iterable The iterable of numbers to
        761 * accumulate.
        762 * @return {!goog.iter.Iterator<number>} A new iterator that returns the
        763 * numbers in the series.
        764 */
        765goog.iter.accumulate = function(iterable) {
        766 var iterator = goog.iter.toIterator(iterable);
        767 var total = 0;
        768 var iter = new goog.iter.Iterator();
        769
        770 iter.next = function() {
        771 total += iterator.next();
        772 return total;
        773 };
        774
        775 return iter;
        776};
        777
        778
        779/**
        780 * Creates an iterator that returns arrays containing the ith elements from the
        781 * provided iterables. The returned arrays will be the same size as the number
        782 * of iterables given in {@code var_args}. Once the shortest iterable is
        783 * exhausted, subsequent calls to {@code next()} will throw
        784 * {@code goog.iter.StopIteration}.
        785 * @see http://docs.python.org/2/library/itertools.html#itertools.izip
        786 * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
        787 * number of iterable objects.
        788 * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns
        789 * arrays of elements from the provided iterables.
        790 * @template VALUE
        791 */
        792goog.iter.zip = function(var_args) {
        793 var args = arguments;
        794 var iter = new goog.iter.Iterator();
        795
        796 if (args.length > 0) {
        797 var iterators = goog.array.map(args, goog.iter.toIterator);
        798 iter.next = function() {
        799 var arr = goog.array.map(iterators, function(it) {
        800 return it.next();
        801 });
        802 return arr;
        803 };
        804 }
        805
        806 return iter;
        807};
        808
        809
        810/**
        811 * Creates an iterator that returns arrays containing the ith elements from the
        812 * provided iterables. The returned arrays will be the same size as the number
        813 * of iterables given in {@code var_args}. Shorter iterables will be extended
        814 * with {@code fillValue}. Once the longest iterable is exhausted, subsequent
        815 * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
        816 * @see http://docs.python.org/2/library/itertools.html#itertools.izip_longest
        817 * @param {VALUE} fillValue The object or value used to fill shorter iterables.
        818 * @param {...!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} var_args Any
        819 * number of iterable objects.
        820 * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator that returns
        821 * arrays of elements from the provided iterables.
        822 * @template VALUE
        823 */
        824goog.iter.zipLongest = function(fillValue, var_args) {
        825 var args = goog.array.slice(arguments, 1);
        826 var iter = new goog.iter.Iterator();
        827
        828 if (args.length > 0) {
        829 var iterators = goog.array.map(args, goog.iter.toIterator);
        830
        831 iter.next = function() {
        832 var iteratorsHaveValues = false; // false when all iterators are empty.
        833 var arr = goog.array.map(iterators, function(it) {
        834 var returnValue;
        835 try {
        836 returnValue = it.next();
        837 // Iterator had a value, so we've not exhausted the iterators.
        838 // Set flag accordingly.
        839 iteratorsHaveValues = true;
        840 } catch (ex) {
        841 if (ex !== goog.iter.StopIteration) {
        842 throw ex;
        843 }
        844 returnValue = fillValue;
        845 }
        846 return returnValue;
        847 });
        848
        849 if (!iteratorsHaveValues) {
        850 throw goog.iter.StopIteration;
        851 }
        852 return arr;
        853 };
        854 }
        855
        856 return iter;
        857};
        858
        859
        860/**
        861 * Creates an iterator that filters {@code iterable} based on a series of
        862 * {@code selectors}. On each call to {@code next()}, one item is taken from
        863 * both the {@code iterable} and {@code selectors} iterators. If the item from
        864 * {@code selectors} evaluates to true, the item from {@code iterable} is given.
        865 * Otherwise, it is skipped. Once either {@code iterable} or {@code selectors}
        866 * is exhausted, subsequent calls to {@code next()} will throw
        867 * {@code goog.iter.StopIteration}.
        868 * @see http://docs.python.org/2/library/itertools.html#itertools.compress
        869 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        870 * iterable to filter.
        871 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} selectors An
        872 * iterable of items to be evaluated in a boolean context to determine if
        873 * the corresponding element in {@code iterable} should be included in the
        874 * result.
        875 * @return {!goog.iter.Iterator<VALUE>} A new iterator that returns the
        876 * filtered values.
        877 * @template VALUE
        878 */
        879goog.iter.compress = function(iterable, selectors) {
        880 var selectorIterator = goog.iter.toIterator(selectors);
        881
        882 return goog.iter.filter(iterable, function() {
        883 return !!selectorIterator.next();
        884 });
        885};
        886
        887
        888
        889/**
        890 * Implements the {@code goog.iter.groupBy} iterator.
        891 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        892 * iterable to group.
        893 * @param {function(...VALUE): KEY=} opt_keyFunc Optional function for
        894 * determining the key value for each group in the {@code iterable}. Default
        895 * is the identity function.
        896 * @constructor
        897 * @extends {goog.iter.Iterator<!Array<?>>}
        898 * @template KEY, VALUE
        899 * @private
        900 */
        901goog.iter.GroupByIterator_ = function(iterable, opt_keyFunc) {
        902
        903 /**
        904 * The iterable to group, coerced to an iterator.
        905 * @type {!goog.iter.Iterator}
        906 */
        907 this.iterator = goog.iter.toIterator(iterable);
        908
        909 /**
        910 * A function for determining the key value for each element in the iterable.
        911 * If no function is provided, the identity function is used and returns the
        912 * element unchanged.
        913 * @type {function(...VALUE): KEY}
        914 */
        915 this.keyFunc = opt_keyFunc || goog.functions.identity;
        916
        917 /**
        918 * The target key for determining the start of a group.
        919 * @type {KEY}
        920 */
        921 this.targetKey;
        922
        923 /**
        924 * The current key visited during iteration.
        925 * @type {KEY}
        926 */
        927 this.currentKey;
        928
        929 /**
        930 * The current value being added to the group.
        931 * @type {VALUE}
        932 */
        933 this.currentValue;
        934};
        935goog.inherits(goog.iter.GroupByIterator_, goog.iter.Iterator);
        936
        937
        938/** @override */
        939goog.iter.GroupByIterator_.prototype.next = function() {
        940 while (this.currentKey == this.targetKey) {
        941 this.currentValue = this.iterator.next(); // Exits on StopIteration
        942 this.currentKey = this.keyFunc(this.currentValue);
        943 }
        944 this.targetKey = this.currentKey;
        945 return [this.currentKey, this.groupItems_(this.targetKey)];
        946};
        947
        948
        949/**
        950 * Performs the grouping of objects using the given key.
        951 * @param {KEY} targetKey The target key object for the group.
        952 * @return {!Array<VALUE>} An array of grouped objects.
        953 * @private
        954 */
        955goog.iter.GroupByIterator_.prototype.groupItems_ = function(targetKey) {
        956 var arr = [];
        957 while (this.currentKey == targetKey) {
        958 arr.push(this.currentValue);
        959 try {
        960 this.currentValue = this.iterator.next();
        961 } catch (ex) {
        962 if (ex !== goog.iter.StopIteration) {
        963 throw ex;
        964 }
        965 break;
        966 }
        967 this.currentKey = this.keyFunc(this.currentValue);
        968 }
        969 return arr;
        970};
        971
        972
        973/**
        974 * Creates an iterator that returns arrays containing elements from the
        975 * {@code iterable} grouped by a key value. For iterables with repeated
        976 * elements (i.e. sorted according to a particular key function), this function
        977 * has a {@code uniq}-like effect. For example, grouping the array:
        978 * {@code [A, B, B, C, C, A]} produces
        979 * {@code [A, [A]], [B, [B, B]], [C, [C, C]], [A, [A]]}.
        980 * @see http://docs.python.org/2/library/itertools.html#itertools.groupby
        981 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        982 * iterable to group.
        983 * @param {function(...VALUE): KEY=} opt_keyFunc Optional function for
        984 * determining the key value for each group in the {@code iterable}. Default
        985 * is the identity function.
        986 * @return {!goog.iter.Iterator<!Array<?>>} A new iterator that returns
        987 * arrays of consecutive key and groups.
        988 * @template KEY, VALUE
        989 */
        990goog.iter.groupBy = function(iterable, opt_keyFunc) {
        991 return new goog.iter.GroupByIterator_(iterable, opt_keyFunc);
        992};
        993
        994
        995/**
        996 * Gives an iterator that gives the result of calling the given function
        997 * <code>f</code> with the arguments taken from the next element from
        998 * <code>iterable</code> (the elements are expected to also be iterables).
        999 *
        1000 * Similar to {@see goog.iter#map} but allows the function to accept multiple
        1001 * arguments from the iterable.
        1002 *
        1003 * @param {!goog.iter.Iterable<!goog.iter.Iterable>} iterable The iterable of
        1004 * iterables to iterate over.
        1005 * @param {function(this:THIS,...*):RESULT} f The function to call for every
        1006 * element. This function takes N+2 arguments, where N represents the
        1007 * number of items from the next element of the iterable. The two
        1008 * additional arguments passed to the function are undefined and the
        1009 * iterator itself. The function should return a new value.
        1010 * @param {THIS=} opt_obj The object to be used as the value of 'this' within
        1011 * {@code f}.
        1012 * @return {!goog.iter.Iterator<RESULT>} A new iterator that returns the
        1013 * results of applying the function to each element in the original
        1014 * iterator.
        1015 * @template THIS, RESULT
        1016 */
        1017goog.iter.starMap = function(iterable, f, opt_obj) {
        1018 var iterator = goog.iter.toIterator(iterable);
        1019 var iter = new goog.iter.Iterator();
        1020
        1021 iter.next = function() {
        1022 var args = goog.iter.toArray(iterator.next());
        1023 return f.apply(opt_obj, goog.array.concat(args, undefined, iterator));
        1024 };
        1025
        1026 return iter;
        1027};
        1028
        1029
        1030/**
        1031 * Returns an array of iterators each of which can iterate over the values in
        1032 * {@code iterable} without advancing the others.
        1033 * @see http://docs.python.org/2/library/itertools.html#itertools.tee
        1034 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1035 * iterable to tee.
        1036 * @param {number=} opt_num The number of iterators to create. Default is 2.
        1037 * @return {!Array<goog.iter.Iterator<VALUE>>} An array of iterators.
        1038 * @template VALUE
        1039 */
        1040goog.iter.tee = function(iterable, opt_num) {
        1041 var iterator = goog.iter.toIterator(iterable);
        1042 var num = goog.isNumber(opt_num) ? opt_num : 2;
        1043 var buffers = goog.array.map(goog.array.range(num), function() {
        1044 return [];
        1045 });
        1046
        1047 var addNextIteratorValueToBuffers = function() {
        1048 var val = iterator.next();
        1049 goog.array.forEach(buffers, function(buffer) {
        1050 buffer.push(val);
        1051 });
        1052 };
        1053
        1054 var createIterator = function(buffer) {
        1055 // Each tee'd iterator has an associated buffer (initially empty). When a
        1056 // tee'd iterator's buffer is empty, it calls
        1057 // addNextIteratorValueToBuffers(), adding the next value to all tee'd
        1058 // iterators' buffers, and then returns that value. This allows each
        1059 // iterator to be advanced independently.
        1060 var iter = new goog.iter.Iterator();
        1061
        1062 iter.next = function() {
        1063 if (goog.array.isEmpty(buffer)) {
        1064 addNextIteratorValueToBuffers();
        1065 }
        1066 goog.asserts.assert(!goog.array.isEmpty(buffer));
        1067 return buffer.shift();
        1068 };
        1069
        1070 return iter;
        1071 };
        1072
        1073 return goog.array.map(buffers, createIterator);
        1074};
        1075
        1076
        1077/**
        1078 * Creates an iterator that returns arrays containing a count and an element
        1079 * obtained from the given {@code iterable}.
        1080 * @see http://docs.python.org/2/library/functions.html#enumerate
        1081 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1082 * iterable to enumerate.
        1083 * @param {number=} opt_start Optional starting value. Default is 0.
        1084 * @return {!goog.iter.Iterator<!Array<?>>} A new iterator containing
        1085 * count/item pairs.
        1086 * @template VALUE
        1087 */
        1088goog.iter.enumerate = function(iterable, opt_start) {
        1089 return goog.iter.zip(goog.iter.count(opt_start), iterable);
        1090};
        1091
        1092
        1093/**
        1094 * Creates an iterator that returns the first {@code limitSize} elements from an
        1095 * iterable. If this number is greater than the number of elements in the
        1096 * iterable, all the elements are returned.
        1097 * @see http://goo.gl/V0sihp Inspired by the limit iterator in Guava.
        1098 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1099 * iterable to limit.
        1100 * @param {number} limitSize The maximum number of elements to return.
        1101 * @return {!goog.iter.Iterator<VALUE>} A new iterator containing
        1102 * {@code limitSize} elements.
        1103 * @template VALUE
        1104 */
        1105goog.iter.limit = function(iterable, limitSize) {
        1106 goog.asserts.assert(goog.math.isInt(limitSize) && limitSize >= 0);
        1107
        1108 var iterator = goog.iter.toIterator(iterable);
        1109
        1110 var iter = new goog.iter.Iterator();
        1111 var remaining = limitSize;
        1112
        1113 iter.next = function() {
        1114 if (remaining-- > 0) {
        1115 return iterator.next();
        1116 }
        1117 throw goog.iter.StopIteration;
        1118 };
        1119
        1120 return iter;
        1121};
        1122
        1123
        1124/**
        1125 * Creates an iterator that is advanced {@code count} steps ahead. Consumed
        1126 * values are silently discarded. If {@code count} is greater than the number
        1127 * of elements in {@code iterable}, an empty iterator is returned. Subsequent
        1128 * calls to {@code next()} will throw {@code goog.iter.StopIteration}.
        1129 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1130 * iterable to consume.
        1131 * @param {number} count The number of elements to consume from the iterator.
        1132 * @return {!goog.iter.Iterator<VALUE>} An iterator advanced zero or more steps
        1133 * ahead.
        1134 * @template VALUE
        1135 */
        1136goog.iter.consume = function(iterable, count) {
        1137 goog.asserts.assert(goog.math.isInt(count) && count >= 0);
        1138
        1139 var iterator = goog.iter.toIterator(iterable);
        1140
        1141 while (count-- > 0) {
        1142 goog.iter.nextOrValue(iterator, null);
        1143 }
        1144
        1145 return iterator;
        1146};
        1147
        1148
        1149/**
        1150 * Creates an iterator that returns a range of elements from an iterable.
        1151 * Similar to {@see goog.array#slice} but does not support negative indexes.
        1152 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1153 * iterable to slice.
        1154 * @param {number} start The index of the first element to return.
        1155 * @param {number=} opt_end The index after the last element to return. If
        1156 * defined, must be greater than or equal to {@code start}.
        1157 * @return {!goog.iter.Iterator<VALUE>} A new iterator containing a slice of
        1158 * the original.
        1159 * @template VALUE
        1160 */
        1161goog.iter.slice = function(iterable, start, opt_end) {
        1162 goog.asserts.assert(goog.math.isInt(start) && start >= 0);
        1163
        1164 var iterator = goog.iter.consume(iterable, start);
        1165
        1166 if (goog.isNumber(opt_end)) {
        1167 goog.asserts.assert(
        1168 goog.math.isInt(/** @type {number} */ (opt_end)) && opt_end >= start);
        1169 iterator = goog.iter.limit(iterator, opt_end - start /* limitSize */);
        1170 }
        1171
        1172 return iterator;
        1173};
        1174
        1175
        1176/**
        1177 * Checks an array for duplicate elements.
        1178 * @param {Array<VALUE>|goog.array.ArrayLike} arr The array to check for
        1179 * duplicates.
        1180 * @return {boolean} True, if the array contains duplicates, false otherwise.
        1181 * @private
        1182 * @template VALUE
        1183 */
        1184// TODO(user): Consider moving this into goog.array as a public function.
        1185goog.iter.hasDuplicates_ = function(arr) {
        1186 var deduped = [];
        1187 goog.array.removeDuplicates(arr, deduped);
        1188 return arr.length != deduped.length;
        1189};
        1190
        1191
        1192/**
        1193 * Creates an iterator that returns permutations of elements in
        1194 * {@code iterable}.
        1195 *
        1196 * Permutations are obtained by taking the Cartesian product of
        1197 * {@code opt_length} iterables and filtering out those with repeated
        1198 * elements. For example, the permutations of {@code [1,2,3]} are
        1199 * {@code [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]}.
        1200 * @see http://docs.python.org/2/library/itertools.html#itertools.permutations
        1201 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1202 * iterable from which to generate permutations.
        1203 * @param {number=} opt_length Length of each permutation. If omitted, defaults
        1204 * to the length of {@code iterable}.
        1205 * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing the
        1206 * permutations of {@code iterable}.
        1207 * @template VALUE
        1208 */
        1209goog.iter.permutations = function(iterable, opt_length) {
        1210 var elements = goog.iter.toArray(iterable);
        1211 var length = goog.isNumber(opt_length) ? opt_length : elements.length;
        1212
        1213 var sets = goog.array.repeat(elements, length);
        1214 var product = goog.iter.product.apply(undefined, sets);
        1215
        1216 return goog.iter.filter(product, function(arr) {
        1217 return !goog.iter.hasDuplicates_(arr);
        1218 });
        1219};
        1220
        1221
        1222/**
        1223 * Creates an iterator that returns combinations of elements from
        1224 * {@code iterable}.
        1225 *
        1226 * Combinations are obtained by taking the {@see goog.iter#permutations} of
        1227 * {@code iterable} and filtering those whose elements appear in the order they
        1228 * are encountered in {@code iterable}. For example, the 3-length combinations
        1229 * of {@code [0,1,2,3]} are {@code [[0,1,2], [0,1,3], [0,2,3], [1,2,3]]}.
        1230 * @see http://docs.python.org/2/library/itertools.html#itertools.combinations
        1231 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1232 * iterable from which to generate combinations.
        1233 * @param {number} length The length of each combination.
        1234 * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing
        1235 * combinations from the {@code iterable}.
        1236 * @template VALUE
        1237 */
        1238goog.iter.combinations = function(iterable, length) {
        1239 var elements = goog.iter.toArray(iterable);
        1240 var indexes = goog.iter.range(elements.length);
        1241 var indexIterator = goog.iter.permutations(indexes, length);
        1242 // sortedIndexIterator will now give arrays of with the given length that
        1243 // indicate what indexes into "elements" should be returned on each iteration.
        1244 var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
        1245 return goog.array.isSorted(arr);
        1246 });
        1247
        1248 var iter = new goog.iter.Iterator();
        1249
        1250 function getIndexFromElements(index) {
        1251 return elements[index];
        1252 }
        1253
        1254 iter.next = function() {
        1255 return goog.array.map(
        1256 /** @type {!Array<number>} */
        1257 (sortedIndexIterator.next()), getIndexFromElements);
        1258 };
        1259
        1260 return iter;
        1261};
        1262
        1263
        1264/**
        1265 * Creates an iterator that returns combinations of elements from
        1266 * {@code iterable}, with repeated elements possible.
        1267 *
        1268 * Combinations are obtained by taking the Cartesian product of {@code length}
        1269 * iterables and filtering those whose elements appear in the order they are
        1270 * encountered in {@code iterable}. For example, the 2-length combinations of
        1271 * {@code [1,2,3]} are {@code [[1,1], [1,2], [1,3], [2,2], [2,3], [3,3]]}.
        1272 * @see http://docs.python.org/2/library/itertools.html#itertools.combinations_with_replacement
        1273 * @see http://en.wikipedia.org/wiki/Combination#Number_of_combinations_with_repetition
        1274 * @param {!goog.iter.Iterator<VALUE>|!goog.iter.Iterable} iterable The
        1275 * iterable to combine.
        1276 * @param {number} length The length of each combination.
        1277 * @return {!goog.iter.Iterator<!Array<VALUE>>} A new iterator containing
        1278 * combinations from the {@code iterable}.
        1279 * @template VALUE
        1280 */
        1281goog.iter.combinationsWithReplacement = function(iterable, length) {
        1282 var elements = goog.iter.toArray(iterable);
        1283 var indexes = goog.array.range(elements.length);
        1284 var sets = goog.array.repeat(indexes, length);
        1285 var indexIterator = goog.iter.product.apply(undefined, sets);
        1286 // sortedIndexIterator will now give arrays of with the given length that
        1287 // indicate what indexes into "elements" should be returned on each iteration.
        1288 var sortedIndexIterator = goog.iter.filter(indexIterator, function(arr) {
        1289 return goog.array.isSorted(arr);
        1290 });
        1291
        1292 var iter = new goog.iter.Iterator();
        1293
        1294 function getIndexFromElements(index) {
        1295 return elements[index];
        1296 }
        1297
        1298 iter.next = function() {
        1299 return goog.array.map(
        1300 /** @type {!Array<number>} */
        1301 (sortedIndexIterator.next()), getIndexFromElements);
        1302 };
        1303
        1304 return iter;
        1305};
        \ No newline at end of file diff --git a/docs/source/lib/goog/json/json.js.src.html b/docs/source/lib/goog/json/json.js.src.html index 3b84a07..86b8db8 100644 --- a/docs/source/lib/goog/json/json.js.src.html +++ b/docs/source/lib/goog/json/json.js.src.html @@ -1 +1 @@ -json.js

        lib/goog/json/json.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview JSON utility functions.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20
        21goog.provide('goog.json');
        22goog.provide('goog.json.Serializer');
        23
        24
        25/**
        26 * Tests if a string is an invalid JSON string. This only ensures that we are
        27 * not using any invalid characters
        28 * @param {string} s The string to test.
        29 * @return {boolean} True if the input is a valid JSON string.
        30 * @private
        31 */
        32goog.json.isValid_ = function(s) {
        33 // All empty whitespace is not valid.
        34 if (/^\s*$/.test(s)) {
        35 return false;
        36 }
        37
        38 // This is taken from http://www.json.org/json2.js which is released to the
        39 // public domain.
        40 // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
        41 // inside strings. We also treat \u2028 and \u2029 as whitespace which they
        42 // are in the RFC but IE and Safari does not match \s to these so we need to
        43 // include them in the reg exps in all places where whitespace is allowed.
        44 // We allowed \x7f inside strings because some tools don't escape it,
        45 // e.g. http://www.json.org/java/org/json/JSONObject.java
        46
        47 // Parsing happens in three stages. In the first stage, we run the text
        48 // against regular expressions that look for non-JSON patterns. We are
        49 // especially concerned with '()' and 'new' because they can cause invocation,
        50 // and '=' because it can cause mutation. But just to be safe, we want to
        51 // reject all unexpected forms.
        52
        53 // We split the first stage into 4 regexp operations in order to work around
        54 // crippling inefficiencies in IE's and Safari's regexp engines. First we
        55 // replace all backslash pairs with '@' (a non-JSON character). Second, we
        56 // replace all simple value tokens with ']' characters. Third, we delete all
        57 // open brackets that follow a colon or comma or that begin the text. Finally,
        58 // we look to see that the remaining characters are only whitespace or ']' or
        59 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
        60
        61 // Don't make these static since they have the global flag.
        62 var backslashesRe = /\\["\\\/bfnrtu]/g;
        63 var simpleValuesRe =
        64 /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
        65 var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
        66 var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
        67
        68 return remainderRe.test(s.replace(backslashesRe, '@').
        69 replace(simpleValuesRe, ']').
        70 replace(openBracketsRe, ''));
        71};
        72
        73
        74/**
        75 * Parses a JSON string and returns the result. This throws an exception if
        76 * the string is an invalid JSON string.
        77 *
        78 * Note that this is very slow on large strings. If you trust the source of
        79 * the string then you should use unsafeParse instead.
        80 *
        81 * @param {*} s The JSON string to parse.
        82 * @return {Object} The object generated from the JSON string.
        83 */
        84goog.json.parse = function(s) {
        85 var o = String(s);
        86 if (goog.json.isValid_(o)) {
        87 /** @preserveTry */
        88 try {
        89 return /** @type {Object} */ (eval('(' + o + ')'));
        90 } catch (ex) {
        91 }
        92 }
        93 throw Error('Invalid JSON string: ' + o);
        94};
        95
        96
        97/**
        98 * Parses a JSON string and returns the result. This uses eval so it is open
        99 * to security issues and it should only be used if you trust the source.
        100 *
        101 * @param {string} s The JSON string to parse.
        102 * @return {Object} The object generated from the JSON string.
        103 */
        104goog.json.unsafeParse = function(s) {
        105 return /** @type {Object} */ (eval('(' + s + ')'));
        106};
        107
        108
        109/**
        110 * JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
        111 *
        112 * TODO(nicksantos): Array should also be a valid replacer.
        113 *
        114 * @typedef {function(this:Object, string, *): *}
        115 */
        116goog.json.Replacer;
        117
        118
        119/**
        120 * JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
        121 *
        122 * @typedef {function(this:Object, string, *): *}
        123 */
        124goog.json.Reviver;
        125
        126
        127/**
        128 * Serializes an object or a value to a JSON string.
        129 *
        130 * @param {*} object The object to serialize.
        131 * @param {?goog.json.Replacer=} opt_replacer A replacer function
        132 * called for each (key, value) pair that determines how the value
        133 * should be serialized. By defult, this just returns the value
        134 * and allows default serialization to kick in.
        135 * @throws Error if there are loops in the object graph.
        136 * @return {string} A JSON string representation of the input.
        137 */
        138goog.json.serialize = function(object, opt_replacer) {
        139 // NOTE(nicksantos): Currently, we never use JSON.stringify.
        140 //
        141 // The last time I evaluated this, JSON.stringify had subtle bugs and behavior
        142 // differences on all browsers, and the performance win was not large enough
        143 // to justify all the issues. This may change in the future as browser
        144 // implementations get better.
        145 //
        146 // assertSerialize in json_test contains if branches for the cases
        147 // that fail.
        148 return new goog.json.Serializer(opt_replacer).serialize(object);
        149};
        150
        151
        152
        153/**
        154 * Class that is used to serialize JSON objects to a string.
        155 * @param {?goog.json.Replacer=} opt_replacer Replacer.
        156 * @constructor
        157 */
        158goog.json.Serializer = function(opt_replacer) {
        159 /**
        160 * @type {goog.json.Replacer|null|undefined}
        161 * @private
        162 */
        163 this.replacer_ = opt_replacer;
        164};
        165
        166
        167/**
        168 * Serializes an object or a value to a JSON string.
        169 *
        170 * @param {*} object The object to serialize.
        171 * @throws Error if there are loops in the object graph.
        172 * @return {string} A JSON string representation of the input.
        173 */
        174goog.json.Serializer.prototype.serialize = function(object) {
        175 var sb = [];
        176 this.serialize_(object, sb);
        177 return sb.join('');
        178};
        179
        180
        181/**
        182 * Serializes a generic value to a JSON string
        183 * @private
        184 * @param {*} object The object to serialize.
        185 * @param {Array} sb Array used as a string builder.
        186 * @throws Error if there are loops in the object graph.
        187 */
        188goog.json.Serializer.prototype.serialize_ = function(object, sb) {
        189 switch (typeof object) {
        190 case 'string':
        191 this.serializeString_(/** @type {string} */ (object), sb);
        192 break;
        193 case 'number':
        194 this.serializeNumber_(/** @type {number} */ (object), sb);
        195 break;
        196 case 'boolean':
        197 sb.push(object);
        198 break;
        199 case 'undefined':
        200 sb.push('null');
        201 break;
        202 case 'object':
        203 if (object == null) {
        204 sb.push('null');
        205 break;
        206 }
        207 if (goog.isArray(object)) {
        208 this.serializeArray(/** @type {!Array} */ (object), sb);
        209 break;
        210 }
        211 // should we allow new String, new Number and new Boolean to be treated
        212 // as string, number and boolean? Most implementations do not and the
        213 // need is not very big
        214 this.serializeObject_(/** @type {Object} */ (object), sb);
        215 break;
        216 case 'function':
        217 // Skip functions.
        218 // TODO(user) Should we return something here?
        219 break;
        220 default:
        221 throw Error('Unknown type: ' + typeof object);
        222 }
        223};
        224
        225
        226/**
        227 * Character mappings used internally for goog.string.quote
        228 * @private
        229 * @type {Object}
        230 */
        231goog.json.Serializer.charToJsonCharCache_ = {
        232 '\"': '\\"',
        233 '\\': '\\\\',
        234 '/': '\\/',
        235 '\b': '\\b',
        236 '\f': '\\f',
        237 '\n': '\\n',
        238 '\r': '\\r',
        239 '\t': '\\t',
        240
        241 '\x0B': '\\u000b' // '\v' is not supported in JScript
        242};
        243
        244
        245/**
        246 * Regular expression used to match characters that need to be replaced.
        247 * The S60 browser has a bug where unicode characters are not matched by
        248 * regular expressions. The condition below detects such behaviour and
        249 * adjusts the regular expression accordingly.
        250 * @private
        251 * @type {RegExp}
        252 */
        253goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
        254 /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
        255
        256
        257/**
        258 * Serializes a string to a JSON string
        259 * @private
        260 * @param {string} s The string to serialize.
        261 * @param {Array} sb Array used as a string builder.
        262 */
        263goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
        264 // The official JSON implementation does not work with international
        265 // characters.
        266 sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
        267 // caching the result improves performance by a factor 2-3
        268 if (c in goog.json.Serializer.charToJsonCharCache_) {
        269 return goog.json.Serializer.charToJsonCharCache_[c];
        270 }
        271
        272 var cc = c.charCodeAt(0);
        273 var rv = '\\u';
        274 if (cc < 16) {
        275 rv += '000';
        276 } else if (cc < 256) {
        277 rv += '00';
        278 } else if (cc < 4096) { // \u1000
        279 rv += '0';
        280 }
        281 return goog.json.Serializer.charToJsonCharCache_[c] = rv + cc.toString(16);
        282 }), '"');
        283};
        284
        285
        286/**
        287 * Serializes a number to a JSON string
        288 * @private
        289 * @param {number} n The number to serialize.
        290 * @param {Array} sb Array used as a string builder.
        291 */
        292goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
        293 sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
        294};
        295
        296
        297/**
        298 * Serializes an array to a JSON string
        299 * @param {Array} arr The array to serialize.
        300 * @param {Array} sb Array used as a string builder.
        301 * @protected
        302 */
        303goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
        304 var l = arr.length;
        305 sb.push('[');
        306 var sep = '';
        307 for (var i = 0; i < l; i++) {
        308 sb.push(sep);
        309
        310 var value = arr[i];
        311 this.serialize_(
        312 this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
        313 sb);
        314
        315 sep = ',';
        316 }
        317 sb.push(']');
        318};
        319
        320
        321/**
        322 * Serializes an object to a JSON string
        323 * @private
        324 * @param {Object} obj The object to serialize.
        325 * @param {Array} sb Array used as a string builder.
        326 */
        327goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
        328 sb.push('{');
        329 var sep = '';
        330 for (var key in obj) {
        331 if (Object.prototype.hasOwnProperty.call(obj, key)) {
        332 var value = obj[key];
        333 // Skip functions.
        334 // TODO(ptucker) Should we return something for function properties?
        335 if (typeof value != 'function') {
        336 sb.push(sep);
        337 this.serializeString_(key, sb);
        338 sb.push(':');
        339
        340 this.serialize_(
        341 this.replacer_ ? this.replacer_.call(obj, key, value) : value,
        342 sb);
        343
        344 sep = ',';
        345 }
        346 }
        347 }
        348 sb.push('}');
        349};
        \ No newline at end of file +json.js

        lib/goog/json/json.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview JSON utility functions.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20
        21goog.provide('goog.json');
        22goog.provide('goog.json.Replacer');
        23goog.provide('goog.json.Reviver');
        24goog.provide('goog.json.Serializer');
        25
        26
        27/**
        28 * @define {boolean} If true, use the native JSON parsing API.
        29 * NOTE(ruilopes): EXPERIMENTAL, handle with care. Setting this to true might
        30 * break your code. The default {@code goog.json.parse} implementation is able
        31 * to handle invalid JSON, such as JSPB.
        32 */
        33goog.define('goog.json.USE_NATIVE_JSON', false);
        34
        35
        36/**
        37 * Tests if a string is an invalid JSON string. This only ensures that we are
        38 * not using any invalid characters
        39 * @param {string} s The string to test.
        40 * @return {boolean} True if the input is a valid JSON string.
        41 */
        42goog.json.isValid = function(s) {
        43 // All empty whitespace is not valid.
        44 if (/^\s*$/.test(s)) {
        45 return false;
        46 }
        47
        48 // This is taken from http://www.json.org/json2.js which is released to the
        49 // public domain.
        50 // Changes: We dissallow \u2028 Line separator and \u2029 Paragraph separator
        51 // inside strings. We also treat \u2028 and \u2029 as whitespace which they
        52 // are in the RFC but IE and Safari does not match \s to these so we need to
        53 // include them in the reg exps in all places where whitespace is allowed.
        54 // We allowed \x7f inside strings because some tools don't escape it,
        55 // e.g. http://www.json.org/java/org/json/JSONObject.java
        56
        57 // Parsing happens in three stages. In the first stage, we run the text
        58 // against regular expressions that look for non-JSON patterns. We are
        59 // especially concerned with '()' and 'new' because they can cause invocation,
        60 // and '=' because it can cause mutation. But just to be safe, we want to
        61 // reject all unexpected forms.
        62
        63 // We split the first stage into 4 regexp operations in order to work around
        64 // crippling inefficiencies in IE's and Safari's regexp engines. First we
        65 // replace all backslash pairs with '@' (a non-JSON character). Second, we
        66 // replace all simple value tokens with ']' characters. Third, we delete all
        67 // open brackets that follow a colon or comma or that begin the text. Finally,
        68 // we look to see that the remaining characters are only whitespace or ']' or
        69 // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
        70
        71 // Don't make these static since they have the global flag.
        72 var backslashesRe = /\\["\\\/bfnrtu]/g;
        73 var simpleValuesRe =
        74 /"[^"\\\n\r\u2028\u2029\x00-\x08\x0a-\x1f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
        75 var openBracketsRe = /(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g;
        76 var remainderRe = /^[\],:{}\s\u2028\u2029]*$/;
        77
        78 return remainderRe.test(s.replace(backslashesRe, '@').
        79 replace(simpleValuesRe, ']').
        80 replace(openBracketsRe, ''));
        81};
        82
        83
        84/**
        85 * Parses a JSON string and returns the result. This throws an exception if
        86 * the string is an invalid JSON string.
        87 *
        88 * Note that this is very slow on large strings. If you trust the source of
        89 * the string then you should use unsafeParse instead.
        90 *
        91 * @param {*} s The JSON string to parse.
        92 * @throws Error if s is invalid JSON.
        93 * @return {Object} The object generated from the JSON string, or null.
        94 */
        95goog.json.parse = goog.json.USE_NATIVE_JSON ?
        96 /** @type {function(*):Object} */ (goog.global['JSON']['parse']) :
        97 function(s) {
        98 var o = String(s);
        99 if (goog.json.isValid(o)) {
        100 /** @preserveTry */
        101 try {
        102 return /** @type {Object} */ (eval('(' + o + ')'));
        103 } catch (ex) {
        104 }
        105 }
        106 throw Error('Invalid JSON string: ' + o);
        107 };
        108
        109
        110/**
        111 * Parses a JSON string and returns the result. This uses eval so it is open
        112 * to security issues and it should only be used if you trust the source.
        113 *
        114 * @param {string} s The JSON string to parse.
        115 * @return {Object} The object generated from the JSON string.
        116 */
        117goog.json.unsafeParse = goog.json.USE_NATIVE_JSON ?
        118 /** @type {function(string):Object} */ (goog.global['JSON']['parse']) :
        119 function(s) {
        120 return /** @type {Object} */ (eval('(' + s + ')'));
        121 };
        122
        123
        124/**
        125 * JSON replacer, as defined in Section 15.12.3 of the ES5 spec.
        126 * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
        127 *
        128 * TODO(nicksantos): Array should also be a valid replacer.
        129 *
        130 * @typedef {function(this:Object, string, *): *}
        131 */
        132goog.json.Replacer;
        133
        134
        135/**
        136 * JSON reviver, as defined in Section 15.12.2 of the ES5 spec.
        137 * @see http://ecma-international.org/ecma-262/5.1/#sec-15.12.3
        138 *
        139 * @typedef {function(this:Object, string, *): *}
        140 */
        141goog.json.Reviver;
        142
        143
        144/**
        145 * Serializes an object or a value to a JSON string.
        146 *
        147 * @param {*} object The object to serialize.
        148 * @param {?goog.json.Replacer=} opt_replacer A replacer function
        149 * called for each (key, value) pair that determines how the value
        150 * should be serialized. By defult, this just returns the value
        151 * and allows default serialization to kick in.
        152 * @throws Error if there are loops in the object graph.
        153 * @return {string} A JSON string representation of the input.
        154 */
        155goog.json.serialize = goog.json.USE_NATIVE_JSON ?
        156 /** @type {function(*, ?goog.json.Replacer=):string} */
        157 (goog.global['JSON']['stringify']) :
        158 function(object, opt_replacer) {
        159 // NOTE(nicksantos): Currently, we never use JSON.stringify.
        160 //
        161 // The last time I evaluated this, JSON.stringify had subtle bugs and
        162 // behavior differences on all browsers, and the performance win was not
        163 // large enough to justify all the issues. This may change in the future
        164 // as browser implementations get better.
        165 //
        166 // assertSerialize in json_test contains if branches for the cases
        167 // that fail.
        168 return new goog.json.Serializer(opt_replacer).serialize(object);
        169 };
        170
        171
        172
        173/**
        174 * Class that is used to serialize JSON objects to a string.
        175 * @param {?goog.json.Replacer=} opt_replacer Replacer.
        176 * @constructor
        177 */
        178goog.json.Serializer = function(opt_replacer) {
        179 /**
        180 * @type {goog.json.Replacer|null|undefined}
        181 * @private
        182 */
        183 this.replacer_ = opt_replacer;
        184};
        185
        186
        187/**
        188 * Serializes an object or a value to a JSON string.
        189 *
        190 * @param {*} object The object to serialize.
        191 * @throws Error if there are loops in the object graph.
        192 * @return {string} A JSON string representation of the input.
        193 */
        194goog.json.Serializer.prototype.serialize = function(object) {
        195 var sb = [];
        196 this.serializeInternal(object, sb);
        197 return sb.join('');
        198};
        199
        200
        201/**
        202 * Serializes a generic value to a JSON string
        203 * @protected
        204 * @param {*} object The object to serialize.
        205 * @param {Array<string>} sb Array used as a string builder.
        206 * @throws Error if there are loops in the object graph.
        207 */
        208goog.json.Serializer.prototype.serializeInternal = function(object, sb) {
        209 if (object == null) {
        210 // undefined == null so this branch covers undefined as well as null
        211 sb.push('null');
        212 return;
        213 }
        214
        215 if (typeof object == 'object') {
        216 if (goog.isArray(object)) {
        217 this.serializeArray(/** @type {!Array<?>} */ (object), sb);
        218 return;
        219 } else if (object instanceof String ||
        220 object instanceof Number ||
        221 object instanceof Boolean) {
        222 object = object.valueOf();
        223 // Fall through to switch below.
        224 } else {
        225 this.serializeObject_(/** @type {Object} */ (object), sb);
        226 return;
        227 }
        228 }
        229
        230 switch (typeof object) {
        231 case 'string':
        232 this.serializeString_(/** @type {string} */ (object), sb);
        233 break;
        234 case 'number':
        235 this.serializeNumber_(/** @type {number} */ (object), sb);
        236 break;
        237 case 'boolean':
        238 sb.push(object);
        239 break;
        240 case 'function':
        241 // Skip functions.
        242 // TODO(user) Should we return something here?
        243 break;
        244 default:
        245 throw Error('Unknown type: ' + typeof object);
        246 }
        247};
        248
        249
        250/**
        251 * Character mappings used internally for goog.string.quote
        252 * @private
        253 * @type {!Object}
        254 */
        255goog.json.Serializer.charToJsonCharCache_ = {
        256 '\"': '\\"',
        257 '\\': '\\\\',
        258 '/': '\\/',
        259 '\b': '\\b',
        260 '\f': '\\f',
        261 '\n': '\\n',
        262 '\r': '\\r',
        263 '\t': '\\t',
        264
        265 '\x0B': '\\u000b' // '\v' is not supported in JScript
        266};
        267
        268
        269/**
        270 * Regular expression used to match characters that need to be replaced.
        271 * The S60 browser has a bug where unicode characters are not matched by
        272 * regular expressions. The condition below detects such behaviour and
        273 * adjusts the regular expression accordingly.
        274 * @private
        275 * @type {!RegExp}
        276 */
        277goog.json.Serializer.charsToReplace_ = /\uffff/.test('\uffff') ?
        278 /[\\\"\x00-\x1f\x7f-\uffff]/g : /[\\\"\x00-\x1f\x7f-\xff]/g;
        279
        280
        281/**
        282 * Serializes a string to a JSON string
        283 * @private
        284 * @param {string} s The string to serialize.
        285 * @param {Array<string>} sb Array used as a string builder.
        286 */
        287goog.json.Serializer.prototype.serializeString_ = function(s, sb) {
        288 // The official JSON implementation does not work with international
        289 // characters.
        290 sb.push('"', s.replace(goog.json.Serializer.charsToReplace_, function(c) {
        291 // caching the result improves performance by a factor 2-3
        292 var rv = goog.json.Serializer.charToJsonCharCache_[c];
        293 if (!rv) {
        294 rv = '\\u' + (c.charCodeAt(0) | 0x10000).toString(16).substr(1);
        295 goog.json.Serializer.charToJsonCharCache_[c] = rv;
        296 }
        297 return rv;
        298 }), '"');
        299};
        300
        301
        302/**
        303 * Serializes a number to a JSON string
        304 * @private
        305 * @param {number} n The number to serialize.
        306 * @param {Array<string>} sb Array used as a string builder.
        307 */
        308goog.json.Serializer.prototype.serializeNumber_ = function(n, sb) {
        309 sb.push(isFinite(n) && !isNaN(n) ? n : 'null');
        310};
        311
        312
        313/**
        314 * Serializes an array to a JSON string
        315 * @param {Array<string>} arr The array to serialize.
        316 * @param {Array<string>} sb Array used as a string builder.
        317 * @protected
        318 */
        319goog.json.Serializer.prototype.serializeArray = function(arr, sb) {
        320 var l = arr.length;
        321 sb.push('[');
        322 var sep = '';
        323 for (var i = 0; i < l; i++) {
        324 sb.push(sep);
        325
        326 var value = arr[i];
        327 this.serializeInternal(
        328 this.replacer_ ? this.replacer_.call(arr, String(i), value) : value,
        329 sb);
        330
        331 sep = ',';
        332 }
        333 sb.push(']');
        334};
        335
        336
        337/**
        338 * Serializes an object to a JSON string
        339 * @private
        340 * @param {Object} obj The object to serialize.
        341 * @param {Array<string>} sb Array used as a string builder.
        342 */
        343goog.json.Serializer.prototype.serializeObject_ = function(obj, sb) {
        344 sb.push('{');
        345 var sep = '';
        346 for (var key in obj) {
        347 if (Object.prototype.hasOwnProperty.call(obj, key)) {
        348 var value = obj[key];
        349 // Skip functions.
        350 // TODO(ptucker) Should we return something for function properties?
        351 if (typeof value != 'function') {
        352 sb.push(sep);
        353 this.serializeString_(key, sb);
        354 sb.push(':');
        355
        356 this.serializeInternal(
        357 this.replacer_ ? this.replacer_.call(obj, key, value) : value,
        358 sb);
        359
        360 sep = ',';
        361 }
        362 }
        363 }
        364 sb.push('}');
        365};
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/assertthat.js.src.html b/docs/source/lib/goog/labs/testing/assertthat.js.src.html index cb81061..775f035 100644 --- a/docs/source/lib/goog/labs/testing/assertthat.js.src.html +++ b/docs/source/lib/goog/labs/testing/assertthat.js.src.html @@ -1 +1 @@ -assertthat.js

        lib/goog/labs/testing/assertthat.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides main functionality of assertThat. assertThat calls the
        17 * matcher's matches method to test if a matcher matches assertThat's arguments.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.MatcherError');
        22goog.provide('goog.labs.testing.assertThat');
        23
        24goog.require('goog.asserts');
        25goog.require('goog.debug.Error');
        26goog.require('goog.labs.testing.Matcher');
        27
        28
        29/**
        30 * Asserts that the actual value evaluated by the matcher is true.
        31 *
        32 * @param {*} actual The object to assert by the matcher.
        33 * @param {!goog.labs.testing.Matcher} matcher A matcher to verify values.
        34 * @param {string=} opt_reason Description of what is asserted.
        35 *
        36 */
        37goog.labs.testing.assertThat = function(actual, matcher, opt_reason) {
        38 if (!matcher.matches(actual)) {
        39 // Prefix the error description with a reason from the assert ?
        40 var prefix = opt_reason ? opt_reason + ': ' : '';
        41 var desc = prefix + matcher.describe(actual);
        42
        43 // some sort of failure here
        44 throw new goog.labs.testing.MatcherError(desc);
        45 }
        46};
        47
        48
        49
        50/**
        51 * Error thrown when a Matcher fails to match the input value.
        52 * @param {string=} opt_message The error message.
        53 * @constructor
        54 * @extends {goog.debug.Error}
        55 */
        56goog.labs.testing.MatcherError = function(opt_message) {
        57 goog.base(this, opt_message);
        58};
        59goog.inherits(goog.labs.testing.MatcherError, goog.debug.Error);
        \ No newline at end of file +assertthat.js

        lib/goog/labs/testing/assertthat.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides main functionality of assertThat. assertThat calls the
        17 * matcher's matches method to test if a matcher matches assertThat's arguments.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.MatcherError');
        22goog.provide('goog.labs.testing.assertThat');
        23
        24goog.require('goog.debug.Error');
        25
        26
        27/**
        28 * Asserts that the actual value evaluated by the matcher is true.
        29 *
        30 * @param {*} actual The object to assert by the matcher.
        31 * @param {!goog.labs.testing.Matcher} matcher A matcher to verify values.
        32 * @param {string=} opt_reason Description of what is asserted.
        33 *
        34 */
        35goog.labs.testing.assertThat = function(actual, matcher, opt_reason) {
        36 if (!matcher.matches(actual)) {
        37 // Prefix the error description with a reason from the assert ?
        38 var prefix = opt_reason ? opt_reason + ': ' : '';
        39 var desc = prefix + matcher.describe(actual);
        40
        41 // some sort of failure here
        42 throw new goog.labs.testing.MatcherError(desc);
        43 }
        44};
        45
        46
        47
        48/**
        49 * Error thrown when a Matcher fails to match the input value.
        50 * @param {string=} opt_message The error message.
        51 * @constructor
        52 * @extends {goog.debug.Error}
        53 * @final
        54 */
        55goog.labs.testing.MatcherError = function(opt_message) {
        56 goog.labs.testing.MatcherError.base(this, 'constructor', opt_message);
        57};
        58goog.inherits(goog.labs.testing.MatcherError, goog.debug.Error);
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/logicmatcher.js.src.html b/docs/source/lib/goog/labs/testing/logicmatcher.js.src.html index 9aa4930..7ecfefb 100644 --- a/docs/source/lib/goog/labs/testing/logicmatcher.js.src.html +++ b/docs/source/lib/goog/labs/testing/logicmatcher.js.src.html @@ -1 +1 @@ -logicmatcher.js

        lib/goog/labs/testing/logicmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in logic matchers: anyOf, allOf, and isNot.
        17 *
        18 */
        19
        20
        21goog.provide('goog.labs.testing.AllOfMatcher');
        22goog.provide('goog.labs.testing.AnyOfMatcher');
        23goog.provide('goog.labs.testing.IsNotMatcher');
        24
        25
        26goog.require('goog.array');
        27goog.require('goog.labs.testing.Matcher');
        28
        29
        30
        31/**
        32 * The AllOf matcher.
        33 *
        34 * @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers.
        35 *
        36 * @constructor
        37 * @implements {goog.labs.testing.Matcher}
        38 */
        39goog.labs.testing.AllOfMatcher = function(matchers) {
        40 /**
        41 * @type {!Array.<!goog.labs.testing.Matcher>}
        42 * @private
        43 */
        44 this.matchers_ = matchers;
        45};
        46
        47
        48/**
        49 * Determines if all of the matchers match the input value.
        50 *
        51 * @override
        52 */
        53goog.labs.testing.AllOfMatcher.prototype.matches = function(actualValue) {
        54 return goog.array.every(this.matchers_, function(matcher) {
        55 return matcher.matches(actualValue);
        56 });
        57};
        58
        59
        60/**
        61 * Describes why the matcher failed. The returned string is a concatenation of
        62 * all the failed matchers' error strings.
        63 *
        64 * @override
        65 */
        66goog.labs.testing.AllOfMatcher.prototype.describe =
        67 function(actualValue) {
        68 // TODO(user) : Optimize this to remove duplication with matches ?
        69 var errorString = '';
        70 goog.array.forEach(this.matchers_, function(matcher) {
        71 if (!matcher.matches(actualValue)) {
        72 errorString += matcher.describe(actualValue) + '\n';
        73 }
        74 });
        75 return errorString;
        76};
        77
        78
        79
        80/**
        81 * The AnyOf matcher.
        82 *
        83 * @param {!Array.<!goog.labs.testing.Matcher>} matchers Input matchers.
        84 *
        85 * @constructor
        86 * @implements {goog.labs.testing.Matcher}
        87 */
        88goog.labs.testing.AnyOfMatcher = function(matchers) {
        89 /**
        90 * @type {!Array.<!goog.labs.testing.Matcher>}
        91 * @private
        92 */
        93 this.matchers_ = matchers;
        94};
        95
        96
        97/**
        98 * Determines if any of the matchers matches the input value.
        99 *
        100 * @override
        101 */
        102goog.labs.testing.AnyOfMatcher.prototype.matches = function(actualValue) {
        103 return goog.array.some(this.matchers_, function(matcher) {
        104 return matcher.matches(actualValue);
        105 });
        106};
        107
        108
        109/**
        110 * Describes why the matcher failed.
        111 *
        112 * @override
        113 */
        114goog.labs.testing.AnyOfMatcher.prototype.describe =
        115 function(actualValue) {
        116 // TODO(user) : Optimize this to remove duplication with matches ?
        117 var errorString = '';
        118 goog.array.forEach(this.matchers_, function(matcher) {
        119 if (!matcher.matches(actualValue)) {
        120 errorString += matcher.describe(actualValue) + '\n';
        121 }
        122 });
        123 return errorString;
        124};
        125
        126
        127
        128/**
        129 * The IsNot matcher.
        130 *
        131 * @param {!goog.labs.testing.Matcher} matcher The matcher to negate.
        132 *
        133 * @constructor
        134 * @implements {goog.labs.testing.Matcher}
        135 */
        136goog.labs.testing.IsNotMatcher = function(matcher) {
        137 /**
        138 * @type {!goog.labs.testing.Matcher}
        139 * @private
        140 */
        141 this.matcher_ = matcher;
        142};
        143
        144
        145/**
        146 * Determines if the input value doesn't satisfy a matcher.
        147 *
        148 * @override
        149 */
        150goog.labs.testing.IsNotMatcher.prototype.matches = function(actualValue) {
        151 return !this.matcher_.matches(actualValue);
        152};
        153
        154
        155/**
        156 * Describes why the matcher failed.
        157 *
        158 * @override
        159 */
        160goog.labs.testing.IsNotMatcher.prototype.describe =
        161 function(actualValue) {
        162 return 'The following is false: ' + this.matcher_.describe(actualValue);
        163};
        164
        165
        166/**
        167 * Creates a matcher that will succeed only if all of the given matchers
        168 * succeed.
        169 *
        170 * @param {...goog.labs.testing.Matcher} var_args The matchers to test
        171 * against.
        172 *
        173 * @return {!goog.labs.testing.AllOfMatcher} The AllOf matcher.
        174 */
        175function allOf(var_args) {
        176 var matchers = goog.array.toArray(arguments);
        177 return new goog.labs.testing.AllOfMatcher(matchers);
        178}
        179
        180
        181/**
        182 * Accepts a set of matchers and returns a matcher which matches
        183 * values which satisfy the constraints of any of the given matchers.
        184 *
        185 * @param {...goog.labs.testing.Matcher} var_args The matchers to test
        186 * against.
        187 *
        188 * @return {!goog.labs.testing.AnyOfMatcher} The AnyOf matcher.
        189 */
        190function anyOf(var_args) {
        191 var matchers = goog.array.toArray(arguments);
        192 return new goog.labs.testing.AnyOfMatcher(matchers);
        193}
        194
        195
        196/**
        197 * Returns a matcher that negates the input matcher. The returned
        198 * matcher matches the values not matched by the input matcher and vice-versa.
        199 *
        200 * @param {!goog.labs.testing.Matcher} matcher The matcher to test against.
        201 *
        202 * @return {!goog.labs.testing.IsNotMatcher} The IsNot matcher.
        203 */
        204function isNot(matcher) {
        205 return new goog.labs.testing.IsNotMatcher(matcher);
        206}
        \ No newline at end of file +logicmatcher.js

        lib/goog/labs/testing/logicmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in logic matchers: anyOf, allOf, and isNot.
        17 *
        18 */
        19
        20
        21goog.provide('goog.labs.testing.AllOfMatcher');
        22goog.provide('goog.labs.testing.AnyOfMatcher');
        23goog.provide('goog.labs.testing.IsNotMatcher');
        24
        25
        26goog.require('goog.array');
        27goog.require('goog.labs.testing.Matcher');
        28
        29
        30
        31/**
        32 * The AllOf matcher.
        33 *
        34 * @param {!Array<!goog.labs.testing.Matcher>} matchers Input matchers.
        35 *
        36 * @constructor
        37 * @struct
        38 * @implements {goog.labs.testing.Matcher}
        39 * @final
        40 */
        41goog.labs.testing.AllOfMatcher = function(matchers) {
        42 /**
        43 * @type {!Array<!goog.labs.testing.Matcher>}
        44 * @private
        45 */
        46 this.matchers_ = matchers;
        47};
        48
        49
        50/**
        51 * Determines if all of the matchers match the input value.
        52 *
        53 * @override
        54 */
        55goog.labs.testing.AllOfMatcher.prototype.matches = function(actualValue) {
        56 return goog.array.every(this.matchers_, function(matcher) {
        57 return matcher.matches(actualValue);
        58 });
        59};
        60
        61
        62/**
        63 * Describes why the matcher failed. The returned string is a concatenation of
        64 * all the failed matchers' error strings.
        65 *
        66 * @override
        67 */
        68goog.labs.testing.AllOfMatcher.prototype.describe =
        69 function(actualValue) {
        70 // TODO(user) : Optimize this to remove duplication with matches ?
        71 var errorString = '';
        72 goog.array.forEach(this.matchers_, function(matcher) {
        73 if (!matcher.matches(actualValue)) {
        74 errorString += matcher.describe(actualValue) + '\n';
        75 }
        76 });
        77 return errorString;
        78};
        79
        80
        81
        82/**
        83 * The AnyOf matcher.
        84 *
        85 * @param {!Array<!goog.labs.testing.Matcher>} matchers Input matchers.
        86 *
        87 * @constructor
        88 * @struct
        89 * @implements {goog.labs.testing.Matcher}
        90 * @final
        91 */
        92goog.labs.testing.AnyOfMatcher = function(matchers) {
        93 /**
        94 * @type {!Array<!goog.labs.testing.Matcher>}
        95 * @private
        96 */
        97 this.matchers_ = matchers;
        98};
        99
        100
        101/**
        102 * Determines if any of the matchers matches the input value.
        103 *
        104 * @override
        105 */
        106goog.labs.testing.AnyOfMatcher.prototype.matches = function(actualValue) {
        107 return goog.array.some(this.matchers_, function(matcher) {
        108 return matcher.matches(actualValue);
        109 });
        110};
        111
        112
        113/**
        114 * Describes why the matcher failed.
        115 *
        116 * @override
        117 */
        118goog.labs.testing.AnyOfMatcher.prototype.describe =
        119 function(actualValue) {
        120 // TODO(user) : Optimize this to remove duplication with matches ?
        121 var errorString = '';
        122 goog.array.forEach(this.matchers_, function(matcher) {
        123 if (!matcher.matches(actualValue)) {
        124 errorString += matcher.describe(actualValue) + '\n';
        125 }
        126 });
        127 return errorString;
        128};
        129
        130
        131
        132/**
        133 * The IsNot matcher.
        134 *
        135 * @param {!goog.labs.testing.Matcher} matcher The matcher to negate.
        136 *
        137 * @constructor
        138 * @struct
        139 * @implements {goog.labs.testing.Matcher}
        140 * @final
        141 */
        142goog.labs.testing.IsNotMatcher = function(matcher) {
        143 /**
        144 * @type {!goog.labs.testing.Matcher}
        145 * @private
        146 */
        147 this.matcher_ = matcher;
        148};
        149
        150
        151/**
        152 * Determines if the input value doesn't satisfy a matcher.
        153 *
        154 * @override
        155 */
        156goog.labs.testing.IsNotMatcher.prototype.matches = function(actualValue) {
        157 return !this.matcher_.matches(actualValue);
        158};
        159
        160
        161/**
        162 * Describes why the matcher failed.
        163 *
        164 * @override
        165 */
        166goog.labs.testing.IsNotMatcher.prototype.describe =
        167 function(actualValue) {
        168 return 'The following is false: ' + this.matcher_.describe(actualValue);
        169};
        170
        171
        172/**
        173 * Creates a matcher that will succeed only if all of the given matchers
        174 * succeed.
        175 *
        176 * @param {...goog.labs.testing.Matcher} var_args The matchers to test
        177 * against.
        178 *
        179 * @return {!goog.labs.testing.AllOfMatcher} The AllOf matcher.
        180 */
        181function allOf(var_args) {
        182 var matchers = goog.array.toArray(arguments);
        183 return new goog.labs.testing.AllOfMatcher(matchers);
        184}
        185
        186
        187/**
        188 * Accepts a set of matchers and returns a matcher which matches
        189 * values which satisfy the constraints of any of the given matchers.
        190 *
        191 * @param {...goog.labs.testing.Matcher} var_args The matchers to test
        192 * against.
        193 *
        194 * @return {!goog.labs.testing.AnyOfMatcher} The AnyOf matcher.
        195 */
        196function anyOf(var_args) {
        197 var matchers = goog.array.toArray(arguments);
        198 return new goog.labs.testing.AnyOfMatcher(matchers);
        199}
        200
        201
        202/**
        203 * Returns a matcher that negates the input matcher. The returned
        204 * matcher matches the values not matched by the input matcher and vice-versa.
        205 *
        206 * @param {!goog.labs.testing.Matcher} matcher The matcher to test against.
        207 *
        208 * @return {!goog.labs.testing.IsNotMatcher} The IsNot matcher.
        209 */
        210function isNot(matcher) {
        211 return new goog.labs.testing.IsNotMatcher(matcher);
        212}
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/matcher.js.src.html b/docs/source/lib/goog/labs/testing/matcher.js.src.html index 2c148eb..0fa61c4 100644 --- a/docs/source/lib/goog/labs/testing/matcher.js.src.html +++ b/docs/source/lib/goog/labs/testing/matcher.js.src.html @@ -1 +1 @@ -matcher.js

        lib/goog/labs/testing/matcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the base Matcher interface. User code should use the
        17 * matchers through assertThat statements and not directly.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.Matcher');
        22
        23
        24
        25/**
        26 * A matcher object to be used in assertThat statements.
        27 * @interface
        28 */
        29goog.labs.testing.Matcher = function() {};
        30
        31
        32/**
        33 * Determines whether a value matches the constraints of the match.
        34 *
        35 * @param {*} value The object to match.
        36 * @return {boolean} Whether the input value matches this matcher.
        37 */
        38goog.labs.testing.Matcher.prototype.matches = function(value) {};
        39
        40
        41/**
        42 * Describes why the matcher failed.
        43 *
        44 * @param {*} value The value that didn't match.
        45 * @param {string=} opt_description A partial description to which the reason
        46 * will be appended.
        47 *
        48 * @return {string} Description of why the matcher failed.
        49 */
        50goog.labs.testing.Matcher.prototype.describe =
        51 function(value, opt_description) {};
        \ No newline at end of file +matcher.js

        lib/goog/labs/testing/matcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the base Matcher interface. User code should use the
        17 * matchers through assertThat statements and not directly.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.Matcher');
        22
        23
        24
        25/**
        26 * A matcher object to be used in assertThat statements.
        27 * @interface
        28 */
        29goog.labs.testing.Matcher = function() {};
        30
        31
        32/**
        33 * Determines whether a value matches the constraints of the match.
        34 *
        35 * @param {*} value The object to match.
        36 * @return {boolean} Whether the input value matches this matcher.
        37 */
        38goog.labs.testing.Matcher.prototype.matches = function(value) {};
        39
        40
        41/**
        42 * Describes why the matcher failed.
        43 *
        44 * @param {*} value The value that didn't match.
        45 * @param {string=} opt_description A partial description to which the reason
        46 * will be appended.
        47 *
        48 * @return {string} Description of why the matcher failed.
        49 */
        50goog.labs.testing.Matcher.prototype.describe =
        51 function(value, opt_description) {};
        52
        53
        54/**
        55 * Generates a Matcher from the ‘matches’ and ‘describe’ functions passed in.
        56 *
        57 * @param {!Function} matchesFunction The ‘matches’ function.
        58 * @param {Function=} opt_describeFunction The ‘describe’ function.
        59 * @return {!Function} The custom matcher.
        60 */
        61goog.labs.testing.Matcher.makeMatcher =
        62 function(matchesFunction, opt_describeFunction) {
        63
        64 /**
        65 * @constructor
        66 * @implements {goog.labs.testing.Matcher}
        67 * @final
        68 */
        69 var matcherConstructor = function() {};
        70
        71 /** @override */
        72 matcherConstructor.prototype.matches = matchesFunction;
        73
        74 if (opt_describeFunction) {
        75 /** @override */
        76 matcherConstructor.prototype.describe = opt_describeFunction;
        77 }
        78
        79 return matcherConstructor;
        80};
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/numbermatcher.js.src.html b/docs/source/lib/goog/labs/testing/numbermatcher.js.src.html index d6a9293..c73dfcf 100644 --- a/docs/source/lib/goog/labs/testing/numbermatcher.js.src.html +++ b/docs/source/lib/goog/labs/testing/numbermatcher.js.src.html @@ -1 +1 @@ -numbermatcher.js

        lib/goog/labs/testing/numbermatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in number matchers like lessThan,
        17 * greaterThan, etc.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.CloseToMatcher');
        22goog.provide('goog.labs.testing.EqualToMatcher');
        23goog.provide('goog.labs.testing.GreaterThanEqualToMatcher');
        24goog.provide('goog.labs.testing.GreaterThanMatcher');
        25goog.provide('goog.labs.testing.LessThanEqualToMatcher');
        26goog.provide('goog.labs.testing.LessThanMatcher');
        27
        28
        29goog.require('goog.asserts');
        30goog.require('goog.labs.testing.Matcher');
        31
        32
        33
        34/**
        35 * The GreaterThan matcher.
        36 *
        37 * @param {number} value The value to compare.
        38 *
        39 * @constructor
        40 * @implements {goog.labs.testing.Matcher}
        41 */
        42goog.labs.testing.GreaterThanMatcher = function(value) {
        43 /**
        44 * @type {number}
        45 * @private
        46 */
        47 this.value_ = value;
        48};
        49
        50
        51/**
        52 * Determines if input value is greater than the expected value.
        53 *
        54 * @override
        55 */
        56goog.labs.testing.GreaterThanMatcher.prototype.matches = function(actualValue) {
        57 goog.asserts.assertNumber(actualValue);
        58 return actualValue > this.value_;
        59};
        60
        61
        62/**
        63 * @override
        64 */
        65goog.labs.testing.GreaterThanMatcher.prototype.describe =
        66 function(actualValue) {
        67 goog.asserts.assertNumber(actualValue);
        68 return actualValue + ' is not greater than ' + this.value_;
        69};
        70
        71
        72
        73/**
        74 * The lessThan matcher.
        75 *
        76 * @param {number} value The value to compare.
        77 *
        78 * @constructor
        79 * @implements {goog.labs.testing.Matcher}
        80 */
        81goog.labs.testing.LessThanMatcher = function(value) {
        82 /**
        83 * @type {number}
        84 * @private
        85 */
        86 this.value_ = value;
        87};
        88
        89
        90/**
        91 * Determines if the input value is less than the expected value.
        92 *
        93 * @override
        94 */
        95goog.labs.testing.LessThanMatcher.prototype.matches = function(actualValue) {
        96 goog.asserts.assertNumber(actualValue);
        97 return actualValue < this.value_;
        98};
        99
        100
        101/**
        102 * @override
        103 */
        104goog.labs.testing.LessThanMatcher.prototype.describe =
        105 function(actualValue) {
        106 goog.asserts.assertNumber(actualValue);
        107 return actualValue + ' is not less than ' + this.value_;
        108};
        109
        110
        111
        112/**
        113 * The GreaterThanEqualTo matcher.
        114 *
        115 * @param {number} value The value to compare.
        116 *
        117 * @constructor
        118 * @implements {goog.labs.testing.Matcher}
        119 */
        120goog.labs.testing.GreaterThanEqualToMatcher = function(value) {
        121 /**
        122 * @type {number}
        123 * @private
        124 */
        125 this.value_ = value;
        126};
        127
        128
        129/**
        130 * Determines if the input value is greater than equal to the expected value.
        131 *
        132 * @override
        133 */
        134goog.labs.testing.GreaterThanEqualToMatcher.prototype.matches =
        135 function(actualValue) {
        136 goog.asserts.assertNumber(actualValue);
        137 return actualValue >= this.value_;
        138};
        139
        140
        141/**
        142 * @override
        143 */
        144goog.labs.testing.GreaterThanEqualToMatcher.prototype.describe =
        145 function(actualValue) {
        146 goog.asserts.assertNumber(actualValue);
        147 return actualValue + ' is not greater than equal to ' + this.value_;
        148};
        149
        150
        151
        152/**
        153 * The LessThanEqualTo matcher.
        154 *
        155 * @param {number} value The value to compare.
        156 *
        157 * @constructor
        158 * @implements {goog.labs.testing.Matcher}
        159 */
        160goog.labs.testing.LessThanEqualToMatcher = function(value) {
        161 /**
        162 * @type {number}
        163 * @private
        164 */
        165 this.value_ = value;
        166};
        167
        168
        169/**
        170 * Determines if the input value is less than or equal to the expected value.
        171 *
        172 * @override
        173 */
        174goog.labs.testing.LessThanEqualToMatcher.prototype.matches =
        175 function(actualValue) {
        176 goog.asserts.assertNumber(actualValue);
        177 return actualValue <= this.value_;
        178};
        179
        180
        181/**
        182 * @override
        183 */
        184goog.labs.testing.LessThanEqualToMatcher.prototype.describe =
        185 function(actualValue) {
        186 goog.asserts.assertNumber(actualValue);
        187 return actualValue + ' is not less than equal to ' + this.value_;
        188};
        189
        190
        191
        192/**
        193 * The EqualTo matcher.
        194 *
        195 * @param {number} value The value to compare.
        196 *
        197 * @constructor
        198 * @implements {goog.labs.testing.Matcher}
        199 */
        200goog.labs.testing.EqualToMatcher = function(value) {
        201 /**
        202 * @type {number}
        203 * @private
        204 */
        205 this.value_ = value;
        206};
        207
        208
        209/**
        210 * Determines if the input value is equal to the expected value.
        211 *
        212 * @override
        213 */
        214goog.labs.testing.EqualToMatcher.prototype.matches = function(actualValue) {
        215 goog.asserts.assertNumber(actualValue);
        216 return actualValue === this.value_;
        217};
        218
        219
        220/**
        221 * @override
        222 */
        223goog.labs.testing.EqualToMatcher.prototype.describe =
        224 function(actualValue) {
        225 goog.asserts.assertNumber(actualValue);
        226 return actualValue + ' is not equal to ' + this.value_;
        227};
        228
        229
        230
        231/**
        232 * The CloseTo matcher.
        233 *
        234 * @param {number} value The value to compare.
        235 * @param {number} range The range to check within.
        236 *
        237 * @constructor
        238 * @implements {goog.labs.testing.Matcher}
        239 */
        240goog.labs.testing.CloseToMatcher = function(value, range) {
        241 /**
        242 * @type {number}
        243 * @private
        244 */
        245 this.value_ = value;
        246 /**
        247 * @type {number}
        248 * @private
        249 */
        250 this.range_ = range;
        251};
        252
        253
        254/**
        255 * Determines if input value is within a certain range of the expected value.
        256 *
        257 * @override
        258 */
        259goog.labs.testing.CloseToMatcher.prototype.matches = function(actualValue) {
        260 goog.asserts.assertNumber(actualValue);
        261 return Math.abs(this.value_ - actualValue) < this.range_;
        262};
        263
        264
        265/**
        266 * @override
        267 */
        268goog.labs.testing.CloseToMatcher.prototype.describe =
        269 function(actualValue) {
        270 goog.asserts.assertNumber(actualValue);
        271 return actualValue + ' is not close to(' + this.range_ + ') ' + this.value_;
        272};
        273
        274
        275/**
        276 * @param {number} value The expected value.
        277 *
        278 * @return {!goog.labs.testing.GreaterThanMatcher} A GreaterThanMatcher.
        279 */
        280function greaterThan(value) {
        281 return new goog.labs.testing.GreaterThanMatcher(value);
        282}
        283
        284
        285/**
        286 * @param {number} value The expected value.
        287 *
        288 * @return {!goog.labs.testing.GreaterThanEqualToMatcher} A
        289 * GreaterThanEqualToMatcher.
        290 */
        291function greaterThanEqualTo(value) {
        292 return new goog.labs.testing.GreaterThanEqualToMatcher(value);
        293}
        294
        295
        296/**
        297 * @param {number} value The expected value.
        298 *
        299 * @return {!goog.labs.testing.LessThanMatcher} A LessThanMatcher.
        300 */
        301function lessThan(value) {
        302 return new goog.labs.testing.LessThanMatcher(value);
        303}
        304
        305
        306/**
        307 * @param {number} value The expected value.
        308 *
        309 * @return {!goog.labs.testing.LessThanEqualToMatcher} A LessThanEqualToMatcher.
        310 */
        311function lessThanEqualTo(value) {
        312 return new goog.labs.testing.LessThanEqualToMatcher(value);
        313}
        314
        315
        316/**
        317 * @param {number} value The expected value.
        318 *
        319 * @return {!goog.labs.testing.EqualToMatcher} An EqualToMatcher.
        320 */
        321function equalTo(value) {
        322 return new goog.labs.testing.EqualToMatcher(value);
        323}
        324
        325
        326/**
        327 * @param {number} value The expected value.
        328 * @param {number} range The maximum allowed difference from the expected value.
        329 *
        330 * @return {!goog.labs.testing.CloseToMatcher} A CloseToMatcher.
        331 */
        332function closeTo(value, range) {
        333 return new goog.labs.testing.CloseToMatcher(value, range);
        334}
        \ No newline at end of file +numbermatcher.js

        lib/goog/labs/testing/numbermatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in number matchers like lessThan,
        17 * greaterThan, etc.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.CloseToMatcher');
        22goog.provide('goog.labs.testing.EqualToMatcher');
        23goog.provide('goog.labs.testing.GreaterThanEqualToMatcher');
        24goog.provide('goog.labs.testing.GreaterThanMatcher');
        25goog.provide('goog.labs.testing.LessThanEqualToMatcher');
        26goog.provide('goog.labs.testing.LessThanMatcher');
        27
        28
        29goog.require('goog.asserts');
        30goog.require('goog.labs.testing.Matcher');
        31
        32
        33
        34/**
        35 * The GreaterThan matcher.
        36 *
        37 * @param {number} value The value to compare.
        38 *
        39 * @constructor
        40 * @struct
        41 * @implements {goog.labs.testing.Matcher}
        42 * @final
        43 */
        44goog.labs.testing.GreaterThanMatcher = function(value) {
        45 /**
        46 * @type {number}
        47 * @private
        48 */
        49 this.value_ = value;
        50};
        51
        52
        53/**
        54 * Determines if input value is greater than the expected value.
        55 *
        56 * @override
        57 */
        58goog.labs.testing.GreaterThanMatcher.prototype.matches = function(actualValue) {
        59 goog.asserts.assertNumber(actualValue);
        60 return actualValue > this.value_;
        61};
        62
        63
        64/**
        65 * @override
        66 */
        67goog.labs.testing.GreaterThanMatcher.prototype.describe =
        68 function(actualValue) {
        69 goog.asserts.assertNumber(actualValue);
        70 return actualValue + ' is not greater than ' + this.value_;
        71};
        72
        73
        74
        75/**
        76 * The lessThan matcher.
        77 *
        78 * @param {number} value The value to compare.
        79 *
        80 * @constructor
        81 * @struct
        82 * @implements {goog.labs.testing.Matcher}
        83 * @final
        84 */
        85goog.labs.testing.LessThanMatcher = function(value) {
        86 /**
        87 * @type {number}
        88 * @private
        89 */
        90 this.value_ = value;
        91};
        92
        93
        94/**
        95 * Determines if the input value is less than the expected value.
        96 *
        97 * @override
        98 */
        99goog.labs.testing.LessThanMatcher.prototype.matches = function(actualValue) {
        100 goog.asserts.assertNumber(actualValue);
        101 return actualValue < this.value_;
        102};
        103
        104
        105/**
        106 * @override
        107 */
        108goog.labs.testing.LessThanMatcher.prototype.describe =
        109 function(actualValue) {
        110 goog.asserts.assertNumber(actualValue);
        111 return actualValue + ' is not less than ' + this.value_;
        112};
        113
        114
        115
        116/**
        117 * The GreaterThanEqualTo matcher.
        118 *
        119 * @param {number} value The value to compare.
        120 *
        121 * @constructor
        122 * @struct
        123 * @implements {goog.labs.testing.Matcher}
        124 * @final
        125 */
        126goog.labs.testing.GreaterThanEqualToMatcher = function(value) {
        127 /**
        128 * @type {number}
        129 * @private
        130 */
        131 this.value_ = value;
        132};
        133
        134
        135/**
        136 * Determines if the input value is greater than equal to the expected value.
        137 *
        138 * @override
        139 */
        140goog.labs.testing.GreaterThanEqualToMatcher.prototype.matches =
        141 function(actualValue) {
        142 goog.asserts.assertNumber(actualValue);
        143 return actualValue >= this.value_;
        144};
        145
        146
        147/**
        148 * @override
        149 */
        150goog.labs.testing.GreaterThanEqualToMatcher.prototype.describe =
        151 function(actualValue) {
        152 goog.asserts.assertNumber(actualValue);
        153 return actualValue + ' is not greater than equal to ' + this.value_;
        154};
        155
        156
        157
        158/**
        159 * The LessThanEqualTo matcher.
        160 *
        161 * @param {number} value The value to compare.
        162 *
        163 * @constructor
        164 * @struct
        165 * @implements {goog.labs.testing.Matcher}
        166 * @final
        167 */
        168goog.labs.testing.LessThanEqualToMatcher = function(value) {
        169 /**
        170 * @type {number}
        171 * @private
        172 */
        173 this.value_ = value;
        174};
        175
        176
        177/**
        178 * Determines if the input value is less than or equal to the expected value.
        179 *
        180 * @override
        181 */
        182goog.labs.testing.LessThanEqualToMatcher.prototype.matches =
        183 function(actualValue) {
        184 goog.asserts.assertNumber(actualValue);
        185 return actualValue <= this.value_;
        186};
        187
        188
        189/**
        190 * @override
        191 */
        192goog.labs.testing.LessThanEqualToMatcher.prototype.describe =
        193 function(actualValue) {
        194 goog.asserts.assertNumber(actualValue);
        195 return actualValue + ' is not less than equal to ' + this.value_;
        196};
        197
        198
        199
        200/**
        201 * The EqualTo matcher.
        202 *
        203 * @param {number} value The value to compare.
        204 *
        205 * @constructor
        206 * @struct
        207 * @implements {goog.labs.testing.Matcher}
        208 * @final
        209 */
        210goog.labs.testing.EqualToMatcher = function(value) {
        211 /**
        212 * @type {number}
        213 * @private
        214 */
        215 this.value_ = value;
        216};
        217
        218
        219/**
        220 * Determines if the input value is equal to the expected value.
        221 *
        222 * @override
        223 */
        224goog.labs.testing.EqualToMatcher.prototype.matches = function(actualValue) {
        225 goog.asserts.assertNumber(actualValue);
        226 return actualValue === this.value_;
        227};
        228
        229
        230/**
        231 * @override
        232 */
        233goog.labs.testing.EqualToMatcher.prototype.describe =
        234 function(actualValue) {
        235 goog.asserts.assertNumber(actualValue);
        236 return actualValue + ' is not equal to ' + this.value_;
        237};
        238
        239
        240
        241/**
        242 * The CloseTo matcher.
        243 *
        244 * @param {number} value The value to compare.
        245 * @param {number} range The range to check within.
        246 *
        247 * @constructor
        248 * @struct
        249 * @implements {goog.labs.testing.Matcher}
        250 * @final
        251 */
        252goog.labs.testing.CloseToMatcher = function(value, range) {
        253 /**
        254 * @type {number}
        255 * @private
        256 */
        257 this.value_ = value;
        258 /**
        259 * @type {number}
        260 * @private
        261 */
        262 this.range_ = range;
        263};
        264
        265
        266/**
        267 * Determines if input value is within a certain range of the expected value.
        268 *
        269 * @override
        270 */
        271goog.labs.testing.CloseToMatcher.prototype.matches = function(actualValue) {
        272 goog.asserts.assertNumber(actualValue);
        273 return Math.abs(this.value_ - actualValue) < this.range_;
        274};
        275
        276
        277/**
        278 * @override
        279 */
        280goog.labs.testing.CloseToMatcher.prototype.describe =
        281 function(actualValue) {
        282 goog.asserts.assertNumber(actualValue);
        283 return actualValue + ' is not close to(' + this.range_ + ') ' + this.value_;
        284};
        285
        286
        287/**
        288 * @param {number} value The expected value.
        289 *
        290 * @return {!goog.labs.testing.GreaterThanMatcher} A GreaterThanMatcher.
        291 */
        292function greaterThan(value) {
        293 return new goog.labs.testing.GreaterThanMatcher(value);
        294}
        295
        296
        297/**
        298 * @param {number} value The expected value.
        299 *
        300 * @return {!goog.labs.testing.GreaterThanEqualToMatcher} A
        301 * GreaterThanEqualToMatcher.
        302 */
        303function greaterThanEqualTo(value) {
        304 return new goog.labs.testing.GreaterThanEqualToMatcher(value);
        305}
        306
        307
        308/**
        309 * @param {number} value The expected value.
        310 *
        311 * @return {!goog.labs.testing.LessThanMatcher} A LessThanMatcher.
        312 */
        313function lessThan(value) {
        314 return new goog.labs.testing.LessThanMatcher(value);
        315}
        316
        317
        318/**
        319 * @param {number} value The expected value.
        320 *
        321 * @return {!goog.labs.testing.LessThanEqualToMatcher} A LessThanEqualToMatcher.
        322 */
        323function lessThanEqualTo(value) {
        324 return new goog.labs.testing.LessThanEqualToMatcher(value);
        325}
        326
        327
        328/**
        329 * @param {number} value The expected value.
        330 *
        331 * @return {!goog.labs.testing.EqualToMatcher} An EqualToMatcher.
        332 */
        333function equalTo(value) {
        334 return new goog.labs.testing.EqualToMatcher(value);
        335}
        336
        337
        338/**
        339 * @param {number} value The expected value.
        340 * @param {number} range The maximum allowed difference from the expected value.
        341 *
        342 * @return {!goog.labs.testing.CloseToMatcher} A CloseToMatcher.
        343 */
        344function closeTo(value, range) {
        345 return new goog.labs.testing.CloseToMatcher(value, range);
        346}
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/objectmatcher.js.src.html b/docs/source/lib/goog/labs/testing/objectmatcher.js.src.html index 69a7f81..f74e010 100644 --- a/docs/source/lib/goog/labs/testing/objectmatcher.js.src.html +++ b/docs/source/lib/goog/labs/testing/objectmatcher.js.src.html @@ -1 +1 @@ -objectmatcher.js

        lib/goog/labs/testing/objectmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in object matchers like equalsObject,
        17 * hasProperty, instanceOf, etc.
        18 */
        19
        20
        21
        22goog.provide('goog.labs.testing.HasPropertyMatcher');
        23goog.provide('goog.labs.testing.InstanceOfMatcher');
        24goog.provide('goog.labs.testing.IsNullMatcher');
        25goog.provide('goog.labs.testing.IsNullOrUndefinedMatcher');
        26goog.provide('goog.labs.testing.IsUndefinedMatcher');
        27goog.provide('goog.labs.testing.ObjectEqualsMatcher');
        28
        29
        30goog.require('goog.labs.testing.Matcher');
        31goog.require('goog.string');
        32
        33
        34
        35/**
        36 * The Equals matcher.
        37 *
        38 * @param {!Object} expectedObject The expected object.
        39 *
        40 * @constructor
        41 * @implements {goog.labs.testing.Matcher}
        42 */
        43goog.labs.testing.ObjectEqualsMatcher = function(expectedObject) {
        44 /**
        45 * @type {!Object}
        46 * @private
        47 */
        48 this.object_ = expectedObject;
        49};
        50
        51
        52/**
        53 * Determines if two objects are the same.
        54 *
        55 * @override
        56 */
        57goog.labs.testing.ObjectEqualsMatcher.prototype.matches =
        58 function(actualObject) {
        59 return actualObject === this.object_;
        60};
        61
        62
        63/**
        64 * @override
        65 */
        66goog.labs.testing.ObjectEqualsMatcher.prototype.describe =
        67 function(actualObject) {
        68 return 'Input object is not the same as the expected object.';
        69};
        70
        71
        72
        73/**
        74 * The HasProperty matcher.
        75 *
        76 * @param {string} property Name of the property to test.
        77 *
        78 * @constructor
        79 * @implements {goog.labs.testing.Matcher}
        80 */
        81goog.labs.testing.HasPropertyMatcher = function(property) {
        82 /**
        83 * @type {string}
        84 * @private
        85 */
        86 this.property_ = property;
        87};
        88
        89
        90/**
        91 * Determines if an object has a property.
        92 *
        93 * @override
        94 */
        95goog.labs.testing.HasPropertyMatcher.prototype.matches =
        96 function(actualObject) {
        97 return this.property_ in actualObject;
        98};
        99
        100
        101/**
        102 * @override
        103 */
        104goog.labs.testing.HasPropertyMatcher.prototype.describe =
        105 function(actualObject) {
        106 return 'Object does not have property: ' + this.property_;
        107};
        108
        109
        110
        111/**
        112 * The InstanceOf matcher.
        113 *
        114 * @param {!Object} object The expected class object.
        115 *
        116 * @constructor
        117 * @implements {goog.labs.testing.Matcher}
        118 */
        119goog.labs.testing.InstanceOfMatcher = function(object) {
        120 /**
        121 * @type {!Object}
        122 * @private
        123 */
        124 this.object_ = object;
        125};
        126
        127
        128/**
        129 * Determines if an object is an instance of another object.
        130 *
        131 * @override
        132 */
        133goog.labs.testing.InstanceOfMatcher.prototype.matches =
        134 function(actualObject) {
        135 return actualObject instanceof this.object_;
        136};
        137
        138
        139/**
        140 * @override
        141 */
        142goog.labs.testing.InstanceOfMatcher.prototype.describe =
        143 function(actualObject) {
        144 return 'Input object is not an instance of the expected object';
        145};
        146
        147
        148
        149/**
        150 * The IsNullOrUndefined matcher.
        151 *
        152 * @constructor
        153 * @implements {goog.labs.testing.Matcher}
        154 */
        155goog.labs.testing.IsNullOrUndefinedMatcher = function() {};
        156
        157
        158/**
        159 * Determines if input value is null or undefined.
        160 *
        161 * @override
        162 */
        163goog.labs.testing.IsNullOrUndefinedMatcher.prototype.matches =
        164 function(actualValue) {
        165 return !goog.isDefAndNotNull(actualValue);
        166};
        167
        168
        169/**
        170 * @override
        171 */
        172goog.labs.testing.IsNullOrUndefinedMatcher.prototype.describe =
        173 function(actualValue) {
        174 return actualValue + ' is not null or undefined.';
        175};
        176
        177
        178
        179/**
        180 * The IsNull matcher.
        181 *
        182 * @constructor
        183 * @implements {goog.labs.testing.Matcher}
        184 */
        185goog.labs.testing.IsNullMatcher = function() {};
        186
        187
        188/**
        189 * Determines if input value is null.
        190 *
        191 * @override
        192 */
        193goog.labs.testing.IsNullMatcher.prototype.matches =
        194 function(actualValue) {
        195 return goog.isNull(actualValue);
        196};
        197
        198
        199/**
        200 * @override
        201 */
        202goog.labs.testing.IsNullMatcher.prototype.describe =
        203 function(actualValue) {
        204 return actualValue + ' is not null.';
        205};
        206
        207
        208
        209/**
        210 * The IsUndefined matcher.
        211 *
        212 * @constructor
        213 * @implements {goog.labs.testing.Matcher}
        214 */
        215goog.labs.testing.IsUndefinedMatcher = function() {};
        216
        217
        218/**
        219 * Determines if input value is undefined.
        220 *
        221 * @override
        222 */
        223goog.labs.testing.IsUndefinedMatcher.prototype.matches =
        224 function(actualValue) {
        225 return !goog.isDef(actualValue);
        226};
        227
        228
        229/**
        230 * @override
        231 */
        232goog.labs.testing.IsUndefinedMatcher.prototype.describe =
        233 function(actualValue) {
        234 return actualValue + ' is not undefined.';
        235};
        236
        237
        238/**
        239 * Returns a matcher that matches objects that are equal to the input object.
        240 * Equality in this case means the two objects are references to the same
        241 * object.
        242 *
        243 * @param {!Object} object The expected object.
        244 *
        245 * @return {!goog.labs.testing.ObjectEqualsMatcher} A
        246 * ObjectEqualsMatcher.
        247 */
        248function equalsObject(object) {
        249 return new goog.labs.testing.ObjectEqualsMatcher(object);
        250}
        251
        252
        253/**
        254 * Returns a matcher that matches objects that contain the input property.
        255 *
        256 * @param {string} property The property name to check.
        257 *
        258 * @return {!goog.labs.testing.HasPropertyMatcher} A HasPropertyMatcher.
        259 */
        260function hasProperty(property) {
        261 return new goog.labs.testing.HasPropertyMatcher(property);
        262}
        263
        264
        265/**
        266 * Returns a matcher that matches instances of the input class.
        267 *
        268 * @param {!Object} object The class object.
        269 *
        270 * @return {!goog.labs.testing.InstanceOfMatcher} A
        271 * InstanceOfMatcher.
        272 */
        273function instanceOfClass(object) {
        274 return new goog.labs.testing.InstanceOfMatcher(object);
        275}
        276
        277
        278/**
        279 * Returns a matcher that matches all null values.
        280 *
        281 * @return {!goog.labs.testing.IsNullMatcher} A IsNullMatcher.
        282 */
        283function isNull() {
        284 return new goog.labs.testing.IsNullMatcher();
        285}
        286
        287
        288/**
        289 * Returns a matcher that matches all null and undefined values.
        290 *
        291 * @return {!goog.labs.testing.IsNullOrUndefinedMatcher} A
        292 * IsNullOrUndefinedMatcher.
        293 */
        294function isNullOrUndefined() {
        295 return new goog.labs.testing.IsNullOrUndefinedMatcher();
        296}
        297
        298
        299/**
        300 * Returns a matcher that matches undefined values.
        301 *
        302 * @return {!goog.labs.testing.IsUndefinedMatcher} A IsUndefinedMatcher.
        303 */
        304function isUndefined() {
        305 return new goog.labs.testing.IsUndefinedMatcher();
        306}
        \ No newline at end of file +objectmatcher.js

        lib/goog/labs/testing/objectmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in object matchers like equalsObject,
        17 * hasProperty, instanceOf, etc.
        18 */
        19
        20
        21
        22goog.provide('goog.labs.testing.HasPropertyMatcher');
        23goog.provide('goog.labs.testing.InstanceOfMatcher');
        24goog.provide('goog.labs.testing.IsNullMatcher');
        25goog.provide('goog.labs.testing.IsNullOrUndefinedMatcher');
        26goog.provide('goog.labs.testing.IsUndefinedMatcher');
        27goog.provide('goog.labs.testing.ObjectEqualsMatcher');
        28
        29
        30goog.require('goog.labs.testing.Matcher');
        31
        32
        33
        34/**
        35 * The Equals matcher.
        36 *
        37 * @param {!Object} expectedObject The expected object.
        38 *
        39 * @constructor
        40 * @struct
        41 * @implements {goog.labs.testing.Matcher}
        42 * @final
        43 */
        44goog.labs.testing.ObjectEqualsMatcher = function(expectedObject) {
        45 /**
        46 * @type {!Object}
        47 * @private
        48 */
        49 this.object_ = expectedObject;
        50};
        51
        52
        53/**
        54 * Determines if two objects are the same.
        55 *
        56 * @override
        57 */
        58goog.labs.testing.ObjectEqualsMatcher.prototype.matches =
        59 function(actualObject) {
        60 return actualObject === this.object_;
        61};
        62
        63
        64/**
        65 * @override
        66 */
        67goog.labs.testing.ObjectEqualsMatcher.prototype.describe =
        68 function(actualObject) {
        69 return 'Input object is not the same as the expected object.';
        70};
        71
        72
        73
        74/**
        75 * The HasProperty matcher.
        76 *
        77 * @param {string} property Name of the property to test.
        78 *
        79 * @constructor
        80 * @struct
        81 * @implements {goog.labs.testing.Matcher}
        82 * @final
        83 */
        84goog.labs.testing.HasPropertyMatcher = function(property) {
        85 /**
        86 * @type {string}
        87 * @private
        88 */
        89 this.property_ = property;
        90};
        91
        92
        93/**
        94 * Determines if an object has a property.
        95 *
        96 * @override
        97 */
        98goog.labs.testing.HasPropertyMatcher.prototype.matches =
        99 function(actualObject) {
        100 return this.property_ in actualObject;
        101};
        102
        103
        104/**
        105 * @override
        106 */
        107goog.labs.testing.HasPropertyMatcher.prototype.describe =
        108 function(actualObject) {
        109 return 'Object does not have property: ' + this.property_;
        110};
        111
        112
        113
        114/**
        115 * The InstanceOf matcher.
        116 *
        117 * @param {!Object} object The expected class object.
        118 *
        119 * @constructor
        120 * @struct
        121 * @implements {goog.labs.testing.Matcher}
        122 * @final
        123 */
        124goog.labs.testing.InstanceOfMatcher = function(object) {
        125 /**
        126 * @type {!Object}
        127 * @private
        128 */
        129 this.object_ = object;
        130};
        131
        132
        133/**
        134 * Determines if an object is an instance of another object.
        135 *
        136 * @override
        137 */
        138goog.labs.testing.InstanceOfMatcher.prototype.matches =
        139 function(actualObject) {
        140 return actualObject instanceof this.object_;
        141};
        142
        143
        144/**
        145 * @override
        146 */
        147goog.labs.testing.InstanceOfMatcher.prototype.describe =
        148 function(actualObject) {
        149 return 'Input object is not an instance of the expected object';
        150};
        151
        152
        153
        154/**
        155 * The IsNullOrUndefined matcher.
        156 *
        157 * @constructor
        158 * @struct
        159 * @implements {goog.labs.testing.Matcher}
        160 * @final
        161 */
        162goog.labs.testing.IsNullOrUndefinedMatcher = function() {};
        163
        164
        165/**
        166 * Determines if input value is null or undefined.
        167 *
        168 * @override
        169 */
        170goog.labs.testing.IsNullOrUndefinedMatcher.prototype.matches =
        171 function(actualValue) {
        172 return !goog.isDefAndNotNull(actualValue);
        173};
        174
        175
        176/**
        177 * @override
        178 */
        179goog.labs.testing.IsNullOrUndefinedMatcher.prototype.describe =
        180 function(actualValue) {
        181 return actualValue + ' is not null or undefined.';
        182};
        183
        184
        185
        186/**
        187 * The IsNull matcher.
        188 *
        189 * @constructor
        190 * @struct
        191 * @implements {goog.labs.testing.Matcher}
        192 * @final
        193 */
        194goog.labs.testing.IsNullMatcher = function() {};
        195
        196
        197/**
        198 * Determines if input value is null.
        199 *
        200 * @override
        201 */
        202goog.labs.testing.IsNullMatcher.prototype.matches =
        203 function(actualValue) {
        204 return goog.isNull(actualValue);
        205};
        206
        207
        208/**
        209 * @override
        210 */
        211goog.labs.testing.IsNullMatcher.prototype.describe =
        212 function(actualValue) {
        213 return actualValue + ' is not null.';
        214};
        215
        216
        217
        218/**
        219 * The IsUndefined matcher.
        220 *
        221 * @constructor
        222 * @struct
        223 * @implements {goog.labs.testing.Matcher}
        224 * @final
        225 */
        226goog.labs.testing.IsUndefinedMatcher = function() {};
        227
        228
        229/**
        230 * Determines if input value is undefined.
        231 *
        232 * @override
        233 */
        234goog.labs.testing.IsUndefinedMatcher.prototype.matches =
        235 function(actualValue) {
        236 return !goog.isDef(actualValue);
        237};
        238
        239
        240/**
        241 * @override
        242 */
        243goog.labs.testing.IsUndefinedMatcher.prototype.describe =
        244 function(actualValue) {
        245 return actualValue + ' is not undefined.';
        246};
        247
        248
        249/**
        250 * Returns a matcher that matches objects that are equal to the input object.
        251 * Equality in this case means the two objects are references to the same
        252 * object.
        253 *
        254 * @param {!Object} object The expected object.
        255 *
        256 * @return {!goog.labs.testing.ObjectEqualsMatcher} A
        257 * ObjectEqualsMatcher.
        258 */
        259function equalsObject(object) {
        260 return new goog.labs.testing.ObjectEqualsMatcher(object);
        261}
        262
        263
        264/**
        265 * Returns a matcher that matches objects that contain the input property.
        266 *
        267 * @param {string} property The property name to check.
        268 *
        269 * @return {!goog.labs.testing.HasPropertyMatcher} A HasPropertyMatcher.
        270 */
        271function hasProperty(property) {
        272 return new goog.labs.testing.HasPropertyMatcher(property);
        273}
        274
        275
        276/**
        277 * Returns a matcher that matches instances of the input class.
        278 *
        279 * @param {!Object} object The class object.
        280 *
        281 * @return {!goog.labs.testing.InstanceOfMatcher} A
        282 * InstanceOfMatcher.
        283 */
        284function instanceOfClass(object) {
        285 return new goog.labs.testing.InstanceOfMatcher(object);
        286}
        287
        288
        289/**
        290 * Returns a matcher that matches all null values.
        291 *
        292 * @return {!goog.labs.testing.IsNullMatcher} A IsNullMatcher.
        293 */
        294function isNull() {
        295 return new goog.labs.testing.IsNullMatcher();
        296}
        297
        298
        299/**
        300 * Returns a matcher that matches all null and undefined values.
        301 *
        302 * @return {!goog.labs.testing.IsNullOrUndefinedMatcher} A
        303 * IsNullOrUndefinedMatcher.
        304 */
        305function isNullOrUndefined() {
        306 return new goog.labs.testing.IsNullOrUndefinedMatcher();
        307}
        308
        309
        310/**
        311 * Returns a matcher that matches undefined values.
        312 *
        313 * @return {!goog.labs.testing.IsUndefinedMatcher} A IsUndefinedMatcher.
        314 */
        315function isUndefined() {
        316 return new goog.labs.testing.IsUndefinedMatcher();
        317}
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/testing/stringmatcher.js.src.html b/docs/source/lib/goog/labs/testing/stringmatcher.js.src.html index 7f7b789..ec37ca4 100644 --- a/docs/source/lib/goog/labs/testing/stringmatcher.js.src.html +++ b/docs/source/lib/goog/labs/testing/stringmatcher.js.src.html @@ -1 +1 @@ -stringmatcher.js

        lib/goog/labs/testing/stringmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in string matchers like containsString,
        17 * startsWith, endsWith, etc.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.ContainsStringMatcher');
        22goog.provide('goog.labs.testing.EndsWithMatcher');
        23goog.provide('goog.labs.testing.EqualToIgnoringCaseMatcher');
        24goog.provide('goog.labs.testing.EqualToIgnoringWhitespaceMatcher');
        25goog.provide('goog.labs.testing.EqualsMatcher');
        26goog.provide('goog.labs.testing.RegexMatcher');
        27goog.provide('goog.labs.testing.StartsWithMatcher');
        28goog.provide('goog.labs.testing.StringContainsInOrderMatcher');
        29
        30
        31goog.require('goog.asserts');
        32goog.require('goog.labs.testing.Matcher');
        33goog.require('goog.string');
        34
        35
        36
        37/**
        38 * The ContainsString matcher.
        39 *
        40 * @param {string} value The expected string.
        41 *
        42 * @constructor
        43 * @implements {goog.labs.testing.Matcher}
        44 */
        45goog.labs.testing.ContainsStringMatcher = function(value) {
        46 /**
        47 * @type {string}
        48 * @private
        49 */
        50 this.value_ = value;
        51};
        52
        53
        54/**
        55 * Determines if input string contains the expected string.
        56 *
        57 * @override
        58 */
        59goog.labs.testing.ContainsStringMatcher.prototype.matches =
        60 function(actualValue) {
        61 goog.asserts.assertString(actualValue);
        62 return goog.string.contains(actualValue, this.value_);
        63};
        64
        65
        66/**
        67 * @override
        68 */
        69goog.labs.testing.ContainsStringMatcher.prototype.describe =
        70 function(actualValue) {
        71 return actualValue + ' does not contain ' + this.value_;
        72};
        73
        74
        75
        76/**
        77 * The EndsWith matcher.
        78 *
        79 * @param {string} value The expected string.
        80 *
        81 * @constructor
        82 * @implements {goog.labs.testing.Matcher}
        83 */
        84goog.labs.testing.EndsWithMatcher = function(value) {
        85 /**
        86 * @type {string}
        87 * @private
        88 */
        89 this.value_ = value;
        90};
        91
        92
        93/**
        94 * Determines if input string ends with the expected string.
        95 *
        96 * @override
        97 */
        98goog.labs.testing.EndsWithMatcher.prototype.matches = function(actualValue) {
        99 goog.asserts.assertString(actualValue);
        100 return goog.string.endsWith(actualValue, this.value_);
        101};
        102
        103
        104/**
        105 * @override
        106 */
        107goog.labs.testing.EndsWithMatcher.prototype.describe =
        108 function(actualValue) {
        109 return actualValue + ' does not end with ' + this.value_;
        110};
        111
        112
        113
        114/**
        115 * The EqualToIgnoringWhitespace matcher.
        116 *
        117 * @param {string} value The expected string.
        118 *
        119 * @constructor
        120 * @implements {goog.labs.testing.Matcher}
        121 */
        122goog.labs.testing.EqualToIgnoringWhitespaceMatcher = function(value) {
        123 /**
        124 * @type {string}
        125 * @private
        126 */
        127 this.value_ = value;
        128};
        129
        130
        131/**
        132 * Determines if input string contains the expected string.
        133 *
        134 * @override
        135 */
        136goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.matches =
        137 function(actualValue) {
        138 goog.asserts.assertString(actualValue);
        139 var string1 = goog.string.collapseWhitespace(actualValue);
        140
        141 return goog.string.caseInsensitiveCompare(this.value_, string1) === 0;
        142};
        143
        144
        145/**
        146 * @override
        147 */
        148goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.describe =
        149 function(actualValue) {
        150 return actualValue + ' is not equal(ignoring whitespace) to ' + this.value_;
        151};
        152
        153
        154
        155/**
        156 * The Equals matcher.
        157 *
        158 * @param {string} value The expected string.
        159 *
        160 * @constructor
        161 * @implements {goog.labs.testing.Matcher}
        162 */
        163goog.labs.testing.EqualsMatcher = function(value) {
        164 /**
        165 * @type {string}
        166 * @private
        167 */
        168 this.value_ = value;
        169};
        170
        171
        172/**
        173 * Determines if input string is equal to the expected string.
        174 *
        175 * @override
        176 */
        177goog.labs.testing.EqualsMatcher.prototype.matches = function(actualValue) {
        178 goog.asserts.assertString(actualValue);
        179 return this.value_ === actualValue;
        180};
        181
        182
        183/**
        184 * @override
        185 */
        186goog.labs.testing.EqualsMatcher.prototype.describe =
        187 function(actualValue) {
        188 return actualValue + ' is not equal to ' + this.value_;
        189};
        190
        191
        192
        193/**
        194 * The MatchesRegex matcher.
        195 *
        196 * @param {!RegExp} regex The expected regex.
        197 *
        198 * @constructor
        199 * @implements {goog.labs.testing.Matcher}
        200 */
        201goog.labs.testing.RegexMatcher = function(regex) {
        202 /**
        203 * @type {!RegExp}
        204 * @private
        205 */
        206 this.regex_ = regex;
        207};
        208
        209
        210/**
        211 * Determines if input string is equal to the expected string.
        212 *
        213 * @override
        214 */
        215goog.labs.testing.RegexMatcher.prototype.matches = function(
        216 actualValue) {
        217 goog.asserts.assertString(actualValue);
        218 return this.regex_.test(actualValue);
        219};
        220
        221
        222/**
        223 * @override
        224 */
        225goog.labs.testing.RegexMatcher.prototype.describe =
        226 function(actualValue) {
        227 return actualValue + ' does not match ' + this.regex_;
        228};
        229
        230
        231
        232/**
        233 * The StartsWith matcher.
        234 *
        235 * @param {string} value The expected string.
        236 *
        237 * @constructor
        238 * @implements {goog.labs.testing.Matcher}
        239 */
        240goog.labs.testing.StartsWithMatcher = function(value) {
        241 /**
        242 * @type {string}
        243 * @private
        244 */
        245 this.value_ = value;
        246};
        247
        248
        249/**
        250 * Determines if input string starts with the expected string.
        251 *
        252 * @override
        253 */
        254goog.labs.testing.StartsWithMatcher.prototype.matches = function(actualValue) {
        255 goog.asserts.assertString(actualValue);
        256 return goog.string.startsWith(actualValue, this.value_);
        257};
        258
        259
        260/**
        261 * @override
        262 */
        263goog.labs.testing.StartsWithMatcher.prototype.describe =
        264 function(actualValue) {
        265 return actualValue + ' does not start with ' + this.value_;
        266};
        267
        268
        269
        270/**
        271 * The StringContainsInOrdermatcher.
        272 *
        273 * @param {Array.<string>} values The expected string values.
        274 *
        275 * @constructor
        276 * @implements {goog.labs.testing.Matcher}
        277 */
        278goog.labs.testing.StringContainsInOrderMatcher = function(values) {
        279 /**
        280 * @type {Array.<string>}
        281 * @private
        282 */
        283 this.values_ = values;
        284};
        285
        286
        287/**
        288 * Determines if input string contains, in order, the expected array of strings.
        289 *
        290 * @override
        291 */
        292goog.labs.testing.StringContainsInOrderMatcher.prototype.matches =
        293 function(actualValue) {
        294 goog.asserts.assertString(actualValue);
        295 var currentIndex, previousIndex = 0;
        296 for (var i = 0; i < this.values_.length; i++) {
        297 currentIndex = goog.string.contains(actualValue, this.values_[i]);
        298 if (currentIndex < 0 || currentIndex < previousIndex) {
        299 return false;
        300 }
        301 previousIndex = currentIndex;
        302 }
        303 return true;
        304};
        305
        306
        307/**
        308 * @override
        309 */
        310goog.labs.testing.StringContainsInOrderMatcher.prototype.describe =
        311 function(actualValue) {
        312 return actualValue + ' does not contain the expected values in order.';
        313};
        314
        315
        316/**
        317 * Matches a string containing the given string.
        318 *
        319 * @param {string} value The expected value.
        320 *
        321 * @return {!goog.labs.testing.ContainsStringMatcher} A
        322 * ContainsStringMatcher.
        323 */
        324function containsString(value) {
        325 return new goog.labs.testing.ContainsStringMatcher(value);
        326}
        327
        328
        329/**
        330 * Matches a string that ends with the given string.
        331 *
        332 * @param {string} value The expected value.
        333 *
        334 * @return {!goog.labs.testing.EndsWithMatcher} A
        335 * EndsWithMatcher.
        336 */
        337function endsWith(value) {
        338 return new goog.labs.testing.EndsWithMatcher(value);
        339}
        340
        341
        342/**
        343 * Matches a string that equals (ignoring whitespace) the given string.
        344 *
        345 * @param {string} value The expected value.
        346 *
        347 * @return {!goog.labs.testing.EqualToIgnoringWhitespaceMatcher} A
        348 * EqualToIgnoringWhitespaceMatcher.
        349 */
        350function equalToIgnoringWhitespace(value) {
        351 return new goog.labs.testing.EqualToIgnoringWhitespaceMatcher(value);
        352}
        353
        354
        355/**
        356 * Matches a string that equals the given string.
        357 *
        358 * @param {string} value The expected value.
        359 *
        360 * @return {!goog.labs.testing.EqualsMatcher} A EqualsMatcher.
        361 */
        362function equals(value) {
        363 return new goog.labs.testing.EqualsMatcher(value);
        364}
        365
        366
        367/**
        368 * Matches a string against a regular expression.
        369 *
        370 * @param {!RegExp} regex The expected regex.
        371 *
        372 * @return {!goog.labs.testing.RegexMatcher} A RegexMatcher.
        373 */
        374function matchesRegex(regex) {
        375 return new goog.labs.testing.RegexMatcher(regex);
        376}
        377
        378
        379/**
        380 * Matches a string that starts with the given string.
        381 *
        382 * @param {string} value The expected value.
        383 *
        384 * @return {!goog.labs.testing.StartsWithMatcher} A
        385 * StartsWithMatcher.
        386 */
        387function startsWith(value) {
        388 return new goog.labs.testing.StartsWithMatcher(value);
        389}
        390
        391
        392/**
        393 * Matches a string that contains the given strings in order.
        394 *
        395 * @param {Array.<string>} values The expected value.
        396 *
        397 * @return {!goog.labs.testing.StringContainsInOrderMatcher} A
        398 * StringContainsInOrderMatcher.
        399 */
        400function stringContainsInOrder(values) {
        401 return new goog.labs.testing.StringContainsInOrderMatcher(values);
        402}
        \ No newline at end of file +stringmatcher.js

        lib/goog/labs/testing/stringmatcher.js

        1// Copyright 2012 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides the built-in string matchers like containsString,
        17 * startsWith, endsWith, etc.
        18 */
        19
        20
        21goog.provide('goog.labs.testing.ContainsStringMatcher');
        22goog.provide('goog.labs.testing.EndsWithMatcher');
        23goog.provide('goog.labs.testing.EqualToIgnoringWhitespaceMatcher');
        24goog.provide('goog.labs.testing.EqualsMatcher');
        25goog.provide('goog.labs.testing.RegexMatcher');
        26goog.provide('goog.labs.testing.StartsWithMatcher');
        27goog.provide('goog.labs.testing.StringContainsInOrderMatcher');
        28
        29
        30goog.require('goog.asserts');
        31goog.require('goog.labs.testing.Matcher');
        32goog.require('goog.string');
        33
        34
        35
        36/**
        37 * The ContainsString matcher.
        38 *
        39 * @param {string} value The expected string.
        40 *
        41 * @constructor
        42 * @struct
        43 * @implements {goog.labs.testing.Matcher}
        44 * @final
        45 */
        46goog.labs.testing.ContainsStringMatcher = function(value) {
        47 /**
        48 * @type {string}
        49 * @private
        50 */
        51 this.value_ = value;
        52};
        53
        54
        55/**
        56 * Determines if input string contains the expected string.
        57 *
        58 * @override
        59 */
        60goog.labs.testing.ContainsStringMatcher.prototype.matches =
        61 function(actualValue) {
        62 goog.asserts.assertString(actualValue);
        63 return goog.string.contains(actualValue, this.value_);
        64};
        65
        66
        67/**
        68 * @override
        69 */
        70goog.labs.testing.ContainsStringMatcher.prototype.describe =
        71 function(actualValue) {
        72 return actualValue + ' does not contain ' + this.value_;
        73};
        74
        75
        76
        77/**
        78 * The EndsWith matcher.
        79 *
        80 * @param {string} value The expected string.
        81 *
        82 * @constructor
        83 * @struct
        84 * @implements {goog.labs.testing.Matcher}
        85 * @final
        86 */
        87goog.labs.testing.EndsWithMatcher = function(value) {
        88 /**
        89 * @type {string}
        90 * @private
        91 */
        92 this.value_ = value;
        93};
        94
        95
        96/**
        97 * Determines if input string ends with the expected string.
        98 *
        99 * @override
        100 */
        101goog.labs.testing.EndsWithMatcher.prototype.matches = function(actualValue) {
        102 goog.asserts.assertString(actualValue);
        103 return goog.string.endsWith(actualValue, this.value_);
        104};
        105
        106
        107/**
        108 * @override
        109 */
        110goog.labs.testing.EndsWithMatcher.prototype.describe =
        111 function(actualValue) {
        112 return actualValue + ' does not end with ' + this.value_;
        113};
        114
        115
        116
        117/**
        118 * The EqualToIgnoringWhitespace matcher.
        119 *
        120 * @param {string} value The expected string.
        121 *
        122 * @constructor
        123 * @struct
        124 * @implements {goog.labs.testing.Matcher}
        125 * @final
        126 */
        127goog.labs.testing.EqualToIgnoringWhitespaceMatcher = function(value) {
        128 /**
        129 * @type {string}
        130 * @private
        131 */
        132 this.value_ = value;
        133};
        134
        135
        136/**
        137 * Determines if input string contains the expected string.
        138 *
        139 * @override
        140 */
        141goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.matches =
        142 function(actualValue) {
        143 goog.asserts.assertString(actualValue);
        144 var string1 = goog.string.collapseWhitespace(actualValue);
        145
        146 return goog.string.caseInsensitiveCompare(this.value_, string1) === 0;
        147};
        148
        149
        150/**
        151 * @override
        152 */
        153goog.labs.testing.EqualToIgnoringWhitespaceMatcher.prototype.describe =
        154 function(actualValue) {
        155 return actualValue + ' is not equal(ignoring whitespace) to ' + this.value_;
        156};
        157
        158
        159
        160/**
        161 * The Equals matcher.
        162 *
        163 * @param {string} value The expected string.
        164 *
        165 * @constructor
        166 * @struct
        167 * @implements {goog.labs.testing.Matcher}
        168 * @final
        169 */
        170goog.labs.testing.EqualsMatcher = function(value) {
        171 /**
        172 * @type {string}
        173 * @private
        174 */
        175 this.value_ = value;
        176};
        177
        178
        179/**
        180 * Determines if input string is equal to the expected string.
        181 *
        182 * @override
        183 */
        184goog.labs.testing.EqualsMatcher.prototype.matches = function(actualValue) {
        185 goog.asserts.assertString(actualValue);
        186 return this.value_ === actualValue;
        187};
        188
        189
        190/**
        191 * @override
        192 */
        193goog.labs.testing.EqualsMatcher.prototype.describe =
        194 function(actualValue) {
        195 return actualValue + ' is not equal to ' + this.value_;
        196};
        197
        198
        199
        200/**
        201 * The MatchesRegex matcher.
        202 *
        203 * @param {!RegExp} regex The expected regex.
        204 *
        205 * @constructor
        206 * @struct
        207 * @implements {goog.labs.testing.Matcher}
        208 * @final
        209 */
        210goog.labs.testing.RegexMatcher = function(regex) {
        211 /**
        212 * @type {!RegExp}
        213 * @private
        214 */
        215 this.regex_ = regex;
        216};
        217
        218
        219/**
        220 * Determines if input string is equal to the expected string.
        221 *
        222 * @override
        223 */
        224goog.labs.testing.RegexMatcher.prototype.matches = function(
        225 actualValue) {
        226 goog.asserts.assertString(actualValue);
        227 return this.regex_.test(actualValue);
        228};
        229
        230
        231/**
        232 * @override
        233 */
        234goog.labs.testing.RegexMatcher.prototype.describe =
        235 function(actualValue) {
        236 return actualValue + ' does not match ' + this.regex_;
        237};
        238
        239
        240
        241/**
        242 * The StartsWith matcher.
        243 *
        244 * @param {string} value The expected string.
        245 *
        246 * @constructor
        247 * @struct
        248 * @implements {goog.labs.testing.Matcher}
        249 * @final
        250 */
        251goog.labs.testing.StartsWithMatcher = function(value) {
        252 /**
        253 * @type {string}
        254 * @private
        255 */
        256 this.value_ = value;
        257};
        258
        259
        260/**
        261 * Determines if input string starts with the expected string.
        262 *
        263 * @override
        264 */
        265goog.labs.testing.StartsWithMatcher.prototype.matches = function(actualValue) {
        266 goog.asserts.assertString(actualValue);
        267 return goog.string.startsWith(actualValue, this.value_);
        268};
        269
        270
        271/**
        272 * @override
        273 */
        274goog.labs.testing.StartsWithMatcher.prototype.describe =
        275 function(actualValue) {
        276 return actualValue + ' does not start with ' + this.value_;
        277};
        278
        279
        280
        281/**
        282 * The StringContainsInOrdermatcher.
        283 *
        284 * @param {Array<string>} values The expected string values.
        285 *
        286 * @constructor
        287 * @struct
        288 * @implements {goog.labs.testing.Matcher}
        289 * @final
        290 */
        291goog.labs.testing.StringContainsInOrderMatcher = function(values) {
        292 /**
        293 * @type {Array<string>}
        294 * @private
        295 */
        296 this.values_ = values;
        297};
        298
        299
        300/**
        301 * Determines if input string contains, in order, the expected array of strings.
        302 *
        303 * @override
        304 */
        305goog.labs.testing.StringContainsInOrderMatcher.prototype.matches =
        306 function(actualValue) {
        307 goog.asserts.assertString(actualValue);
        308 var currentIndex, previousIndex = 0;
        309 for (var i = 0; i < this.values_.length; i++) {
        310 currentIndex = goog.string.contains(actualValue, this.values_[i]);
        311 if (currentIndex < 0 || currentIndex < previousIndex) {
        312 return false;
        313 }
        314 previousIndex = currentIndex;
        315 }
        316 return true;
        317};
        318
        319
        320/**
        321 * @override
        322 */
        323goog.labs.testing.StringContainsInOrderMatcher.prototype.describe =
        324 function(actualValue) {
        325 return actualValue + ' does not contain the expected values in order.';
        326};
        327
        328
        329/**
        330 * Matches a string containing the given string.
        331 *
        332 * @param {string} value The expected value.
        333 *
        334 * @return {!goog.labs.testing.ContainsStringMatcher} A
        335 * ContainsStringMatcher.
        336 */
        337function containsString(value) {
        338 return new goog.labs.testing.ContainsStringMatcher(value);
        339}
        340
        341
        342/**
        343 * Matches a string that ends with the given string.
        344 *
        345 * @param {string} value The expected value.
        346 *
        347 * @return {!goog.labs.testing.EndsWithMatcher} A
        348 * EndsWithMatcher.
        349 */
        350function endsWith(value) {
        351 return new goog.labs.testing.EndsWithMatcher(value);
        352}
        353
        354
        355/**
        356 * Matches a string that equals (ignoring whitespace) the given string.
        357 *
        358 * @param {string} value The expected value.
        359 *
        360 * @return {!goog.labs.testing.EqualToIgnoringWhitespaceMatcher} A
        361 * EqualToIgnoringWhitespaceMatcher.
        362 */
        363function equalToIgnoringWhitespace(value) {
        364 return new goog.labs.testing.EqualToIgnoringWhitespaceMatcher(value);
        365}
        366
        367
        368/**
        369 * Matches a string that equals the given string.
        370 *
        371 * @param {string} value The expected value.
        372 *
        373 * @return {!goog.labs.testing.EqualsMatcher} A EqualsMatcher.
        374 */
        375function equals(value) {
        376 return new goog.labs.testing.EqualsMatcher(value);
        377}
        378
        379
        380/**
        381 * Matches a string against a regular expression.
        382 *
        383 * @param {!RegExp} regex The expected regex.
        384 *
        385 * @return {!goog.labs.testing.RegexMatcher} A RegexMatcher.
        386 */
        387function matchesRegex(regex) {
        388 return new goog.labs.testing.RegexMatcher(regex);
        389}
        390
        391
        392/**
        393 * Matches a string that starts with the given string.
        394 *
        395 * @param {string} value The expected value.
        396 *
        397 * @return {!goog.labs.testing.StartsWithMatcher} A
        398 * StartsWithMatcher.
        399 */
        400function startsWith(value) {
        401 return new goog.labs.testing.StartsWithMatcher(value);
        402}
        403
        404
        405/**
        406 * Matches a string that contains the given strings in order.
        407 *
        408 * @param {Array<string>} values The expected value.
        409 *
        410 * @return {!goog.labs.testing.StringContainsInOrderMatcher} A
        411 * StringContainsInOrderMatcher.
        412 */
        413function stringContainsInOrder(values) {
        414 return new goog.labs.testing.StringContainsInOrderMatcher(values);
        415}
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/useragent/browser.js.src.html b/docs/source/lib/goog/labs/useragent/browser.js.src.html new file mode 100644 index 0000000..9ee8ef3 --- /dev/null +++ b/docs/source/lib/goog/labs/useragent/browser.js.src.html @@ -0,0 +1 @@ +browser.js

        lib/goog/labs/useragent/browser.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Closure user agent detection (Browser).
        17 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
        18 * For more information on rendering engine, platform, or device see the other
        19 * sub-namespaces in goog.labs.userAgent, goog.labs.userAgent.platform,
        20 * goog.labs.userAgent.device respectively.)
        21 *
        22 * @author martone@google.com (Andy Martone)
        23 */
        24
        25goog.provide('goog.labs.userAgent.browser');
        26
        27goog.require('goog.array');
        28goog.require('goog.labs.userAgent.util');
        29goog.require('goog.object');
        30goog.require('goog.string');
        31
        32
        33// TODO(nnaze): Refactor to remove excessive exclusion logic in matching
        34// functions.
        35
        36
        37/**
        38 * @return {boolean} Whether the user's browser is Opera.
        39 * @private
        40 */
        41goog.labs.userAgent.browser.matchOpera_ = function() {
        42 return goog.labs.userAgent.util.matchUserAgent('Opera') ||
        43 goog.labs.userAgent.util.matchUserAgent('OPR');
        44};
        45
        46
        47/**
        48 * @return {boolean} Whether the user's browser is IE.
        49 * @private
        50 */
        51goog.labs.userAgent.browser.matchIE_ = function() {
        52 return goog.labs.userAgent.util.matchUserAgent('Edge') ||
        53 goog.labs.userAgent.util.matchUserAgent('Trident') ||
        54 goog.labs.userAgent.util.matchUserAgent('MSIE');
        55};
        56
        57
        58/**
        59 * @return {boolean} Whether the user's browser is Firefox.
        60 * @private
        61 */
        62goog.labs.userAgent.browser.matchFirefox_ = function() {
        63 return goog.labs.userAgent.util.matchUserAgent('Firefox');
        64};
        65
        66
        67/**
        68 * @return {boolean} Whether the user's browser is Safari.
        69 * @private
        70 */
        71goog.labs.userAgent.browser.matchSafari_ = function() {
        72 return goog.labs.userAgent.util.matchUserAgent('Safari') &&
        73 !(goog.labs.userAgent.browser.matchChrome_() ||
        74 goog.labs.userAgent.browser.matchCoast_() ||
        75 goog.labs.userAgent.browser.matchOpera_() ||
        76 goog.labs.userAgent.browser.matchIE_() ||
        77 goog.labs.userAgent.browser.isSilk() ||
        78 goog.labs.userAgent.util.matchUserAgent('Android'));
        79};
        80
        81
        82/**
        83 * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
        84 * iOS browser).
        85 * @private
        86 */
        87goog.labs.userAgent.browser.matchCoast_ = function() {
        88 return goog.labs.userAgent.util.matchUserAgent('Coast');
        89};
        90
        91
        92/**
        93 * @return {boolean} Whether the user's browser is iOS Webview.
        94 * @private
        95 */
        96goog.labs.userAgent.browser.matchIosWebview_ = function() {
        97 // iOS Webview does not show up as Chrome or Safari. Also check for Opera's
        98 // WebKit-based iOS browser, Coast.
        99 return (goog.labs.userAgent.util.matchUserAgent('iPad') ||
        100 goog.labs.userAgent.util.matchUserAgent('iPhone')) &&
        101 !goog.labs.userAgent.browser.matchSafari_() &&
        102 !goog.labs.userAgent.browser.matchChrome_() &&
        103 !goog.labs.userAgent.browser.matchCoast_() &&
        104 goog.labs.userAgent.util.matchUserAgent('AppleWebKit');
        105};
        106
        107
        108/**
        109 * @return {boolean} Whether the user's browser is Chrome.
        110 * @private
        111 */
        112goog.labs.userAgent.browser.matchChrome_ = function() {
        113 return (goog.labs.userAgent.util.matchUserAgent('Chrome') ||
        114 goog.labs.userAgent.util.matchUserAgent('CriOS')) &&
        115 !goog.labs.userAgent.browser.matchOpera_() &&
        116 !goog.labs.userAgent.browser.matchIE_();
        117};
        118
        119
        120/**
        121 * @return {boolean} Whether the user's browser is the Android browser.
        122 * @private
        123 */
        124goog.labs.userAgent.browser.matchAndroidBrowser_ = function() {
        125 // Android can appear in the user agent string for Chrome on Android.
        126 // This is not the Android standalone browser if it does.
        127 return goog.labs.userAgent.util.matchUserAgent('Android') &&
        128 !(goog.labs.userAgent.browser.isChrome() ||
        129 goog.labs.userAgent.browser.isFirefox() ||
        130 goog.labs.userAgent.browser.isOpera() ||
        131 goog.labs.userAgent.browser.isSilk());
        132};
        133
        134
        135/**
        136 * @return {boolean} Whether the user's browser is Opera.
        137 */
        138goog.labs.userAgent.browser.isOpera = goog.labs.userAgent.browser.matchOpera_;
        139
        140
        141/**
        142 * @return {boolean} Whether the user's browser is IE.
        143 */
        144goog.labs.userAgent.browser.isIE = goog.labs.userAgent.browser.matchIE_;
        145
        146
        147/**
        148 * @return {boolean} Whether the user's browser is Firefox.
        149 */
        150goog.labs.userAgent.browser.isFirefox =
        151 goog.labs.userAgent.browser.matchFirefox_;
        152
        153
        154/**
        155 * @return {boolean} Whether the user's browser is Safari.
        156 */
        157goog.labs.userAgent.browser.isSafari =
        158 goog.labs.userAgent.browser.matchSafari_;
        159
        160
        161/**
        162 * @return {boolean} Whether the user's browser is Coast (Opera's Webkit-based
        163 * iOS browser).
        164 */
        165goog.labs.userAgent.browser.isCoast =
        166 goog.labs.userAgent.browser.matchCoast_;
        167
        168
        169/**
        170 * @return {boolean} Whether the user's browser is iOS Webview.
        171 */
        172goog.labs.userAgent.browser.isIosWebview =
        173 goog.labs.userAgent.browser.matchIosWebview_;
        174
        175
        176/**
        177 * @return {boolean} Whether the user's browser is Chrome.
        178 */
        179goog.labs.userAgent.browser.isChrome =
        180 goog.labs.userAgent.browser.matchChrome_;
        181
        182
        183/**
        184 * @return {boolean} Whether the user's browser is the Android browser.
        185 */
        186goog.labs.userAgent.browser.isAndroidBrowser =
        187 goog.labs.userAgent.browser.matchAndroidBrowser_;
        188
        189
        190/**
        191 * For more information, see:
        192 * http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
        193 * @return {boolean} Whether the user's browser is Silk.
        194 */
        195goog.labs.userAgent.browser.isSilk = function() {
        196 return goog.labs.userAgent.util.matchUserAgent('Silk');
        197};
        198
        199
        200/**
        201 * @return {string} The browser version or empty string if version cannot be
        202 * determined. Note that for Internet Explorer, this returns the version of
        203 * the browser, not the version of the rendering engine. (IE 8 in
        204 * compatibility mode will return 8.0 rather than 7.0. To determine the
        205 * rendering engine version, look at document.documentMode instead. See
        206 * http://msdn.microsoft.com/en-us/library/cc196988(v=vs.85).aspx for more
        207 * details.)
        208 */
        209goog.labs.userAgent.browser.getVersion = function() {
        210 var userAgentString = goog.labs.userAgent.util.getUserAgent();
        211 // Special case IE since IE's version is inside the parenthesis and
        212 // without the '/'.
        213 if (goog.labs.userAgent.browser.isIE()) {
        214 return goog.labs.userAgent.browser.getIEVersion_(userAgentString);
        215 }
        216
        217 var versionTuples = goog.labs.userAgent.util.extractVersionTuples(
        218 userAgentString);
        219
        220 // Construct a map for easy lookup.
        221 var versionMap = {};
        222 goog.array.forEach(versionTuples, function(tuple) {
        223 // Note that the tuple is of length three, but we only care about the
        224 // first two.
        225 var key = tuple[0];
        226 var value = tuple[1];
        227 versionMap[key] = value;
        228 });
        229
        230 var versionMapHasKey = goog.partial(goog.object.containsKey, versionMap);
        231
        232 // Gives the value with the first key it finds, otherwise empty string.
        233 function lookUpValueWithKeys(keys) {
        234 var key = goog.array.find(keys, versionMapHasKey);
        235 return versionMap[key] || '';
        236 }
        237
        238 // Check Opera before Chrome since Opera 15+ has "Chrome" in the string.
        239 // See
        240 // http://my.opera.com/ODIN/blog/2013/07/15/opera-user-agent-strings-opera-15-and-beyond
        241 if (goog.labs.userAgent.browser.isOpera()) {
        242 // Opera 10 has Version/10.0 but Opera/9.8, so look for "Version" first.
        243 // Opera uses 'OPR' for more recent UAs.
        244 return lookUpValueWithKeys(['Version', 'Opera', 'OPR']);
        245 }
        246
        247 if (goog.labs.userAgent.browser.isChrome()) {
        248 return lookUpValueWithKeys(['Chrome', 'CriOS']);
        249 }
        250
        251 // Usually products browser versions are in the third tuple after "Mozilla"
        252 // and the engine.
        253 var tuple = versionTuples[2];
        254 return tuple && tuple[1] || '';
        255};
        256
        257
        258/**
        259 * @param {string|number} version The version to check.
        260 * @return {boolean} Whether the browser version is higher or the same as the
        261 * given version.
        262 */
        263goog.labs.userAgent.browser.isVersionOrHigher = function(version) {
        264 return goog.string.compareVersions(goog.labs.userAgent.browser.getVersion(),
        265 version) >= 0;
        266};
        267
        268
        269/**
        270 * Determines IE version. More information:
        271 * http://msdn.microsoft.com/en-us/library/ie/bg182625(v=vs.85).aspx#uaString
        272 * http://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
        273 * http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
        274 * http://blogs.msdn.com/b/ie/archive/2009/01/09/the-internet-explorer-8-user-agent-string-updated-edition.aspx
        275 *
        276 * @param {string} userAgent the User-Agent.
        277 * @return {string}
        278 * @private
        279 */
        280goog.labs.userAgent.browser.getIEVersion_ = function(userAgent) {
        281 // IE11 may identify itself as MSIE 9.0 or MSIE 10.0 due to an IE 11 upgrade
        282 // bug. Example UA:
        283 // Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
        284 // like Gecko.
        285 // See http://www.whatismybrowser.com/developers/unknown-user-agent-fragments.
        286 var rv = /rv: *([\d\.]*)/.exec(userAgent);
        287 if (rv && rv[1]) {
        288 return rv[1];
        289 }
        290
        291 var edge = /Edge\/([\d\.]+)/.exec(userAgent);
        292 if (edge) {
        293 return edge[1];
        294 }
        295
        296 var version = '';
        297 var msie = /MSIE +([\d\.]+)/.exec(userAgent);
        298 if (msie && msie[1]) {
        299 // IE in compatibility mode usually identifies itself as MSIE 7.0; in this
        300 // case, use the Trident version to determine the version of IE. For more
        301 // details, see the links above.
        302 var tridentVersion = /Trident\/(\d.\d)/.exec(userAgent);
        303 if (msie[1] == '7.0') {
        304 if (tridentVersion && tridentVersion[1]) {
        305 switch (tridentVersion[1]) {
        306 case '4.0':
        307 version = '8.0';
        308 break;
        309 case '5.0':
        310 version = '9.0';
        311 break;
        312 case '6.0':
        313 version = '10.0';
        314 break;
        315 case '7.0':
        316 version = '11.0';
        317 break;
        318 }
        319 } else {
        320 version = '7.0';
        321 }
        322 } else {
        323 version = msie[1];
        324 }
        325 }
        326 return version;
        327};
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/useragent/engine.js.src.html b/docs/source/lib/goog/labs/useragent/engine.js.src.html new file mode 100644 index 0000000..2077e6d --- /dev/null +++ b/docs/source/lib/goog/labs/useragent/engine.js.src.html @@ -0,0 +1 @@ +engine.js

        lib/goog/labs/useragent/engine.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Closure user agent detection.
        17 * @see http://en.wikipedia.org/wiki/User_agent
        18 * For more information on browser brand, platform, or device see the other
        19 * sub-namespaces in goog.labs.userAgent (browser, platform, and device).
        20 *
        21 */
        22
        23goog.provide('goog.labs.userAgent.engine');
        24
        25goog.require('goog.array');
        26goog.require('goog.labs.userAgent.util');
        27goog.require('goog.string');
        28
        29
        30/**
        31 * @return {boolean} Whether the rendering engine is Presto.
        32 */
        33goog.labs.userAgent.engine.isPresto = function() {
        34 return goog.labs.userAgent.util.matchUserAgent('Presto');
        35};
        36
        37
        38/**
        39 * @return {boolean} Whether the rendering engine is Trident.
        40 */
        41goog.labs.userAgent.engine.isTrident = function() {
        42 // IE only started including the Trident token in IE8.
        43 return goog.labs.userAgent.util.matchUserAgent('Trident') ||
        44 goog.labs.userAgent.util.matchUserAgent('MSIE');
        45};
        46
        47
        48/**
        49 * @return {boolean} Whether the rendering engine is Edge.
        50 */
        51goog.labs.userAgent.engine.isEdge = function() {
        52 return goog.labs.userAgent.util.matchUserAgent('Edge');
        53};
        54
        55
        56/**
        57 * @return {boolean} Whether the rendering engine is WebKit.
        58 */
        59goog.labs.userAgent.engine.isWebKit = function() {
        60 return goog.labs.userAgent.util.matchUserAgentIgnoreCase('WebKit') &&
        61 !goog.labs.userAgent.engine.isEdge();
        62};
        63
        64
        65/**
        66 * @return {boolean} Whether the rendering engine is Gecko.
        67 */
        68goog.labs.userAgent.engine.isGecko = function() {
        69 return goog.labs.userAgent.util.matchUserAgent('Gecko') &&
        70 !goog.labs.userAgent.engine.isWebKit() &&
        71 !goog.labs.userAgent.engine.isTrident() &&
        72 !goog.labs.userAgent.engine.isEdge();
        73};
        74
        75
        76/**
        77 * @return {string} The rendering engine's version or empty string if version
        78 * can't be determined.
        79 */
        80goog.labs.userAgent.engine.getVersion = function() {
        81 var userAgentString = goog.labs.userAgent.util.getUserAgent();
        82 if (userAgentString) {
        83 var tuples = goog.labs.userAgent.util.extractVersionTuples(
        84 userAgentString);
        85
        86 var engineTuple = goog.labs.userAgent.engine.getEngineTuple_(tuples);
        87 if (engineTuple) {
        88 // In Gecko, the version string is either in the browser info or the
        89 // Firefox version. See Gecko user agent string reference:
        90 // http://goo.gl/mULqa
        91 if (engineTuple[0] == 'Gecko') {
        92 return goog.labs.userAgent.engine.getVersionForKey_(
        93 tuples, 'Firefox');
        94 }
        95
        96 return engineTuple[1];
        97 }
        98
        99 // MSIE has only one version identifier, and the Trident version is
        100 // specified in the parenthetical. IE Edge is covered in the engine tuple
        101 // detection.
        102 var browserTuple = tuples[0];
        103 var info;
        104 if (browserTuple && (info = browserTuple[2])) {
        105 var match = /Trident\/([^\s;]+)/.exec(info);
        106 if (match) {
        107 return match[1];
        108 }
        109 }
        110 }
        111 return '';
        112};
        113
        114
        115/**
        116 * @param {!Array.<!Array.<string>>} tuples Extracted version tuples.
        117 * @return {!Array.<string>|undefined} The engine tuple or undefined if not
        118 * found.
        119 * @private
        120 */
        121goog.labs.userAgent.engine.getEngineTuple_ = function(tuples) {
        122 if (!goog.labs.userAgent.engine.isEdge()) {
        123 return tuples[1];
        124 }
        125 for (var i = 0; i < tuples.length; i++) {
        126 var tuple = tuples[i];
        127 if (tuple[0] == 'Edge') {
        128 return tuple;
        129 }
        130 }
        131};
        132
        133
        134/**
        135 * @param {string|number} version The version to check.
        136 * @return {boolean} Whether the rendering engine version is higher or the same
        137 * as the given version.
        138 */
        139goog.labs.userAgent.engine.isVersionOrHigher = function(version) {
        140 return goog.string.compareVersions(goog.labs.userAgent.engine.getVersion(),
        141 version) >= 0;
        142};
        143
        144
        145/**
        146 * @param {!Array<!Array<string>>} tuples Version tuples.
        147 * @param {string} key The key to look for.
        148 * @return {string} The version string of the given key, if present.
        149 * Otherwise, the empty string.
        150 * @private
        151 */
        152goog.labs.userAgent.engine.getVersionForKey_ = function(tuples, key) {
        153 // TODO(nnaze): Move to util if useful elsewhere.
        154
        155 var pair = goog.array.find(tuples, function(pair) {
        156 return key == pair[0];
        157 });
        158
        159 return pair && pair[1] || '';
        160};
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/useragent/platform.js.src.html b/docs/source/lib/goog/labs/useragent/platform.js.src.html new file mode 100644 index 0000000..f141003 --- /dev/null +++ b/docs/source/lib/goog/labs/useragent/platform.js.src.html @@ -0,0 +1 @@ +platform.js

        lib/goog/labs/useragent/platform.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Closure user agent platform detection.
        17 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
        18 * For more information on browser brand, rendering engine, or device see the
        19 * other sub-namespaces in goog.labs.userAgent (browser, engine, and device
        20 * respectively).
        21 *
        22 */
        23
        24goog.provide('goog.labs.userAgent.platform');
        25
        26goog.require('goog.labs.userAgent.util');
        27goog.require('goog.string');
        28
        29
        30/**
        31 * @return {boolean} Whether the platform is Android.
        32 */
        33goog.labs.userAgent.platform.isAndroid = function() {
        34 return goog.labs.userAgent.util.matchUserAgent('Android');
        35};
        36
        37
        38/**
        39 * @return {boolean} Whether the platform is iPod.
        40 */
        41goog.labs.userAgent.platform.isIpod = function() {
        42 return goog.labs.userAgent.util.matchUserAgent('iPod');
        43};
        44
        45
        46/**
        47 * @return {boolean} Whether the platform is iPhone.
        48 */
        49goog.labs.userAgent.platform.isIphone = function() {
        50 return goog.labs.userAgent.util.matchUserAgent('iPhone') &&
        51 !goog.labs.userAgent.util.matchUserAgent('iPod') &&
        52 !goog.labs.userAgent.util.matchUserAgent('iPad');
        53};
        54
        55
        56/**
        57 * @return {boolean} Whether the platform is iPad.
        58 */
        59goog.labs.userAgent.platform.isIpad = function() {
        60 return goog.labs.userAgent.util.matchUserAgent('iPad');
        61};
        62
        63
        64/**
        65 * @return {boolean} Whether the platform is iOS.
        66 */
        67goog.labs.userAgent.platform.isIos = function() {
        68 return goog.labs.userAgent.platform.isIphone() ||
        69 goog.labs.userAgent.platform.isIpad() ||
        70 goog.labs.userAgent.platform.isIpod();
        71};
        72
        73
        74/**
        75 * @return {boolean} Whether the platform is Mac.
        76 */
        77goog.labs.userAgent.platform.isMacintosh = function() {
        78 return goog.labs.userAgent.util.matchUserAgent('Macintosh');
        79};
        80
        81
        82/**
        83 * Note: ChromeOS is not considered to be Linux as it does not report itself
        84 * as Linux in the user agent string.
        85 * @return {boolean} Whether the platform is Linux.
        86 */
        87goog.labs.userAgent.platform.isLinux = function() {
        88 return goog.labs.userAgent.util.matchUserAgent('Linux');
        89};
        90
        91
        92/**
        93 * @return {boolean} Whether the platform is Windows.
        94 */
        95goog.labs.userAgent.platform.isWindows = function() {
        96 return goog.labs.userAgent.util.matchUserAgent('Windows');
        97};
        98
        99
        100/**
        101 * @return {boolean} Whether the platform is ChromeOS.
        102 */
        103goog.labs.userAgent.platform.isChromeOS = function() {
        104 return goog.labs.userAgent.util.matchUserAgent('CrOS');
        105};
        106
        107
        108/**
        109 * The version of the platform. We only determine the version for Windows,
        110 * Mac, and Chrome OS. It doesn't make much sense on Linux. For Windows, we only
        111 * look at the NT version. Non-NT-based versions (e.g. 95, 98, etc.) are given
        112 * version 0.0.
        113 *
        114 * @return {string} The platform version or empty string if version cannot be
        115 * determined.
        116 */
        117goog.labs.userAgent.platform.getVersion = function() {
        118 var userAgentString = goog.labs.userAgent.util.getUserAgent();
        119 var version = '', re;
        120 if (goog.labs.userAgent.platform.isWindows()) {
        121 re = /Windows (?:NT|Phone) ([0-9.]+)/;
        122 var match = re.exec(userAgentString);
        123 if (match) {
        124 version = match[1];
        125 } else {
        126 version = '0.0';
        127 }
        128 } else if (goog.labs.userAgent.platform.isIos()) {
        129 re = /(?:iPhone|iPod|iPad|CPU)\s+OS\s+(\S+)/;
        130 var match = re.exec(userAgentString);
        131 // Report the version as x.y.z and not x_y_z
        132 version = match && match[1].replace(/_/g, '.');
        133 } else if (goog.labs.userAgent.platform.isMacintosh()) {
        134 re = /Mac OS X ([0-9_.]+)/;
        135 var match = re.exec(userAgentString);
        136 // Note: some old versions of Camino do not report an OSX version.
        137 // Default to 10.
        138 version = match ? match[1].replace(/_/g, '.') : '10';
        139 } else if (goog.labs.userAgent.platform.isAndroid()) {
        140 re = /Android\s+([^\);]+)(\)|;)/;
        141 var match = re.exec(userAgentString);
        142 version = match && match[1];
        143 } else if (goog.labs.userAgent.platform.isChromeOS()) {
        144 re = /(?:CrOS\s+(?:i686|x86_64)\s+([0-9.]+))/;
        145 var match = re.exec(userAgentString);
        146 version = match && match[1];
        147 }
        148 return version || '';
        149};
        150
        151
        152/**
        153 * @param {string|number} version The version to check.
        154 * @return {boolean} Whether the browser version is higher or the same as the
        155 * given version.
        156 */
        157goog.labs.userAgent.platform.isVersionOrHigher = function(version) {
        158 return goog.string.compareVersions(goog.labs.userAgent.platform.getVersion(),
        159 version) >= 0;
        160};
        \ No newline at end of file diff --git a/docs/source/lib/goog/labs/useragent/util.js.src.html b/docs/source/lib/goog/labs/useragent/util.js.src.html new file mode 100644 index 0000000..6f4e40e --- /dev/null +++ b/docs/source/lib/goog/labs/useragent/util.js.src.html @@ -0,0 +1 @@ +util.js

        lib/goog/labs/useragent/util.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities used by goog.labs.userAgent tools. These functions
        17 * should not be used outside of goog.labs.userAgent.*.
        18 *
        19 *
        20 * @author nnaze@google.com (Nathan Naze)
        21 */
        22
        23goog.provide('goog.labs.userAgent.util');
        24
        25goog.require('goog.string');
        26
        27
        28/**
        29 * Gets the native userAgent string from navigator if it exists.
        30 * If navigator or navigator.userAgent string is missing, returns an empty
        31 * string.
        32 * @return {string}
        33 * @private
        34 */
        35goog.labs.userAgent.util.getNativeUserAgentString_ = function() {
        36 var navigator = goog.labs.userAgent.util.getNavigator_();
        37 if (navigator) {
        38 var userAgent = navigator.userAgent;
        39 if (userAgent) {
        40 return userAgent;
        41 }
        42 }
        43 return '';
        44};
        45
        46
        47/**
        48 * Getter for the native navigator.
        49 * This is a separate function so it can be stubbed out in testing.
        50 * @return {Navigator}
        51 * @private
        52 */
        53goog.labs.userAgent.util.getNavigator_ = function() {
        54 return goog.global.navigator;
        55};
        56
        57
        58/**
        59 * A possible override for applications which wish to not check
        60 * navigator.userAgent but use a specified value for detection instead.
        61 * @private {string}
        62 */
        63goog.labs.userAgent.util.userAgent_ =
        64 goog.labs.userAgent.util.getNativeUserAgentString_();
        65
        66
        67/**
        68 * Applications may override browser detection on the built in
        69 * navigator.userAgent object by setting this string. Set to null to use the
        70 * browser object instead.
        71 * @param {?string=} opt_userAgent The User-Agent override.
        72 */
        73goog.labs.userAgent.util.setUserAgent = function(opt_userAgent) {
        74 goog.labs.userAgent.util.userAgent_ = opt_userAgent ||
        75 goog.labs.userAgent.util.getNativeUserAgentString_();
        76};
        77
        78
        79/**
        80 * @return {string} The user agent string.
        81 */
        82goog.labs.userAgent.util.getUserAgent = function() {
        83 return goog.labs.userAgent.util.userAgent_;
        84};
        85
        86
        87/**
        88 * @param {string} str
        89 * @return {boolean} Whether the user agent contains the given string, ignoring
        90 * case.
        91 */
        92goog.labs.userAgent.util.matchUserAgent = function(str) {
        93 var userAgent = goog.labs.userAgent.util.getUserAgent();
        94 return goog.string.contains(userAgent, str);
        95};
        96
        97
        98/**
        99 * @param {string} str
        100 * @return {boolean} Whether the user agent contains the given string.
        101 */
        102goog.labs.userAgent.util.matchUserAgentIgnoreCase = function(str) {
        103 var userAgent = goog.labs.userAgent.util.getUserAgent();
        104 return goog.string.caseInsensitiveContains(userAgent, str);
        105};
        106
        107
        108/**
        109 * Parses the user agent into tuples for each section.
        110 * @param {string} userAgent
        111 * @return {!Array<!Array<string>>} Tuples of key, version, and the contents
        112 * of the parenthetical.
        113 */
        114goog.labs.userAgent.util.extractVersionTuples = function(userAgent) {
        115 // Matches each section of a user agent string.
        116 // Example UA:
        117 // Mozilla/5.0 (iPad; U; CPU OS 3_2_1 like Mac OS X; en-us)
        118 // AppleWebKit/531.21.10 (KHTML, like Gecko) Mobile/7B405
        119 // This has three version tuples: Mozilla, AppleWebKit, and Mobile.
        120
        121 var versionRegExp = new RegExp(
        122 // Key. Note that a key may have a space.
        123 // (i.e. 'Mobile Safari' in 'Mobile Safari/5.0')
        124 '(\\w[\\w ]+)' +
        125
        126 '/' + // slash
        127 '([^\\s]+)' + // version (i.e. '5.0b')
        128 '\\s*' + // whitespace
        129 '(?:\\((.*?)\\))?', // parenthetical info. parentheses not matched.
        130 'g');
        131
        132 var data = [];
        133 var match;
        134
        135 // Iterate and collect the version tuples. Each iteration will be the
        136 // next regex match.
        137 while (match = versionRegExp.exec(userAgent)) {
        138 data.push([
        139 match[1], // key
        140 match[2], // value
        141 // || undefined as this is not undefined in IE7 and IE8
        142 match[3] || undefined // info
        143 ]);
        144 }
        145
        146 return data;
        147};
        148
        \ No newline at end of file diff --git a/docs/source/lib/goog/log/log.js.src.html b/docs/source/lib/goog/log/log.js.src.html new file mode 100644 index 0000000..a0bae37 --- /dev/null +++ b/docs/source/lib/goog/log/log.js.src.html @@ -0,0 +1 @@ +log.js

        lib/goog/log/log.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Basic strippable logging definitions.
        17 * @see http://go/closurelogging
        18 *
        19 * @author johnlenz@google.com (John Lenz)
        20 */
        21
        22goog.provide('goog.log');
        23goog.provide('goog.log.Level');
        24goog.provide('goog.log.LogRecord');
        25goog.provide('goog.log.Logger');
        26
        27goog.require('goog.debug');
        28goog.require('goog.debug.LogManager');
        29goog.require('goog.debug.LogRecord');
        30goog.require('goog.debug.Logger');
        31
        32
        33/** @define {boolean} Whether logging is enabled. */
        34goog.define('goog.log.ENABLED', goog.debug.LOGGING_ENABLED);
        35
        36
        37/** @const */
        38goog.log.ROOT_LOGGER_NAME = goog.debug.Logger.ROOT_LOGGER_NAME;
        39
        40
        41
        42/**
        43 * @constructor
        44 * @final
        45 */
        46goog.log.Logger = goog.debug.Logger;
        47
        48
        49
        50/**
        51 * @constructor
        52 * @final
        53 */
        54goog.log.Level = goog.debug.Logger.Level;
        55
        56
        57
        58/**
        59 * @constructor
        60 * @final
        61 */
        62goog.log.LogRecord = goog.debug.LogRecord;
        63
        64
        65/**
        66 * Finds or creates a logger for a named subsystem. If a logger has already been
        67 * created with the given name it is returned. Otherwise a new logger is
        68 * created. If a new logger is created its log level will be configured based
        69 * on the goog.debug.LogManager configuration and it will configured to also
        70 * send logging output to its parent's handlers.
        71 * @see goog.debug.LogManager
        72 *
        73 * @param {string} name A name for the logger. This should be a dot-separated
        74 * name and should normally be based on the package name or class name of
        75 * the subsystem, such as goog.net.BrowserChannel.
        76 * @param {goog.log.Level=} opt_level If provided, override the
        77 * default logging level with the provided level.
        78 * @return {goog.log.Logger} The named logger or null if logging is disabled.
        79 */
        80goog.log.getLogger = function(name, opt_level) {
        81 if (goog.log.ENABLED) {
        82 var logger = goog.debug.LogManager.getLogger(name);
        83 if (opt_level && logger) {
        84 logger.setLevel(opt_level);
        85 }
        86 return logger;
        87 } else {
        88 return null;
        89 }
        90};
        91
        92
        93// TODO(johnlenz): try to tighten the types to these functions.
        94/**
        95 * Adds a handler to the logger. This doesn't use the event system because
        96 * we want to be able to add logging to the event system.
        97 * @param {goog.log.Logger} logger
        98 * @param {Function} handler Handler function to add.
        99 */
        100goog.log.addHandler = function(logger, handler) {
        101 if (goog.log.ENABLED && logger) {
        102 logger.addHandler(handler);
        103 }
        104};
        105
        106
        107/**
        108 * Removes a handler from the logger. This doesn't use the event system because
        109 * we want to be able to add logging to the event system.
        110 * @param {goog.log.Logger} logger
        111 * @param {Function} handler Handler function to remove.
        112 * @return {boolean} Whether the handler was removed.
        113 */
        114goog.log.removeHandler = function(logger, handler) {
        115 if (goog.log.ENABLED && logger) {
        116 return logger.removeHandler(handler);
        117 } else {
        118 return false;
        119 }
        120};
        121
        122
        123/**
        124 * Logs a message. If the logger is currently enabled for the
        125 * given message level then the given message is forwarded to all the
        126 * registered output Handler objects.
        127 * @param {goog.log.Logger} logger
        128 * @param {goog.log.Level} level One of the level identifiers.
        129 * @param {goog.debug.Loggable} msg The message to log.
        130 * @param {Error|Object=} opt_exception An exception associated with the
        131 * message.
        132 */
        133goog.log.log = function(logger, level, msg, opt_exception) {
        134 if (goog.log.ENABLED && logger) {
        135 logger.log(level, msg, opt_exception);
        136 }
        137};
        138
        139
        140/**
        141 * Logs a message at the Level.SEVERE level.
        142 * If the logger is currently enabled for the given message level then the
        143 * given message is forwarded to all the registered output Handler objects.
        144 * @param {goog.log.Logger} logger
        145 * @param {goog.debug.Loggable} msg The message to log.
        146 * @param {Error=} opt_exception An exception associated with the message.
        147 */
        148goog.log.error = function(logger, msg, opt_exception) {
        149 if (goog.log.ENABLED && logger) {
        150 logger.severe(msg, opt_exception);
        151 }
        152};
        153
        154
        155/**
        156 * Logs a message at the Level.WARNING level.
        157 * If the logger is currently enabled for the given message level then the
        158 * given message is forwarded to all the registered output Handler objects.
        159 * @param {goog.log.Logger} logger
        160 * @param {goog.debug.Loggable} msg The message to log.
        161 * @param {Error=} opt_exception An exception associated with the message.
        162 */
        163goog.log.warning = function(logger, msg, opt_exception) {
        164 if (goog.log.ENABLED && logger) {
        165 logger.warning(msg, opt_exception);
        166 }
        167};
        168
        169
        170/**
        171 * Logs a message at the Level.INFO level.
        172 * If the logger is currently enabled for the given message level then the
        173 * given message is forwarded to all the registered output Handler objects.
        174 * @param {goog.log.Logger} logger
        175 * @param {goog.debug.Loggable} msg The message to log.
        176 * @param {Error=} opt_exception An exception associated with the message.
        177 */
        178goog.log.info = function(logger, msg, opt_exception) {
        179 if (goog.log.ENABLED && logger) {
        180 logger.info(msg, opt_exception);
        181 }
        182};
        183
        184
        185/**
        186 * Logs a message at the Level.Fine level.
        187 * If the logger is currently enabled for the given message level then the
        188 * given message is forwarded to all the registered output Handler objects.
        189 * @param {goog.log.Logger} logger
        190 * @param {goog.debug.Loggable} msg The message to log.
        191 * @param {Error=} opt_exception An exception associated with the message.
        192 */
        193goog.log.fine = function(logger, msg, opt_exception) {
        194 if (goog.log.ENABLED && logger) {
        195 logger.fine(msg, opt_exception);
        196 }
        197};
        \ No newline at end of file diff --git a/docs/source/lib/goog/math/box.js.src.html b/docs/source/lib/goog/math/box.js.src.html new file mode 100644 index 0000000..cb48d73 --- /dev/null +++ b/docs/source/lib/goog/math/box.js.src.html @@ -0,0 +1 @@ +box.js

        lib/goog/math/box.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A utility class for representing a numeric box.
        17 */
        18
        19
        20goog.provide('goog.math.Box');
        21
        22goog.require('goog.math.Coordinate');
        23
        24
        25
        26/**
        27 * Class for representing a box. A box is specified as a top, right, bottom,
        28 * and left. A box is useful for representing margins and padding.
        29 *
        30 * This class assumes 'screen coordinates': larger Y coordinates are further
        31 * from the top of the screen.
        32 *
        33 * @param {number} top Top.
        34 * @param {number} right Right.
        35 * @param {number} bottom Bottom.
        36 * @param {number} left Left.
        37 * @struct
        38 * @constructor
        39 */
        40goog.math.Box = function(top, right, bottom, left) {
        41 /**
        42 * Top
        43 * @type {number}
        44 */
        45 this.top = top;
        46
        47 /**
        48 * Right
        49 * @type {number}
        50 */
        51 this.right = right;
        52
        53 /**
        54 * Bottom
        55 * @type {number}
        56 */
        57 this.bottom = bottom;
        58
        59 /**
        60 * Left
        61 * @type {number}
        62 */
        63 this.left = left;
        64};
        65
        66
        67/**
        68 * Creates a Box by bounding a collection of goog.math.Coordinate objects
        69 * @param {...goog.math.Coordinate} var_args Coordinates to be included inside
        70 * the box.
        71 * @return {!goog.math.Box} A Box containing all the specified Coordinates.
        72 */
        73goog.math.Box.boundingBox = function(var_args) {
        74 var box = new goog.math.Box(arguments[0].y, arguments[0].x,
        75 arguments[0].y, arguments[0].x);
        76 for (var i = 1; i < arguments.length; i++) {
        77 var coord = arguments[i];
        78 box.top = Math.min(box.top, coord.y);
        79 box.right = Math.max(box.right, coord.x);
        80 box.bottom = Math.max(box.bottom, coord.y);
        81 box.left = Math.min(box.left, coord.x);
        82 }
        83 return box;
        84};
        85
        86
        87/**
        88 * @return {number} width The width of this Box.
        89 */
        90goog.math.Box.prototype.getWidth = function() {
        91 return this.right - this.left;
        92};
        93
        94
        95/**
        96 * @return {number} height The height of this Box.
        97 */
        98goog.math.Box.prototype.getHeight = function() {
        99 return this.bottom - this.top;
        100};
        101
        102
        103/**
        104 * Creates a copy of the box with the same dimensions.
        105 * @return {!goog.math.Box} A clone of this Box.
        106 */
        107goog.math.Box.prototype.clone = function() {
        108 return new goog.math.Box(this.top, this.right, this.bottom, this.left);
        109};
        110
        111
        112if (goog.DEBUG) {
        113 /**
        114 * Returns a nice string representing the box.
        115 * @return {string} In the form (50t, 73r, 24b, 13l).
        116 * @override
        117 */
        118 goog.math.Box.prototype.toString = function() {
        119 return '(' + this.top + 't, ' + this.right + 'r, ' + this.bottom + 'b, ' +
        120 this.left + 'l)';
        121 };
        122}
        123
        124
        125/**
        126 * Returns whether the box contains a coordinate or another box.
        127 *
        128 * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
        129 * @return {boolean} Whether the box contains the coordinate or other box.
        130 */
        131goog.math.Box.prototype.contains = function(other) {
        132 return goog.math.Box.contains(this, other);
        133};
        134
        135
        136/**
        137 * Expands box with the given margins.
        138 *
        139 * @param {number|goog.math.Box} top Top margin or box with all margins.
        140 * @param {number=} opt_right Right margin.
        141 * @param {number=} opt_bottom Bottom margin.
        142 * @param {number=} opt_left Left margin.
        143 * @return {!goog.math.Box} A reference to this Box.
        144 */
        145goog.math.Box.prototype.expand = function(top, opt_right, opt_bottom,
        146 opt_left) {
        147 if (goog.isObject(top)) {
        148 this.top -= top.top;
        149 this.right += top.right;
        150 this.bottom += top.bottom;
        151 this.left -= top.left;
        152 } else {
        153 this.top -= top;
        154 this.right += opt_right;
        155 this.bottom += opt_bottom;
        156 this.left -= opt_left;
        157 }
        158
        159 return this;
        160};
        161
        162
        163/**
        164 * Expand this box to include another box.
        165 * NOTE(user): This is used in code that needs to be very fast, please don't
        166 * add functionality to this function at the expense of speed (variable
        167 * arguments, accepting multiple argument types, etc).
        168 * @param {goog.math.Box} box The box to include in this one.
        169 */
        170goog.math.Box.prototype.expandToInclude = function(box) {
        171 this.left = Math.min(this.left, box.left);
        172 this.top = Math.min(this.top, box.top);
        173 this.right = Math.max(this.right, box.right);
        174 this.bottom = Math.max(this.bottom, box.bottom);
        175};
        176
        177
        178/**
        179 * Compares boxes for equality.
        180 * @param {goog.math.Box} a A Box.
        181 * @param {goog.math.Box} b A Box.
        182 * @return {boolean} True iff the boxes are equal, or if both are null.
        183 */
        184goog.math.Box.equals = function(a, b) {
        185 if (a == b) {
        186 return true;
        187 }
        188 if (!a || !b) {
        189 return false;
        190 }
        191 return a.top == b.top && a.right == b.right &&
        192 a.bottom == b.bottom && a.left == b.left;
        193};
        194
        195
        196/**
        197 * Returns whether a box contains a coordinate or another box.
        198 *
        199 * @param {goog.math.Box} box A Box.
        200 * @param {goog.math.Coordinate|goog.math.Box} other A Coordinate or a Box.
        201 * @return {boolean} Whether the box contains the coordinate or other box.
        202 */
        203goog.math.Box.contains = function(box, other) {
        204 if (!box || !other) {
        205 return false;
        206 }
        207
        208 if (other instanceof goog.math.Box) {
        209 return other.left >= box.left && other.right <= box.right &&
        210 other.top >= box.top && other.bottom <= box.bottom;
        211 }
        212
        213 // other is a Coordinate.
        214 return other.x >= box.left && other.x <= box.right &&
        215 other.y >= box.top && other.y <= box.bottom;
        216};
        217
        218
        219/**
        220 * Returns the relative x position of a coordinate compared to a box. Returns
        221 * zero if the coordinate is inside the box.
        222 *
        223 * @param {goog.math.Box} box A Box.
        224 * @param {goog.math.Coordinate} coord A Coordinate.
        225 * @return {number} The x position of {@code coord} relative to the nearest
        226 * side of {@code box}, or zero if {@code coord} is inside {@code box}.
        227 */
        228goog.math.Box.relativePositionX = function(box, coord) {
        229 if (coord.x < box.left) {
        230 return coord.x - box.left;
        231 } else if (coord.x > box.right) {
        232 return coord.x - box.right;
        233 }
        234 return 0;
        235};
        236
        237
        238/**
        239 * Returns the relative y position of a coordinate compared to a box. Returns
        240 * zero if the coordinate is inside the box.
        241 *
        242 * @param {goog.math.Box} box A Box.
        243 * @param {goog.math.Coordinate} coord A Coordinate.
        244 * @return {number} The y position of {@code coord} relative to the nearest
        245 * side of {@code box}, or zero if {@code coord} is inside {@code box}.
        246 */
        247goog.math.Box.relativePositionY = function(box, coord) {
        248 if (coord.y < box.top) {
        249 return coord.y - box.top;
        250 } else if (coord.y > box.bottom) {
        251 return coord.y - box.bottom;
        252 }
        253 return 0;
        254};
        255
        256
        257/**
        258 * Returns the distance between a coordinate and the nearest corner/side of a
        259 * box. Returns zero if the coordinate is inside the box.
        260 *
        261 * @param {goog.math.Box} box A Box.
        262 * @param {goog.math.Coordinate} coord A Coordinate.
        263 * @return {number} The distance between {@code coord} and the nearest
        264 * corner/side of {@code box}, or zero if {@code coord} is inside
        265 * {@code box}.
        266 */
        267goog.math.Box.distance = function(box, coord) {
        268 var x = goog.math.Box.relativePositionX(box, coord);
        269 var y = goog.math.Box.relativePositionY(box, coord);
        270 return Math.sqrt(x * x + y * y);
        271};
        272
        273
        274/**
        275 * Returns whether two boxes intersect.
        276 *
        277 * @param {goog.math.Box} a A Box.
        278 * @param {goog.math.Box} b A second Box.
        279 * @return {boolean} Whether the boxes intersect.
        280 */
        281goog.math.Box.intersects = function(a, b) {
        282 return (a.left <= b.right && b.left <= a.right &&
        283 a.top <= b.bottom && b.top <= a.bottom);
        284};
        285
        286
        287/**
        288 * Returns whether two boxes would intersect with additional padding.
        289 *
        290 * @param {goog.math.Box} a A Box.
        291 * @param {goog.math.Box} b A second Box.
        292 * @param {number} padding The additional padding.
        293 * @return {boolean} Whether the boxes intersect.
        294 */
        295goog.math.Box.intersectsWithPadding = function(a, b, padding) {
        296 return (a.left <= b.right + padding && b.left <= a.right + padding &&
        297 a.top <= b.bottom + padding && b.top <= a.bottom + padding);
        298};
        299
        300
        301/**
        302 * Rounds the fields to the next larger integer values.
        303 *
        304 * @return {!goog.math.Box} This box with ceil'd fields.
        305 */
        306goog.math.Box.prototype.ceil = function() {
        307 this.top = Math.ceil(this.top);
        308 this.right = Math.ceil(this.right);
        309 this.bottom = Math.ceil(this.bottom);
        310 this.left = Math.ceil(this.left);
        311 return this;
        312};
        313
        314
        315/**
        316 * Rounds the fields to the next smaller integer values.
        317 *
        318 * @return {!goog.math.Box} This box with floored fields.
        319 */
        320goog.math.Box.prototype.floor = function() {
        321 this.top = Math.floor(this.top);
        322 this.right = Math.floor(this.right);
        323 this.bottom = Math.floor(this.bottom);
        324 this.left = Math.floor(this.left);
        325 return this;
        326};
        327
        328
        329/**
        330 * Rounds the fields to nearest integer values.
        331 *
        332 * @return {!goog.math.Box} This box with rounded fields.
        333 */
        334goog.math.Box.prototype.round = function() {
        335 this.top = Math.round(this.top);
        336 this.right = Math.round(this.right);
        337 this.bottom = Math.round(this.bottom);
        338 this.left = Math.round(this.left);
        339 return this;
        340};
        341
        342
        343/**
        344 * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
        345 * is given, then the left and right values are translated by the coordinate's
        346 * x value and the top and bottom values are translated by the coordinate's y
        347 * value. Otherwise, {@code tx} and {@code opt_ty} are used to translate the x
        348 * and y dimension values.
        349 *
        350 * @param {number|goog.math.Coordinate} tx The value to translate the x
        351 * dimension values by or the the coordinate to translate this box by.
        352 * @param {number=} opt_ty The value to translate y dimension values by.
        353 * @return {!goog.math.Box} This box after translating.
        354 */
        355goog.math.Box.prototype.translate = function(tx, opt_ty) {
        356 if (tx instanceof goog.math.Coordinate) {
        357 this.left += tx.x;
        358 this.right += tx.x;
        359 this.top += tx.y;
        360 this.bottom += tx.y;
        361 } else {
        362 this.left += tx;
        363 this.right += tx;
        364 if (goog.isNumber(opt_ty)) {
        365 this.top += opt_ty;
        366 this.bottom += opt_ty;
        367 }
        368 }
        369 return this;
        370};
        371
        372
        373/**
        374 * Scales this coordinate by the given scale factors. The x and y dimension
        375 * values are scaled by {@code sx} and {@code opt_sy} respectively.
        376 * If {@code opt_sy} is not given, then {@code sx} is used for both x and y.
        377 *
        378 * @param {number} sx The scale factor to use for the x dimension.
        379 * @param {number=} opt_sy The scale factor to use for the y dimension.
        380 * @return {!goog.math.Box} This box after scaling.
        381 */
        382goog.math.Box.prototype.scale = function(sx, opt_sy) {
        383 var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
        384 this.left *= sx;
        385 this.right *= sx;
        386 this.top *= sy;
        387 this.bottom *= sy;
        388 return this;
        389};
        \ No newline at end of file diff --git a/docs/source/lib/goog/math/coordinate.js.src.html b/docs/source/lib/goog/math/coordinate.js.src.html new file mode 100644 index 0000000..3fcf521 --- /dev/null +++ b/docs/source/lib/goog/math/coordinate.js.src.html @@ -0,0 +1 @@ +coordinate.js

        lib/goog/math/coordinate.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A utility class for representing two-dimensional positions.
        17 */
        18
        19
        20goog.provide('goog.math.Coordinate');
        21
        22goog.require('goog.math');
        23
        24
        25
        26/**
        27 * Class for representing coordinates and positions.
        28 * @param {number=} opt_x Left, defaults to 0.
        29 * @param {number=} opt_y Top, defaults to 0.
        30 * @struct
        31 * @constructor
        32 */
        33goog.math.Coordinate = function(opt_x, opt_y) {
        34 /**
        35 * X-value
        36 * @type {number}
        37 */
        38 this.x = goog.isDef(opt_x) ? opt_x : 0;
        39
        40 /**
        41 * Y-value
        42 * @type {number}
        43 */
        44 this.y = goog.isDef(opt_y) ? opt_y : 0;
        45};
        46
        47
        48/**
        49 * Returns a new copy of the coordinate.
        50 * @return {!goog.math.Coordinate} A clone of this coordinate.
        51 */
        52goog.math.Coordinate.prototype.clone = function() {
        53 return new goog.math.Coordinate(this.x, this.y);
        54};
        55
        56
        57if (goog.DEBUG) {
        58 /**
        59 * Returns a nice string representing the coordinate.
        60 * @return {string} In the form (50, 73).
        61 * @override
        62 */
        63 goog.math.Coordinate.prototype.toString = function() {
        64 return '(' + this.x + ', ' + this.y + ')';
        65 };
        66}
        67
        68
        69/**
        70 * Compares coordinates for equality.
        71 * @param {goog.math.Coordinate} a A Coordinate.
        72 * @param {goog.math.Coordinate} b A Coordinate.
        73 * @return {boolean} True iff the coordinates are equal, or if both are null.
        74 */
        75goog.math.Coordinate.equals = function(a, b) {
        76 if (a == b) {
        77 return true;
        78 }
        79 if (!a || !b) {
        80 return false;
        81 }
        82 return a.x == b.x && a.y == b.y;
        83};
        84
        85
        86/**
        87 * Returns the distance between two coordinates.
        88 * @param {!goog.math.Coordinate} a A Coordinate.
        89 * @param {!goog.math.Coordinate} b A Coordinate.
        90 * @return {number} The distance between {@code a} and {@code b}.
        91 */
        92goog.math.Coordinate.distance = function(a, b) {
        93 var dx = a.x - b.x;
        94 var dy = a.y - b.y;
        95 return Math.sqrt(dx * dx + dy * dy);
        96};
        97
        98
        99/**
        100 * Returns the magnitude of a coordinate.
        101 * @param {!goog.math.Coordinate} a A Coordinate.
        102 * @return {number} The distance between the origin and {@code a}.
        103 */
        104goog.math.Coordinate.magnitude = function(a) {
        105 return Math.sqrt(a.x * a.x + a.y * a.y);
        106};
        107
        108
        109/**
        110 * Returns the angle from the origin to a coordinate.
        111 * @param {!goog.math.Coordinate} a A Coordinate.
        112 * @return {number} The angle, in degrees, clockwise from the positive X
        113 * axis to {@code a}.
        114 */
        115goog.math.Coordinate.azimuth = function(a) {
        116 return goog.math.angle(0, 0, a.x, a.y);
        117};
        118
        119
        120/**
        121 * Returns the squared distance between two coordinates. Squared distances can
        122 * be used for comparisons when the actual value is not required.
        123 *
        124 * Performance note: eliminating the square root is an optimization often used
        125 * in lower-level languages, but the speed difference is not nearly as
        126 * pronounced in JavaScript (only a few percent.)
        127 *
        128 * @param {!goog.math.Coordinate} a A Coordinate.
        129 * @param {!goog.math.Coordinate} b A Coordinate.
        130 * @return {number} The squared distance between {@code a} and {@code b}.
        131 */
        132goog.math.Coordinate.squaredDistance = function(a, b) {
        133 var dx = a.x - b.x;
        134 var dy = a.y - b.y;
        135 return dx * dx + dy * dy;
        136};
        137
        138
        139/**
        140 * Returns the difference between two coordinates as a new
        141 * goog.math.Coordinate.
        142 * @param {!goog.math.Coordinate} a A Coordinate.
        143 * @param {!goog.math.Coordinate} b A Coordinate.
        144 * @return {!goog.math.Coordinate} A Coordinate representing the difference
        145 * between {@code a} and {@code b}.
        146 */
        147goog.math.Coordinate.difference = function(a, b) {
        148 return new goog.math.Coordinate(a.x - b.x, a.y - b.y);
        149};
        150
        151
        152/**
        153 * Returns the sum of two coordinates as a new goog.math.Coordinate.
        154 * @param {!goog.math.Coordinate} a A Coordinate.
        155 * @param {!goog.math.Coordinate} b A Coordinate.
        156 * @return {!goog.math.Coordinate} A Coordinate representing the sum of the two
        157 * coordinates.
        158 */
        159goog.math.Coordinate.sum = function(a, b) {
        160 return new goog.math.Coordinate(a.x + b.x, a.y + b.y);
        161};
        162
        163
        164/**
        165 * Rounds the x and y fields to the next larger integer values.
        166 * @return {!goog.math.Coordinate} This coordinate with ceil'd fields.
        167 */
        168goog.math.Coordinate.prototype.ceil = function() {
        169 this.x = Math.ceil(this.x);
        170 this.y = Math.ceil(this.y);
        171 return this;
        172};
        173
        174
        175/**
        176 * Rounds the x and y fields to the next smaller integer values.
        177 * @return {!goog.math.Coordinate} This coordinate with floored fields.
        178 */
        179goog.math.Coordinate.prototype.floor = function() {
        180 this.x = Math.floor(this.x);
        181 this.y = Math.floor(this.y);
        182 return this;
        183};
        184
        185
        186/**
        187 * Rounds the x and y fields to the nearest integer values.
        188 * @return {!goog.math.Coordinate} This coordinate with rounded fields.
        189 */
        190goog.math.Coordinate.prototype.round = function() {
        191 this.x = Math.round(this.x);
        192 this.y = Math.round(this.y);
        193 return this;
        194};
        195
        196
        197/**
        198 * Translates this box by the given offsets. If a {@code goog.math.Coordinate}
        199 * is given, then the x and y values are translated by the coordinate's x and y.
        200 * Otherwise, x and y are translated by {@code tx} and {@code opt_ty}
        201 * respectively.
        202 * @param {number|goog.math.Coordinate} tx The value to translate x by or the
        203 * the coordinate to translate this coordinate by.
        204 * @param {number=} opt_ty The value to translate y by.
        205 * @return {!goog.math.Coordinate} This coordinate after translating.
        206 */
        207goog.math.Coordinate.prototype.translate = function(tx, opt_ty) {
        208 if (tx instanceof goog.math.Coordinate) {
        209 this.x += tx.x;
        210 this.y += tx.y;
        211 } else {
        212 this.x += tx;
        213 if (goog.isNumber(opt_ty)) {
        214 this.y += opt_ty;
        215 }
        216 }
        217 return this;
        218};
        219
        220
        221/**
        222 * Scales this coordinate by the given scale factors. The x and y values are
        223 * scaled by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy}
        224 * is not given, then {@code sx} is used for both x and y.
        225 * @param {number} sx The scale factor to use for the x dimension.
        226 * @param {number=} opt_sy The scale factor to use for the y dimension.
        227 * @return {!goog.math.Coordinate} This coordinate after scaling.
        228 */
        229goog.math.Coordinate.prototype.scale = function(sx, opt_sy) {
        230 var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
        231 this.x *= sx;
        232 this.y *= sy;
        233 return this;
        234};
        235
        236
        237/**
        238 * Rotates this coordinate clockwise about the origin (or, optionally, the given
        239 * center) by the given angle, in radians.
        240 * @param {number} radians The angle by which to rotate this coordinate
        241 * clockwise about the given center, in radians.
        242 * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
        243 * to (0, 0) if not given.
        244 */
        245goog.math.Coordinate.prototype.rotateRadians = function(radians, opt_center) {
        246 var center = opt_center || new goog.math.Coordinate(0, 0);
        247
        248 var x = this.x;
        249 var y = this.y;
        250 var cos = Math.cos(radians);
        251 var sin = Math.sin(radians);
        252
        253 this.x = (x - center.x) * cos - (y - center.y) * sin + center.x;
        254 this.y = (x - center.x) * sin + (y - center.y) * cos + center.y;
        255};
        256
        257
        258/**
        259 * Rotates this coordinate clockwise about the origin (or, optionally, the given
        260 * center) by the given angle, in degrees.
        261 * @param {number} degrees The angle by which to rotate this coordinate
        262 * clockwise about the given center, in degrees.
        263 * @param {!goog.math.Coordinate=} opt_center The center of rotation. Defaults
        264 * to (0, 0) if not given.
        265 */
        266goog.math.Coordinate.prototype.rotateDegrees = function(degrees, opt_center) {
        267 this.rotateRadians(goog.math.toRadians(degrees), opt_center);
        268};
        \ No newline at end of file diff --git a/docs/source/lib/goog/math/math.js.src.html b/docs/source/lib/goog/math/math.js.src.html new file mode 100644 index 0000000..e4dc5c4 --- /dev/null +++ b/docs/source/lib/goog/math/math.js.src.html @@ -0,0 +1 @@ +math.js

        lib/goog/math/math.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Additional mathematical functions.
        17 */
        18
        19goog.provide('goog.math');
        20
        21goog.require('goog.array');
        22goog.require('goog.asserts');
        23
        24
        25/**
        26 * Returns a random integer greater than or equal to 0 and less than {@code a}.
        27 * @param {number} a The upper bound for the random integer (exclusive).
        28 * @return {number} A random integer N such that 0 <= N < a.
        29 */
        30goog.math.randomInt = function(a) {
        31 return Math.floor(Math.random() * a);
        32};
        33
        34
        35/**
        36 * Returns a random number greater than or equal to {@code a} and less than
        37 * {@code b}.
        38 * @param {number} a The lower bound for the random number (inclusive).
        39 * @param {number} b The upper bound for the random number (exclusive).
        40 * @return {number} A random number N such that a <= N < b.
        41 */
        42goog.math.uniformRandom = function(a, b) {
        43 return a + Math.random() * (b - a);
        44};
        45
        46
        47/**
        48 * Takes a number and clamps it to within the provided bounds.
        49 * @param {number} value The input number.
        50 * @param {number} min The minimum value to return.
        51 * @param {number} max The maximum value to return.
        52 * @return {number} The input number if it is within bounds, or the nearest
        53 * number within the bounds.
        54 */
        55goog.math.clamp = function(value, min, max) {
        56 return Math.min(Math.max(value, min), max);
        57};
        58
        59
        60/**
        61 * The % operator in JavaScript returns the remainder of a / b, but differs from
        62 * some other languages in that the result will have the same sign as the
        63 * dividend. For example, -1 % 8 == -1, whereas in some other languages
        64 * (such as Python) the result would be 7. This function emulates the more
        65 * correct modulo behavior, which is useful for certain applications such as
        66 * calculating an offset index in a circular list.
        67 *
        68 * @param {number} a The dividend.
        69 * @param {number} b The divisor.
        70 * @return {number} a % b where the result is between 0 and b (either 0 <= x < b
        71 * or b < x <= 0, depending on the sign of b).
        72 */
        73goog.math.modulo = function(a, b) {
        74 var r = a % b;
        75 // If r and b differ in sign, add b to wrap the result to the correct sign.
        76 return (r * b < 0) ? r + b : r;
        77};
        78
        79
        80/**
        81 * Performs linear interpolation between values a and b. Returns the value
        82 * between a and b proportional to x (when x is between 0 and 1. When x is
        83 * outside this range, the return value is a linear extrapolation).
        84 * @param {number} a A number.
        85 * @param {number} b A number.
        86 * @param {number} x The proportion between a and b.
        87 * @return {number} The interpolated value between a and b.
        88 */
        89goog.math.lerp = function(a, b, x) {
        90 return a + x * (b - a);
        91};
        92
        93
        94/**
        95 * Tests whether the two values are equal to each other, within a certain
        96 * tolerance to adjust for floating point errors.
        97 * @param {number} a A number.
        98 * @param {number} b A number.
        99 * @param {number=} opt_tolerance Optional tolerance range. Defaults
        100 * to 0.000001. If specified, should be greater than 0.
        101 * @return {boolean} Whether {@code a} and {@code b} are nearly equal.
        102 */
        103goog.math.nearlyEquals = function(a, b, opt_tolerance) {
        104 return Math.abs(a - b) <= (opt_tolerance || 0.000001);
        105};
        106
        107
        108// TODO(user): Rename to normalizeAngle, retaining old name as deprecated
        109// alias.
        110/**
        111 * Normalizes an angle to be in range [0-360). Angles outside this range will
        112 * be normalized to be the equivalent angle with that range.
        113 * @param {number} angle Angle in degrees.
        114 * @return {number} Standardized angle.
        115 */
        116goog.math.standardAngle = function(angle) {
        117 return goog.math.modulo(angle, 360);
        118};
        119
        120
        121/**
        122 * Normalizes an angle to be in range [0-2*PI). Angles outside this range will
        123 * be normalized to be the equivalent angle with that range.
        124 * @param {number} angle Angle in radians.
        125 * @return {number} Standardized angle.
        126 */
        127goog.math.standardAngleInRadians = function(angle) {
        128 return goog.math.modulo(angle, 2 * Math.PI);
        129};
        130
        131
        132/**
        133 * Converts degrees to radians.
        134 * @param {number} angleDegrees Angle in degrees.
        135 * @return {number} Angle in radians.
        136 */
        137goog.math.toRadians = function(angleDegrees) {
        138 return angleDegrees * Math.PI / 180;
        139};
        140
        141
        142/**
        143 * Converts radians to degrees.
        144 * @param {number} angleRadians Angle in radians.
        145 * @return {number} Angle in degrees.
        146 */
        147goog.math.toDegrees = function(angleRadians) {
        148 return angleRadians * 180 / Math.PI;
        149};
        150
        151
        152/**
        153 * For a given angle and radius, finds the X portion of the offset.
        154 * @param {number} degrees Angle in degrees (zero points in +X direction).
        155 * @param {number} radius Radius.
        156 * @return {number} The x-distance for the angle and radius.
        157 */
        158goog.math.angleDx = function(degrees, radius) {
        159 return radius * Math.cos(goog.math.toRadians(degrees));
        160};
        161
        162
        163/**
        164 * For a given angle and radius, finds the Y portion of the offset.
        165 * @param {number} degrees Angle in degrees (zero points in +X direction).
        166 * @param {number} radius Radius.
        167 * @return {number} The y-distance for the angle and radius.
        168 */
        169goog.math.angleDy = function(degrees, radius) {
        170 return radius * Math.sin(goog.math.toRadians(degrees));
        171};
        172
        173
        174/**
        175 * Computes the angle between two points (x1,y1) and (x2,y2).
        176 * Angle zero points in the +X direction, 90 degrees points in the +Y
        177 * direction (down) and from there we grow clockwise towards 360 degrees.
        178 * @param {number} x1 x of first point.
        179 * @param {number} y1 y of first point.
        180 * @param {number} x2 x of second point.
        181 * @param {number} y2 y of second point.
        182 * @return {number} Standardized angle in degrees of the vector from
        183 * x1,y1 to x2,y2.
        184 */
        185goog.math.angle = function(x1, y1, x2, y2) {
        186 return goog.math.standardAngle(goog.math.toDegrees(Math.atan2(y2 - y1,
        187 x2 - x1)));
        188};
        189
        190
        191/**
        192 * Computes the difference between startAngle and endAngle (angles in degrees).
        193 * @param {number} startAngle Start angle in degrees.
        194 * @param {number} endAngle End angle in degrees.
        195 * @return {number} The number of degrees that when added to
        196 * startAngle will result in endAngle. Positive numbers mean that the
        197 * direction is clockwise. Negative numbers indicate a counter-clockwise
        198 * direction.
        199 * The shortest route (clockwise vs counter-clockwise) between the angles
        200 * is used.
        201 * When the difference is 180 degrees, the function returns 180 (not -180)
        202 * angleDifference(30, 40) is 10, and angleDifference(40, 30) is -10.
        203 * angleDifference(350, 10) is 20, and angleDifference(10, 350) is -20.
        204 */
        205goog.math.angleDifference = function(startAngle, endAngle) {
        206 var d = goog.math.standardAngle(endAngle) -
        207 goog.math.standardAngle(startAngle);
        208 if (d > 180) {
        209 d = d - 360;
        210 } else if (d <= -180) {
        211 d = 360 + d;
        212 }
        213 return d;
        214};
        215
        216
        217/**
        218 * Returns the sign of a number as per the "sign" or "signum" function.
        219 * @param {number} x The number to take the sign of.
        220 * @return {number} -1 when negative, 1 when positive, 0 when 0. Preserves
        221 * signed zeros and NaN.
        222 */
        223goog.math.sign = Math.sign || function(x) {
        224 if (x > 0) {
        225 return 1;
        226 }
        227 if (x < 0) {
        228 return -1;
        229 }
        230 return x; // Preserves signed zeros and NaN.
        231};
        232
        233
        234/**
        235 * JavaScript implementation of Longest Common Subsequence problem.
        236 * http://en.wikipedia.org/wiki/Longest_common_subsequence
        237 *
        238 * Returns the longest possible array that is subarray of both of given arrays.
        239 *
        240 * @param {Array<Object>} array1 First array of objects.
        241 * @param {Array<Object>} array2 Second array of objects.
        242 * @param {Function=} opt_compareFn Function that acts as a custom comparator
        243 * for the array ojects. Function should return true if objects are equal,
        244 * otherwise false.
        245 * @param {Function=} opt_collectorFn Function used to decide what to return
        246 * as a result subsequence. It accepts 2 arguments: index of common element
        247 * in the first array and index in the second. The default function returns
        248 * element from the first array.
        249 * @return {!Array<Object>} A list of objects that are common to both arrays
        250 * such that there is no common subsequence with size greater than the
        251 * length of the list.
        252 */
        253goog.math.longestCommonSubsequence = function(
        254 array1, array2, opt_compareFn, opt_collectorFn) {
        255
        256 var compare = opt_compareFn || function(a, b) {
        257 return a == b;
        258 };
        259
        260 var collect = opt_collectorFn || function(i1, i2) {
        261 return array1[i1];
        262 };
        263
        264 var length1 = array1.length;
        265 var length2 = array2.length;
        266
        267 var arr = [];
        268 for (var i = 0; i < length1 + 1; i++) {
        269 arr[i] = [];
        270 arr[i][0] = 0;
        271 }
        272
        273 for (var j = 0; j < length2 + 1; j++) {
        274 arr[0][j] = 0;
        275 }
        276
        277 for (i = 1; i <= length1; i++) {
        278 for (j = 1; j <= length2; j++) {
        279 if (compare(array1[i - 1], array2[j - 1])) {
        280 arr[i][j] = arr[i - 1][j - 1] + 1;
        281 } else {
        282 arr[i][j] = Math.max(arr[i - 1][j], arr[i][j - 1]);
        283 }
        284 }
        285 }
        286
        287 // Backtracking
        288 var result = [];
        289 var i = length1, j = length2;
        290 while (i > 0 && j > 0) {
        291 if (compare(array1[i - 1], array2[j - 1])) {
        292 result.unshift(collect(i - 1, j - 1));
        293 i--;
        294 j--;
        295 } else {
        296 if (arr[i - 1][j] > arr[i][j - 1]) {
        297 i--;
        298 } else {
        299 j--;
        300 }
        301 }
        302 }
        303
        304 return result;
        305};
        306
        307
        308/**
        309 * Returns the sum of the arguments.
        310 * @param {...number} var_args Numbers to add.
        311 * @return {number} The sum of the arguments (0 if no arguments were provided,
        312 * {@code NaN} if any of the arguments is not a valid number).
        313 */
        314goog.math.sum = function(var_args) {
        315 return /** @type {number} */ (goog.array.reduce(arguments,
        316 function(sum, value) {
        317 return sum + value;
        318 }, 0));
        319};
        320
        321
        322/**
        323 * Returns the arithmetic mean of the arguments.
        324 * @param {...number} var_args Numbers to average.
        325 * @return {number} The average of the arguments ({@code NaN} if no arguments
        326 * were provided or any of the arguments is not a valid number).
        327 */
        328goog.math.average = function(var_args) {
        329 return goog.math.sum.apply(null, arguments) / arguments.length;
        330};
        331
        332
        333/**
        334 * Returns the unbiased sample variance of the arguments. For a definition,
        335 * see e.g. http://en.wikipedia.org/wiki/Variance
        336 * @param {...number} var_args Number samples to analyze.
        337 * @return {number} The unbiased sample variance of the arguments (0 if fewer
        338 * than two samples were provided, or {@code NaN} if any of the samples is
        339 * not a valid number).
        340 */
        341goog.math.sampleVariance = function(var_args) {
        342 var sampleSize = arguments.length;
        343 if (sampleSize < 2) {
        344 return 0;
        345 }
        346
        347 var mean = goog.math.average.apply(null, arguments);
        348 var variance = goog.math.sum.apply(null, goog.array.map(arguments,
        349 function(val) {
        350 return Math.pow(val - mean, 2);
        351 })) / (sampleSize - 1);
        352
        353 return variance;
        354};
        355
        356
        357/**
        358 * Returns the sample standard deviation of the arguments. For a definition of
        359 * sample standard deviation, see e.g.
        360 * http://en.wikipedia.org/wiki/Standard_deviation
        361 * @param {...number} var_args Number samples to analyze.
        362 * @return {number} The sample standard deviation of the arguments (0 if fewer
        363 * than two samples were provided, or {@code NaN} if any of the samples is
        364 * not a valid number).
        365 */
        366goog.math.standardDeviation = function(var_args) {
        367 return Math.sqrt(goog.math.sampleVariance.apply(null, arguments));
        368};
        369
        370
        371/**
        372 * Returns whether the supplied number represents an integer, i.e. that is has
        373 * no fractional component. No range-checking is performed on the number.
        374 * @param {number} num The number to test.
        375 * @return {boolean} Whether {@code num} is an integer.
        376 */
        377goog.math.isInt = function(num) {
        378 return isFinite(num) && num % 1 == 0;
        379};
        380
        381
        382/**
        383 * Returns whether the supplied number is finite and not NaN.
        384 * @param {number} num The number to test.
        385 * @return {boolean} Whether {@code num} is a finite number.
        386 */
        387goog.math.isFiniteNumber = function(num) {
        388 return isFinite(num) && !isNaN(num);
        389};
        390
        391
        392/**
        393 * @param {number} num The number to test.
        394 * @return {boolean} Whether it is negative zero.
        395 */
        396goog.math.isNegativeZero = function(num) {
        397 return num == 0 && 1 / num < 0;
        398};
        399
        400
        401/**
        402 * Returns the precise value of floor(log10(num)).
        403 * Simpler implementations didn't work because of floating point rounding
        404 * errors. For example
        405 * <ul>
        406 * <li>Math.floor(Math.log(num) / Math.LN10) is off by one for num == 1e+3.
        407 * <li>Math.floor(Math.log(num) * Math.LOG10E) is off by one for num == 1e+15.
        408 * <li>Math.floor(Math.log10(num)) is off by one for num == 1e+15 - 1.
        409 * </ul>
        410 * @param {number} num A floating point number.
        411 * @return {number} Its logarithm to base 10 rounded down to the nearest
        412 * integer if num > 0. -Infinity if num == 0. NaN if num < 0.
        413 */
        414goog.math.log10Floor = function(num) {
        415 if (num > 0) {
        416 var x = Math.round(Math.log(num) * Math.LOG10E);
        417 return x - (parseFloat('1e' + x) > num);
        418 }
        419 return num == 0 ? -Infinity : NaN;
        420};
        421
        422
        423/**
        424 * A tweaked variant of {@code Math.floor} which tolerates if the passed number
        425 * is infinitesimally smaller than the closest integer. It often happens with
        426 * the results of floating point calculations because of the finite precision
        427 * of the intermediate results. For example {@code Math.floor(Math.log(1000) /
        428 * Math.LN10) == 2}, not 3 as one would expect.
        429 * @param {number} num A number.
        430 * @param {number=} opt_epsilon An infinitesimally small positive number, the
        431 * rounding error to tolerate.
        432 * @return {number} The largest integer less than or equal to {@code num}.
        433 */
        434goog.math.safeFloor = function(num, opt_epsilon) {
        435 goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
        436 return Math.floor(num + (opt_epsilon || 2e-15));
        437};
        438
        439
        440/**
        441 * A tweaked variant of {@code Math.ceil}. See {@code goog.math.safeFloor} for
        442 * details.
        443 * @param {number} num A number.
        444 * @param {number=} opt_epsilon An infinitesimally small positive number, the
        445 * rounding error to tolerate.
        446 * @return {number} The smallest integer greater than or equal to {@code num}.
        447 */
        448goog.math.safeCeil = function(num, opt_epsilon) {
        449 goog.asserts.assert(!goog.isDef(opt_epsilon) || opt_epsilon > 0);
        450 return Math.ceil(num - (opt_epsilon || 2e-15));
        451};
        \ No newline at end of file diff --git a/docs/source/lib/goog/math/rect.js.src.html b/docs/source/lib/goog/math/rect.js.src.html new file mode 100644 index 0000000..0ee35fb --- /dev/null +++ b/docs/source/lib/goog/math/rect.js.src.html @@ -0,0 +1 @@ +rect.js

        lib/goog/math/rect.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A utility class for representing rectangles.
        17 */
        18
        19goog.provide('goog.math.Rect');
        20
        21goog.require('goog.math.Box');
        22goog.require('goog.math.Coordinate');
        23goog.require('goog.math.Size');
        24
        25
        26
        27/**
        28 * Class for representing rectangular regions.
        29 * @param {number} x Left.
        30 * @param {number} y Top.
        31 * @param {number} w Width.
        32 * @param {number} h Height.
        33 * @struct
        34 * @constructor
        35 */
        36goog.math.Rect = function(x, y, w, h) {
        37 /** @type {number} */
        38 this.left = x;
        39
        40 /** @type {number} */
        41 this.top = y;
        42
        43 /** @type {number} */
        44 this.width = w;
        45
        46 /** @type {number} */
        47 this.height = h;
        48};
        49
        50
        51/**
        52 * @return {!goog.math.Rect} A new copy of this Rectangle.
        53 */
        54goog.math.Rect.prototype.clone = function() {
        55 return new goog.math.Rect(this.left, this.top, this.width, this.height);
        56};
        57
        58
        59/**
        60 * Returns a new Box object with the same position and dimensions as this
        61 * rectangle.
        62 * @return {!goog.math.Box} A new Box representation of this Rectangle.
        63 */
        64goog.math.Rect.prototype.toBox = function() {
        65 var right = this.left + this.width;
        66 var bottom = this.top + this.height;
        67 return new goog.math.Box(this.top,
        68 right,
        69 bottom,
        70 this.left);
        71};
        72
        73
        74/**
        75 * Creates a new Rect object with the position and size given.
        76 * @param {!goog.math.Coordinate} position The top-left coordinate of the Rect
        77 * @param {!goog.math.Size} size The size of the Rect
        78 * @return {!goog.math.Rect} A new Rect initialized with the given position and
        79 * size.
        80 */
        81goog.math.Rect.createFromPositionAndSize = function(position, size) {
        82 return new goog.math.Rect(position.x, position.y, size.width, size.height);
        83};
        84
        85
        86/**
        87 * Creates a new Rect object with the same position and dimensions as a given
        88 * Box. Note that this is only the inverse of toBox if left/top are defined.
        89 * @param {goog.math.Box} box A box.
        90 * @return {!goog.math.Rect} A new Rect initialized with the box's position
        91 * and size.
        92 */
        93goog.math.Rect.createFromBox = function(box) {
        94 return new goog.math.Rect(box.left, box.top,
        95 box.right - box.left, box.bottom - box.top);
        96};
        97
        98
        99if (goog.DEBUG) {
        100 /**
        101 * Returns a nice string representing size and dimensions of rectangle.
        102 * @return {string} In the form (50, 73 - 75w x 25h).
        103 * @override
        104 */
        105 goog.math.Rect.prototype.toString = function() {
        106 return '(' + this.left + ', ' + this.top + ' - ' + this.width + 'w x ' +
        107 this.height + 'h)';
        108 };
        109}
        110
        111
        112/**
        113 * Compares rectangles for equality.
        114 * @param {goog.math.Rect} a A Rectangle.
        115 * @param {goog.math.Rect} b A Rectangle.
        116 * @return {boolean} True iff the rectangles have the same left, top, width,
        117 * and height, or if both are null.
        118 */
        119goog.math.Rect.equals = function(a, b) {
        120 if (a == b) {
        121 return true;
        122 }
        123 if (!a || !b) {
        124 return false;
        125 }
        126 return a.left == b.left && a.width == b.width &&
        127 a.top == b.top && a.height == b.height;
        128};
        129
        130
        131/**
        132 * Computes the intersection of this rectangle and the rectangle parameter. If
        133 * there is no intersection, returns false and leaves this rectangle as is.
        134 * @param {goog.math.Rect} rect A Rectangle.
        135 * @return {boolean} True iff this rectangle intersects with the parameter.
        136 */
        137goog.math.Rect.prototype.intersection = function(rect) {
        138 var x0 = Math.max(this.left, rect.left);
        139 var x1 = Math.min(this.left + this.width, rect.left + rect.width);
        140
        141 if (x0 <= x1) {
        142 var y0 = Math.max(this.top, rect.top);
        143 var y1 = Math.min(this.top + this.height, rect.top + rect.height);
        144
        145 if (y0 <= y1) {
        146 this.left = x0;
        147 this.top = y0;
        148 this.width = x1 - x0;
        149 this.height = y1 - y0;
        150
        151 return true;
        152 }
        153 }
        154 return false;
        155};
        156
        157
        158/**
        159 * Returns the intersection of two rectangles. Two rectangles intersect if they
        160 * touch at all, for example, two zero width and height rectangles would
        161 * intersect if they had the same top and left.
        162 * @param {goog.math.Rect} a A Rectangle.
        163 * @param {goog.math.Rect} b A Rectangle.
        164 * @return {goog.math.Rect} A new intersection rect (even if width and height
        165 * are 0), or null if there is no intersection.
        166 */
        167goog.math.Rect.intersection = function(a, b) {
        168 // There is no nice way to do intersection via a clone, because any such
        169 // clone might be unnecessary if this function returns null. So, we duplicate
        170 // code from above.
        171
        172 var x0 = Math.max(a.left, b.left);
        173 var x1 = Math.min(a.left + a.width, b.left + b.width);
        174
        175 if (x0 <= x1) {
        176 var y0 = Math.max(a.top, b.top);
        177 var y1 = Math.min(a.top + a.height, b.top + b.height);
        178
        179 if (y0 <= y1) {
        180 return new goog.math.Rect(x0, y0, x1 - x0, y1 - y0);
        181 }
        182 }
        183 return null;
        184};
        185
        186
        187/**
        188 * Returns whether two rectangles intersect. Two rectangles intersect if they
        189 * touch at all, for example, two zero width and height rectangles would
        190 * intersect if they had the same top and left.
        191 * @param {goog.math.Rect} a A Rectangle.
        192 * @param {goog.math.Rect} b A Rectangle.
        193 * @return {boolean} Whether a and b intersect.
        194 */
        195goog.math.Rect.intersects = function(a, b) {
        196 return (a.left <= b.left + b.width && b.left <= a.left + a.width &&
        197 a.top <= b.top + b.height && b.top <= a.top + a.height);
        198};
        199
        200
        201/**
        202 * Returns whether a rectangle intersects this rectangle.
        203 * @param {goog.math.Rect} rect A rectangle.
        204 * @return {boolean} Whether rect intersects this rectangle.
        205 */
        206goog.math.Rect.prototype.intersects = function(rect) {
        207 return goog.math.Rect.intersects(this, rect);
        208};
        209
        210
        211/**
        212 * Computes the difference regions between two rectangles. The return value is
        213 * an array of 0 to 4 rectangles defining the remaining regions of the first
        214 * rectangle after the second has been subtracted.
        215 * @param {goog.math.Rect} a A Rectangle.
        216 * @param {goog.math.Rect} b A Rectangle.
        217 * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
        218 * together define the difference area of rectangle a minus rectangle b.
        219 */
        220goog.math.Rect.difference = function(a, b) {
        221 var intersection = goog.math.Rect.intersection(a, b);
        222 if (!intersection || !intersection.height || !intersection.width) {
        223 return [a.clone()];
        224 }
        225
        226 var result = [];
        227
        228 var top = a.top;
        229 var height = a.height;
        230
        231 var ar = a.left + a.width;
        232 var ab = a.top + a.height;
        233
        234 var br = b.left + b.width;
        235 var bb = b.top + b.height;
        236
        237 // Subtract off any area on top where A extends past B
        238 if (b.top > a.top) {
        239 result.push(new goog.math.Rect(a.left, a.top, a.width, b.top - a.top));
        240 top = b.top;
        241 // If we're moving the top down, we also need to subtract the height diff.
        242 height -= b.top - a.top;
        243 }
        244 // Subtract off any area on bottom where A extends past B
        245 if (bb < ab) {
        246 result.push(new goog.math.Rect(a.left, bb, a.width, ab - bb));
        247 height = bb - top;
        248 }
        249 // Subtract any area on left where A extends past B
        250 if (b.left > a.left) {
        251 result.push(new goog.math.Rect(a.left, top, b.left - a.left, height));
        252 }
        253 // Subtract any area on right where A extends past B
        254 if (br < ar) {
        255 result.push(new goog.math.Rect(br, top, ar - br, height));
        256 }
        257
        258 return result;
        259};
        260
        261
        262/**
        263 * Computes the difference regions between this rectangle and {@code rect}. The
        264 * return value is an array of 0 to 4 rectangles defining the remaining regions
        265 * of this rectangle after the other has been subtracted.
        266 * @param {goog.math.Rect} rect A Rectangle.
        267 * @return {!Array<!goog.math.Rect>} An array with 0 to 4 rectangles which
        268 * together define the difference area of rectangle a minus rectangle b.
        269 */
        270goog.math.Rect.prototype.difference = function(rect) {
        271 return goog.math.Rect.difference(this, rect);
        272};
        273
        274
        275/**
        276 * Expand this rectangle to also include the area of the given rectangle.
        277 * @param {goog.math.Rect} rect The other rectangle.
        278 */
        279goog.math.Rect.prototype.boundingRect = function(rect) {
        280 // We compute right and bottom before we change left and top below.
        281 var right = Math.max(this.left + this.width, rect.left + rect.width);
        282 var bottom = Math.max(this.top + this.height, rect.top + rect.height);
        283
        284 this.left = Math.min(this.left, rect.left);
        285 this.top = Math.min(this.top, rect.top);
        286
        287 this.width = right - this.left;
        288 this.height = bottom - this.top;
        289};
        290
        291
        292/**
        293 * Returns a new rectangle which completely contains both input rectangles.
        294 * @param {goog.math.Rect} a A rectangle.
        295 * @param {goog.math.Rect} b A rectangle.
        296 * @return {goog.math.Rect} A new bounding rect, or null if either rect is
        297 * null.
        298 */
        299goog.math.Rect.boundingRect = function(a, b) {
        300 if (!a || !b) {
        301 return null;
        302 }
        303
        304 var clone = a.clone();
        305 clone.boundingRect(b);
        306
        307 return clone;
        308};
        309
        310
        311/**
        312 * Tests whether this rectangle entirely contains another rectangle or
        313 * coordinate.
        314 *
        315 * @param {goog.math.Rect|goog.math.Coordinate} another The rectangle or
        316 * coordinate to test for containment.
        317 * @return {boolean} Whether this rectangle contains given rectangle or
        318 * coordinate.
        319 */
        320goog.math.Rect.prototype.contains = function(another) {
        321 if (another instanceof goog.math.Rect) {
        322 return this.left <= another.left &&
        323 this.left + this.width >= another.left + another.width &&
        324 this.top <= another.top &&
        325 this.top + this.height >= another.top + another.height;
        326 } else { // (another instanceof goog.math.Coordinate)
        327 return another.x >= this.left &&
        328 another.x <= this.left + this.width &&
        329 another.y >= this.top &&
        330 another.y <= this.top + this.height;
        331 }
        332};
        333
        334
        335/**
        336 * @param {!goog.math.Coordinate} point A coordinate.
        337 * @return {number} The squared distance between the point and the closest
        338 * point inside the rectangle. Returns 0 if the point is inside the
        339 * rectangle.
        340 */
        341goog.math.Rect.prototype.squaredDistance = function(point) {
        342 var dx = point.x < this.left ?
        343 this.left - point.x : Math.max(point.x - (this.left + this.width), 0);
        344 var dy = point.y < this.top ?
        345 this.top - point.y : Math.max(point.y - (this.top + this.height), 0);
        346 return dx * dx + dy * dy;
        347};
        348
        349
        350/**
        351 * @param {!goog.math.Coordinate} point A coordinate.
        352 * @return {number} The distance between the point and the closest point
        353 * inside the rectangle. Returns 0 if the point is inside the rectangle.
        354 */
        355goog.math.Rect.prototype.distance = function(point) {
        356 return Math.sqrt(this.squaredDistance(point));
        357};
        358
        359
        360/**
        361 * @return {!goog.math.Size} The size of this rectangle.
        362 */
        363goog.math.Rect.prototype.getSize = function() {
        364 return new goog.math.Size(this.width, this.height);
        365};
        366
        367
        368/**
        369 * @return {!goog.math.Coordinate} A new coordinate for the top-left corner of
        370 * the rectangle.
        371 */
        372goog.math.Rect.prototype.getTopLeft = function() {
        373 return new goog.math.Coordinate(this.left, this.top);
        374};
        375
        376
        377/**
        378 * @return {!goog.math.Coordinate} A new coordinate for the center of the
        379 * rectangle.
        380 */
        381goog.math.Rect.prototype.getCenter = function() {
        382 return new goog.math.Coordinate(
        383 this.left + this.width / 2, this.top + this.height / 2);
        384};
        385
        386
        387/**
        388 * @return {!goog.math.Coordinate} A new coordinate for the bottom-right corner
        389 * of the rectangle.
        390 */
        391goog.math.Rect.prototype.getBottomRight = function() {
        392 return new goog.math.Coordinate(
        393 this.left + this.width, this.top + this.height);
        394};
        395
        396
        397/**
        398 * Rounds the fields to the next larger integer values.
        399 * @return {!goog.math.Rect} This rectangle with ceil'd fields.
        400 */
        401goog.math.Rect.prototype.ceil = function() {
        402 this.left = Math.ceil(this.left);
        403 this.top = Math.ceil(this.top);
        404 this.width = Math.ceil(this.width);
        405 this.height = Math.ceil(this.height);
        406 return this;
        407};
        408
        409
        410/**
        411 * Rounds the fields to the next smaller integer values.
        412 * @return {!goog.math.Rect} This rectangle with floored fields.
        413 */
        414goog.math.Rect.prototype.floor = function() {
        415 this.left = Math.floor(this.left);
        416 this.top = Math.floor(this.top);
        417 this.width = Math.floor(this.width);
        418 this.height = Math.floor(this.height);
        419 return this;
        420};
        421
        422
        423/**
        424 * Rounds the fields to nearest integer values.
        425 * @return {!goog.math.Rect} This rectangle with rounded fields.
        426 */
        427goog.math.Rect.prototype.round = function() {
        428 this.left = Math.round(this.left);
        429 this.top = Math.round(this.top);
        430 this.width = Math.round(this.width);
        431 this.height = Math.round(this.height);
        432 return this;
        433};
        434
        435
        436/**
        437 * Translates this rectangle by the given offsets. If a
        438 * {@code goog.math.Coordinate} is given, then the left and top values are
        439 * translated by the coordinate's x and y values. Otherwise, top and left are
        440 * translated by {@code tx} and {@code opt_ty} respectively.
        441 * @param {number|goog.math.Coordinate} tx The value to translate left by or the
        442 * the coordinate to translate this rect by.
        443 * @param {number=} opt_ty The value to translate top by.
        444 * @return {!goog.math.Rect} This rectangle after translating.
        445 */
        446goog.math.Rect.prototype.translate = function(tx, opt_ty) {
        447 if (tx instanceof goog.math.Coordinate) {
        448 this.left += tx.x;
        449 this.top += tx.y;
        450 } else {
        451 this.left += tx;
        452 if (goog.isNumber(opt_ty)) {
        453 this.top += opt_ty;
        454 }
        455 }
        456 return this;
        457};
        458
        459
        460/**
        461 * Scales this rectangle by the given scale factors. The left and width values
        462 * are scaled by {@code sx} and the top and height values are scaled by
        463 * {@code opt_sy}. If {@code opt_sy} is not given, then all fields are scaled
        464 * by {@code sx}.
        465 * @param {number} sx The scale factor to use for the x dimension.
        466 * @param {number=} opt_sy The scale factor to use for the y dimension.
        467 * @return {!goog.math.Rect} This rectangle after scaling.
        468 */
        469goog.math.Rect.prototype.scale = function(sx, opt_sy) {
        470 var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
        471 this.left *= sx;
        472 this.width *= sx;
        473 this.top *= sy;
        474 this.height *= sy;
        475 return this;
        476};
        \ No newline at end of file diff --git a/docs/source/lib/goog/math/size.js.src.html b/docs/source/lib/goog/math/size.js.src.html new file mode 100644 index 0000000..d35a7f5 --- /dev/null +++ b/docs/source/lib/goog/math/size.js.src.html @@ -0,0 +1 @@ +size.js

        lib/goog/math/size.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A utility class for representing two-dimensional sizes.
        17 * @author brenneman@google.com (Shawn Brenneman)
        18 */
        19
        20
        21goog.provide('goog.math.Size');
        22
        23
        24
        25/**
        26 * Class for representing sizes consisting of a width and height. Undefined
        27 * width and height support is deprecated and results in compiler warning.
        28 * @param {number} width Width.
        29 * @param {number} height Height.
        30 * @struct
        31 * @constructor
        32 */
        33goog.math.Size = function(width, height) {
        34 /**
        35 * Width
        36 * @type {number}
        37 */
        38 this.width = width;
        39
        40 /**
        41 * Height
        42 * @type {number}
        43 */
        44 this.height = height;
        45};
        46
        47
        48/**
        49 * Compares sizes for equality.
        50 * @param {goog.math.Size} a A Size.
        51 * @param {goog.math.Size} b A Size.
        52 * @return {boolean} True iff the sizes have equal widths and equal
        53 * heights, or if both are null.
        54 */
        55goog.math.Size.equals = function(a, b) {
        56 if (a == b) {
        57 return true;
        58 }
        59 if (!a || !b) {
        60 return false;
        61 }
        62 return a.width == b.width && a.height == b.height;
        63};
        64
        65
        66/**
        67 * @return {!goog.math.Size} A new copy of the Size.
        68 */
        69goog.math.Size.prototype.clone = function() {
        70 return new goog.math.Size(this.width, this.height);
        71};
        72
        73
        74if (goog.DEBUG) {
        75 /**
        76 * Returns a nice string representing size.
        77 * @return {string} In the form (50 x 73).
        78 * @override
        79 */
        80 goog.math.Size.prototype.toString = function() {
        81 return '(' + this.width + ' x ' + this.height + ')';
        82 };
        83}
        84
        85
        86/**
        87 * @return {number} The longer of the two dimensions in the size.
        88 */
        89goog.math.Size.prototype.getLongest = function() {
        90 return Math.max(this.width, this.height);
        91};
        92
        93
        94/**
        95 * @return {number} The shorter of the two dimensions in the size.
        96 */
        97goog.math.Size.prototype.getShortest = function() {
        98 return Math.min(this.width, this.height);
        99};
        100
        101
        102/**
        103 * @return {number} The area of the size (width * height).
        104 */
        105goog.math.Size.prototype.area = function() {
        106 return this.width * this.height;
        107};
        108
        109
        110/**
        111 * @return {number} The perimeter of the size (width + height) * 2.
        112 */
        113goog.math.Size.prototype.perimeter = function() {
        114 return (this.width + this.height) * 2;
        115};
        116
        117
        118/**
        119 * @return {number} The ratio of the size's width to its height.
        120 */
        121goog.math.Size.prototype.aspectRatio = function() {
        122 return this.width / this.height;
        123};
        124
        125
        126/**
        127 * @return {boolean} True if the size has zero area, false if both dimensions
        128 * are non-zero numbers.
        129 */
        130goog.math.Size.prototype.isEmpty = function() {
        131 return !this.area();
        132};
        133
        134
        135/**
        136 * Clamps the width and height parameters upward to integer values.
        137 * @return {!goog.math.Size} This size with ceil'd components.
        138 */
        139goog.math.Size.prototype.ceil = function() {
        140 this.width = Math.ceil(this.width);
        141 this.height = Math.ceil(this.height);
        142 return this;
        143};
        144
        145
        146/**
        147 * @param {!goog.math.Size} target The target size.
        148 * @return {boolean} True if this Size is the same size or smaller than the
        149 * target size in both dimensions.
        150 */
        151goog.math.Size.prototype.fitsInside = function(target) {
        152 return this.width <= target.width && this.height <= target.height;
        153};
        154
        155
        156/**
        157 * Clamps the width and height parameters downward to integer values.
        158 * @return {!goog.math.Size} This size with floored components.
        159 */
        160goog.math.Size.prototype.floor = function() {
        161 this.width = Math.floor(this.width);
        162 this.height = Math.floor(this.height);
        163 return this;
        164};
        165
        166
        167/**
        168 * Rounds the width and height parameters to integer values.
        169 * @return {!goog.math.Size} This size with rounded components.
        170 */
        171goog.math.Size.prototype.round = function() {
        172 this.width = Math.round(this.width);
        173 this.height = Math.round(this.height);
        174 return this;
        175};
        176
        177
        178/**
        179 * Scales this size by the given scale factors. The width and height are scaled
        180 * by {@code sx} and {@code opt_sy} respectively. If {@code opt_sy} is not
        181 * given, then {@code sx} is used for both the width and height.
        182 * @param {number} sx The scale factor to use for the width.
        183 * @param {number=} opt_sy The scale factor to use for the height.
        184 * @return {!goog.math.Size} This Size object after scaling.
        185 */
        186goog.math.Size.prototype.scale = function(sx, opt_sy) {
        187 var sy = goog.isNumber(opt_sy) ? opt_sy : sx;
        188 this.width *= sx;
        189 this.height *= sy;
        190 return this;
        191};
        192
        193
        194/**
        195 * Uniformly scales the size to perfectly cover the dimensions of a given size.
        196 * If the size is already larger than the target, it will be scaled down to the
        197 * minimum size at which it still covers the entire target. The original aspect
        198 * ratio will be preserved.
        199 *
        200 * This function assumes that both Sizes contain strictly positive dimensions.
        201 * @param {!goog.math.Size} target The target size.
        202 * @return {!goog.math.Size} This Size object, after optional scaling.
        203 */
        204goog.math.Size.prototype.scaleToCover = function(target) {
        205 var s = this.aspectRatio() <= target.aspectRatio() ?
        206 target.width / this.width :
        207 target.height / this.height;
        208
        209 return this.scale(s);
        210};
        211
        212
        213/**
        214 * Uniformly scales the size to fit inside the dimensions of a given size. The
        215 * original aspect ratio will be preserved.
        216 *
        217 * This function assumes that both Sizes contain strictly positive dimensions.
        218 * @param {!goog.math.Size} target The target size.
        219 * @return {!goog.math.Size} This Size object, after optional scaling.
        220 */
        221goog.math.Size.prototype.scaleToFit = function(target) {
        222 var s = this.aspectRatio() > target.aspectRatio() ?
        223 target.width / this.width :
        224 target.height / this.height;
        225
        226 return this.scale(s);
        227};
        \ No newline at end of file diff --git a/docs/source/lib/goog/net/wrapperxmlhttpfactory.js.src.html b/docs/source/lib/goog/net/wrapperxmlhttpfactory.js.src.html index 000b805..ce7f2a4 100644 --- a/docs/source/lib/goog/net/wrapperxmlhttpfactory.js.src.html +++ b/docs/source/lib/goog/net/wrapperxmlhttpfactory.js.src.html @@ -1 +1 @@ -wrapperxmlhttpfactory.js

        lib/goog/net/wrapperxmlhttpfactory.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Implementation of XmlHttpFactory which allows construction from
        17 * simple factory methods.
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.WrapperXmlHttpFactory');
        22
        23goog.require('goog.net.XmlHttpFactory');
        24
        25
        26
        27/**
        28 * An xhr factory subclass which can be constructed using two factory methods.
        29 * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
        30 * with an unchanged signature.
        31 * @param {function() : !(XMLHttpRequest|GearsHttpRequest)} xhrFactory A
        32 * function which returns a new XHR object.
        33 * @param {function() : !Object} optionsFactory A function which returns the
        34 * options associated with xhr objects from this factory.
        35 * @extends {goog.net.XmlHttpFactory}
        36 * @constructor
        37 */
        38goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
        39 goog.net.XmlHttpFactory.call(this);
        40
        41 /**
        42 * XHR factory method.
        43 * @type {function() : !(XMLHttpRequest|GearsHttpRequest)}
        44 * @private
        45 */
        46 this.xhrFactory_ = xhrFactory;
        47
        48 /**
        49 * Options factory method.
        50 * @type {function() : !Object}
        51 * @private
        52 */
        53 this.optionsFactory_ = optionsFactory;
        54};
        55goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
        56
        57
        58/** @override */
        59goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
        60 return this.xhrFactory_();
        61};
        62
        63
        64/** @override */
        65goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
        66 return this.optionsFactory_();
        67};
        68
        \ No newline at end of file +wrapperxmlhttpfactory.js

        lib/goog/net/wrapperxmlhttpfactory.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Implementation of XmlHttpFactory which allows construction from
        17 * simple factory methods.
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.WrapperXmlHttpFactory');
        22
        23/** @suppress {extraRequire} Typedef. */
        24goog.require('goog.net.XhrLike');
        25goog.require('goog.net.XmlHttpFactory');
        26
        27
        28
        29/**
        30 * An xhr factory subclass which can be constructed using two factory methods.
        31 * This exists partly to allow the preservation of goog.net.XmlHttp.setFactory()
        32 * with an unchanged signature.
        33 * @param {function():!goog.net.XhrLike.OrNative} xhrFactory
        34 * A function which returns a new XHR object.
        35 * @param {function():!Object} optionsFactory A function which returns the
        36 * options associated with xhr objects from this factory.
        37 * @extends {goog.net.XmlHttpFactory}
        38 * @constructor
        39 * @final
        40 */
        41goog.net.WrapperXmlHttpFactory = function(xhrFactory, optionsFactory) {
        42 goog.net.XmlHttpFactory.call(this);
        43
        44 /**
        45 * XHR factory method.
        46 * @type {function() : !goog.net.XhrLike.OrNative}
        47 * @private
        48 */
        49 this.xhrFactory_ = xhrFactory;
        50
        51 /**
        52 * Options factory method.
        53 * @type {function() : !Object}
        54 * @private
        55 */
        56 this.optionsFactory_ = optionsFactory;
        57};
        58goog.inherits(goog.net.WrapperXmlHttpFactory, goog.net.XmlHttpFactory);
        59
        60
        61/** @override */
        62goog.net.WrapperXmlHttpFactory.prototype.createInstance = function() {
        63 return this.xhrFactory_();
        64};
        65
        66
        67/** @override */
        68goog.net.WrapperXmlHttpFactory.prototype.getOptions = function() {
        69 return this.optionsFactory_();
        70};
        71
        \ No newline at end of file diff --git a/docs/source/lib/goog/net/xhrlike.js.src.html b/docs/source/lib/goog/net/xhrlike.js.src.html new file mode 100644 index 0000000..39baf98 --- /dev/null +++ b/docs/source/lib/goog/net/xhrlike.js.src.html @@ -0,0 +1 @@ +xhrlike.js

        lib/goog/net/xhrlike.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.net.XhrLike');
        16
        17
        18
        19/**
        20 * Interface for the common parts of XMLHttpRequest.
        21 *
        22 * Mostly copied from externs/w3c_xml.js.
        23 *
        24 * @interface
        25 * @see http://www.w3.org/TR/XMLHttpRequest/
        26 */
        27goog.net.XhrLike = function() {};
        28
        29
        30/**
        31 * Typedef that refers to either native or custom-implemented XHR objects.
        32 * @typedef {!goog.net.XhrLike|!XMLHttpRequest}
        33 */
        34goog.net.XhrLike.OrNative;
        35
        36
        37/**
        38 * @type {function()|null|undefined}
        39 * @see http://www.w3.org/TR/XMLHttpRequest/#handler-xhr-onreadystatechange
        40 */
        41goog.net.XhrLike.prototype.onreadystatechange;
        42
        43
        44/**
        45 * @type {string}
        46 * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsetext-attribute
        47 */
        48goog.net.XhrLike.prototype.responseText;
        49
        50
        51/**
        52 * @type {Document}
        53 * @see http://www.w3.org/TR/XMLHttpRequest/#the-responsexml-attribute
        54 */
        55goog.net.XhrLike.prototype.responseXML;
        56
        57
        58/**
        59 * @type {number}
        60 * @see http://www.w3.org/TR/XMLHttpRequest/#readystate
        61 */
        62goog.net.XhrLike.prototype.readyState;
        63
        64
        65/**
        66 * @type {number}
        67 * @see http://www.w3.org/TR/XMLHttpRequest/#status
        68 */
        69goog.net.XhrLike.prototype.status;
        70
        71
        72/**
        73 * @type {string}
        74 * @see http://www.w3.org/TR/XMLHttpRequest/#statustext
        75 */
        76goog.net.XhrLike.prototype.statusText;
        77
        78
        79/**
        80 * @param {string} method
        81 * @param {string} url
        82 * @param {?boolean=} opt_async
        83 * @param {?string=} opt_user
        84 * @param {?string=} opt_password
        85 * @see http://www.w3.org/TR/XMLHttpRequest/#the-open()-method
        86 */
        87goog.net.XhrLike.prototype.open = function(method, url, opt_async, opt_user,
        88 opt_password) {};
        89
        90
        91/**
        92 * @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=} opt_data
        93 * @see http://www.w3.org/TR/XMLHttpRequest/#the-send()-method
        94 */
        95goog.net.XhrLike.prototype.send = function(opt_data) {};
        96
        97
        98/**
        99 * @see http://www.w3.org/TR/XMLHttpRequest/#the-abort()-method
        100 */
        101goog.net.XhrLike.prototype.abort = function() {};
        102
        103
        104/**
        105 * @param {string} header
        106 * @param {string} value
        107 * @see http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method
        108 */
        109goog.net.XhrLike.prototype.setRequestHeader = function(header, value) {};
        110
        111
        112/**
        113 * @param {string} header
        114 * @return {string}
        115 * @see http://www.w3.org/TR/XMLHttpRequest/#the-getresponseheader()-method
        116 */
        117goog.net.XhrLike.prototype.getResponseHeader = function(header) {};
        118
        119
        120/**
        121 * @return {string}
        122 * @see http://www.w3.org/TR/XMLHttpRequest/#the-getallresponseheaders()-method
        123 */
        124goog.net.XhrLike.prototype.getAllResponseHeaders = function() {};
        \ No newline at end of file diff --git a/docs/source/lib/goog/net/xmlhttp.js.src.html b/docs/source/lib/goog/net/xmlhttp.js.src.html index 9292738..52d0ce2 100644 --- a/docs/source/lib/goog/net/xmlhttp.js.src.html +++ b/docs/source/lib/goog/net/xmlhttp.js.src.html @@ -1 +1 @@ -xmlhttp.js

        lib/goog/net/xmlhttp.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Low level handling of XMLHttpRequest.
        17 * @author arv@google.com (Erik Arvidsson)
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.DefaultXmlHttpFactory');
        22goog.provide('goog.net.XmlHttp');
        23goog.provide('goog.net.XmlHttp.OptionType');
        24goog.provide('goog.net.XmlHttp.ReadyState');
        25
        26goog.require('goog.net.WrapperXmlHttpFactory');
        27goog.require('goog.net.XmlHttpFactory');
        28
        29
        30/**
        31 * Static class for creating XMLHttpRequest objects.
        32 * @return {!(XMLHttpRequest|GearsHttpRequest)} A new XMLHttpRequest object.
        33 */
        34goog.net.XmlHttp = function() {
        35 return goog.net.XmlHttp.factory_.createInstance();
        36};
        37
        38
        39/**
        40 * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
        41 * true strips the ActiveX probing code.
        42 */
        43goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
        44
        45
        46/**
        47 * Gets the options to use with the XMLHttpRequest objects obtained using
        48 * the static methods.
        49 * @return {Object} The options.
        50 */
        51goog.net.XmlHttp.getOptions = function() {
        52 return goog.net.XmlHttp.factory_.getOptions();
        53};
        54
        55
        56/**
        57 * Type of options that an XmlHttp object can have.
        58 * @enum {number}
        59 */
        60goog.net.XmlHttp.OptionType = {
        61 /**
        62 * Whether a goog.nullFunction should be used to clear the onreadystatechange
        63 * handler instead of null.
        64 */
        65 USE_NULL_FUNCTION: 0,
        66
        67 /**
        68 * NOTE(user): In IE if send() errors on a *local* request the readystate
        69 * is still changed to COMPLETE. We need to ignore it and allow the
        70 * try/catch around send() to pick up the error.
        71 */
        72 LOCAL_REQUEST_ERROR: 1
        73};
        74
        75
        76/**
        77 * Status constants for XMLHTTP, matches:
        78 * http://msdn.microsoft.com/library/default.asp?url=/library/
        79 * en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
        80 * @enum {number}
        81 */
        82goog.net.XmlHttp.ReadyState = {
        83 /**
        84 * Constant for when xmlhttprequest.readyState is uninitialized
        85 */
        86 UNINITIALIZED: 0,
        87
        88 /**
        89 * Constant for when xmlhttprequest.readyState is loading.
        90 */
        91 LOADING: 1,
        92
        93 /**
        94 * Constant for when xmlhttprequest.readyState is loaded.
        95 */
        96 LOADED: 2,
        97
        98 /**
        99 * Constant for when xmlhttprequest.readyState is in an interactive state.
        100 */
        101 INTERACTIVE: 3,
        102
        103 /**
        104 * Constant for when xmlhttprequest.readyState is completed
        105 */
        106 COMPLETE: 4
        107};
        108
        109
        110/**
        111 * The global factory instance for creating XMLHttpRequest objects.
        112 * @type {goog.net.XmlHttpFactory}
        113 * @private
        114 */
        115goog.net.XmlHttp.factory_;
        116
        117
        118/**
        119 * Sets the factories for creating XMLHttpRequest objects and their options.
        120 * @param {Function} factory The factory for XMLHttpRequest objects.
        121 * @param {Function} optionsFactory The factory for options.
        122 * @deprecated Use setGlobalFactory instead.
        123 */
        124goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
        125 goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
        126 /** @type {function() : !(XMLHttpRequest|GearsHttpRequest)} */ (factory),
        127 /** @type {function() : !Object}*/ (optionsFactory)));
        128};
        129
        130
        131/**
        132 * Sets the global factory object.
        133 * @param {!goog.net.XmlHttpFactory} factory New global factory object.
        134 */
        135goog.net.XmlHttp.setGlobalFactory = function(factory) {
        136 goog.net.XmlHttp.factory_ = factory;
        137};
        138
        139
        140
        141/**
        142 * Default factory to use when creating xhr objects. You probably shouldn't be
        143 * instantiating this directly, but rather using it via goog.net.XmlHttp.
        144 * @extends {goog.net.XmlHttpFactory}
        145 * @constructor
        146 */
        147goog.net.DefaultXmlHttpFactory = function() {
        148 goog.net.XmlHttpFactory.call(this);
        149};
        150goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
        151
        152
        153/** @override */
        154goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
        155 var progId = this.getProgId_();
        156 if (progId) {
        157 return new ActiveXObject(progId);
        158 } else {
        159 return new XMLHttpRequest();
        160 }
        161};
        162
        163
        164/** @override */
        165goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
        166 var progId = this.getProgId_();
        167 var options = {};
        168 if (progId) {
        169 options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
        170 options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
        171 }
        172 return options;
        173};
        174
        175
        176/**
        177 * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
        178 * @type {string|undefined}
        179 * @private
        180 */
        181goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
        182
        183
        184/**
        185 * Initialize the private state used by other functions.
        186 * @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
        187 * @private
        188 */
        189goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
        190 if (goog.net.XmlHttp.ASSUME_NATIVE_XHR) {
        191 return '';
        192 }
        193
        194 // The following blog post describes what PROG IDs to use to create the
        195 // XMLHTTP object in Internet Explorer:
        196 // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
        197 // However we do not (yet) fully trust that this will be OK for old versions
        198 // of IE on Win9x so we therefore keep the last 2.
        199 if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
        200 typeof ActiveXObject != 'undefined') {
        201 // Candidate Active X types.
        202 var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
        203 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
        204 for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
        205 var candidate = ACTIVE_X_IDENTS[i];
        206 /** @preserveTry */
        207 try {
        208 new ActiveXObject(candidate);
        209 // NOTE(user): cannot assign progid and return candidate in one line
        210 // because JSCompiler complaings: BUG 658126
        211 this.ieProgId_ = candidate;
        212 return candidate;
        213 } catch (e) {
        214 // do nothing; try next choice
        215 }
        216 }
        217
        218 // couldn't find any matches
        219 throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
        220 ' or MSXML might not be installed');
        221 }
        222
        223 return /** @type {string} */ (this.ieProgId_);
        224};
        225
        226
        227//Set the global factory to an instance of the default factory.
        228goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
        \ No newline at end of file +xmlhttp.js

        lib/goog/net/xmlhttp.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Low level handling of XMLHttpRequest.
        17 * @author arv@google.com (Erik Arvidsson)
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.DefaultXmlHttpFactory');
        22goog.provide('goog.net.XmlHttp');
        23goog.provide('goog.net.XmlHttp.OptionType');
        24goog.provide('goog.net.XmlHttp.ReadyState');
        25goog.provide('goog.net.XmlHttpDefines');
        26
        27goog.require('goog.asserts');
        28goog.require('goog.net.WrapperXmlHttpFactory');
        29goog.require('goog.net.XmlHttpFactory');
        30
        31
        32/**
        33 * Static class for creating XMLHttpRequest objects.
        34 * @return {!goog.net.XhrLike.OrNative} A new XMLHttpRequest object.
        35 */
        36goog.net.XmlHttp = function() {
        37 return goog.net.XmlHttp.factory_.createInstance();
        38};
        39
        40
        41/**
        42 * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
        43 * true bypasses the ActiveX probing code.
        44 * NOTE(ruilopes): Due to the way JSCompiler works, this define *will not* strip
        45 * out the ActiveX probing code from binaries. To achieve this, use
        46 * {@code goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR} instead.
        47 * TODO(ruilopes): Collapse both defines.
        48 */
        49goog.define('goog.net.XmlHttp.ASSUME_NATIVE_XHR', false);
        50
        51
        52/** @const */
        53goog.net.XmlHttpDefines = {};
        54
        55
        56/**
        57 * @define {boolean} Whether to assume XMLHttpRequest exists. Setting this to
        58 * true eliminates the ActiveX probing code.
        59 */
        60goog.define('goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR', false);
        61
        62
        63/**
        64 * Gets the options to use with the XMLHttpRequest objects obtained using
        65 * the static methods.
        66 * @return {Object} The options.
        67 */
        68goog.net.XmlHttp.getOptions = function() {
        69 return goog.net.XmlHttp.factory_.getOptions();
        70};
        71
        72
        73/**
        74 * Type of options that an XmlHttp object can have.
        75 * @enum {number}
        76 */
        77goog.net.XmlHttp.OptionType = {
        78 /**
        79 * Whether a goog.nullFunction should be used to clear the onreadystatechange
        80 * handler instead of null.
        81 */
        82 USE_NULL_FUNCTION: 0,
        83
        84 /**
        85 * NOTE(user): In IE if send() errors on a *local* request the readystate
        86 * is still changed to COMPLETE. We need to ignore it and allow the
        87 * try/catch around send() to pick up the error.
        88 */
        89 LOCAL_REQUEST_ERROR: 1
        90};
        91
        92
        93/**
        94 * Status constants for XMLHTTP, matches:
        95 * http://msdn.microsoft.com/library/default.asp?url=/library/
        96 * en-us/xmlsdk/html/0e6a34e4-f90c-489d-acff-cb44242fafc6.asp
        97 * @enum {number}
        98 */
        99goog.net.XmlHttp.ReadyState = {
        100 /**
        101 * Constant for when xmlhttprequest.readyState is uninitialized
        102 */
        103 UNINITIALIZED: 0,
        104
        105 /**
        106 * Constant for when xmlhttprequest.readyState is loading.
        107 */
        108 LOADING: 1,
        109
        110 /**
        111 * Constant for when xmlhttprequest.readyState is loaded.
        112 */
        113 LOADED: 2,
        114
        115 /**
        116 * Constant for when xmlhttprequest.readyState is in an interactive state.
        117 */
        118 INTERACTIVE: 3,
        119
        120 /**
        121 * Constant for when xmlhttprequest.readyState is completed
        122 */
        123 COMPLETE: 4
        124};
        125
        126
        127/**
        128 * The global factory instance for creating XMLHttpRequest objects.
        129 * @type {goog.net.XmlHttpFactory}
        130 * @private
        131 */
        132goog.net.XmlHttp.factory_;
        133
        134
        135/**
        136 * Sets the factories for creating XMLHttpRequest objects and their options.
        137 * @param {Function} factory The factory for XMLHttpRequest objects.
        138 * @param {Function} optionsFactory The factory for options.
        139 * @deprecated Use setGlobalFactory instead.
        140 */
        141goog.net.XmlHttp.setFactory = function(factory, optionsFactory) {
        142 goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
        143 goog.asserts.assert(factory),
        144 goog.asserts.assert(optionsFactory)));
        145};
        146
        147
        148/**
        149 * Sets the global factory object.
        150 * @param {!goog.net.XmlHttpFactory} factory New global factory object.
        151 */
        152goog.net.XmlHttp.setGlobalFactory = function(factory) {
        153 goog.net.XmlHttp.factory_ = factory;
        154};
        155
        156
        157
        158/**
        159 * Default factory to use when creating xhr objects. You probably shouldn't be
        160 * instantiating this directly, but rather using it via goog.net.XmlHttp.
        161 * @extends {goog.net.XmlHttpFactory}
        162 * @constructor
        163 */
        164goog.net.DefaultXmlHttpFactory = function() {
        165 goog.net.XmlHttpFactory.call(this);
        166};
        167goog.inherits(goog.net.DefaultXmlHttpFactory, goog.net.XmlHttpFactory);
        168
        169
        170/** @override */
        171goog.net.DefaultXmlHttpFactory.prototype.createInstance = function() {
        172 var progId = this.getProgId_();
        173 if (progId) {
        174 return new ActiveXObject(progId);
        175 } else {
        176 return new XMLHttpRequest();
        177 }
        178};
        179
        180
        181/** @override */
        182goog.net.DefaultXmlHttpFactory.prototype.internalGetOptions = function() {
        183 var progId = this.getProgId_();
        184 var options = {};
        185 if (progId) {
        186 options[goog.net.XmlHttp.OptionType.USE_NULL_FUNCTION] = true;
        187 options[goog.net.XmlHttp.OptionType.LOCAL_REQUEST_ERROR] = true;
        188 }
        189 return options;
        190};
        191
        192
        193/**
        194 * The ActiveX PROG ID string to use to create xhr's in IE. Lazily initialized.
        195 * @type {string|undefined}
        196 * @private
        197 */
        198goog.net.DefaultXmlHttpFactory.prototype.ieProgId_;
        199
        200
        201/**
        202 * Initialize the private state used by other functions.
        203 * @return {string} The ActiveX PROG ID string to use to create xhr's in IE.
        204 * @private
        205 */
        206goog.net.DefaultXmlHttpFactory.prototype.getProgId_ = function() {
        207 if (goog.net.XmlHttp.ASSUME_NATIVE_XHR ||
        208 goog.net.XmlHttpDefines.ASSUME_NATIVE_XHR) {
        209 return '';
        210 }
        211
        212 // The following blog post describes what PROG IDs to use to create the
        213 // XMLHTTP object in Internet Explorer:
        214 // http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
        215 // However we do not (yet) fully trust that this will be OK for old versions
        216 // of IE on Win9x so we therefore keep the last 2.
        217 if (!this.ieProgId_ && typeof XMLHttpRequest == 'undefined' &&
        218 typeof ActiveXObject != 'undefined') {
        219 // Candidate Active X types.
        220 var ACTIVE_X_IDENTS = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0',
        221 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP'];
        222 for (var i = 0; i < ACTIVE_X_IDENTS.length; i++) {
        223 var candidate = ACTIVE_X_IDENTS[i];
        224 /** @preserveTry */
        225 try {
        226 new ActiveXObject(candidate);
        227 // NOTE(user): cannot assign progid and return candidate in one line
        228 // because JSCompiler complaings: BUG 658126
        229 this.ieProgId_ = candidate;
        230 return candidate;
        231 } catch (e) {
        232 // do nothing; try next choice
        233 }
        234 }
        235
        236 // couldn't find any matches
        237 throw Error('Could not create ActiveXObject. ActiveX might be disabled,' +
        238 ' or MSXML might not be installed');
        239 }
        240
        241 return /** @type {string} */ (this.ieProgId_);
        242};
        243
        244
        245//Set the global factory to an instance of the default factory.
        246goog.net.XmlHttp.setGlobalFactory(new goog.net.DefaultXmlHttpFactory());
        \ No newline at end of file diff --git a/docs/source/lib/goog/net/xmlhttpfactory.js.src.html b/docs/source/lib/goog/net/xmlhttpfactory.js.src.html index 09406c3..6dde268 100644 --- a/docs/source/lib/goog/net/xmlhttpfactory.js.src.html +++ b/docs/source/lib/goog/net/xmlhttpfactory.js.src.html @@ -1 +1 @@ -xmlhttpfactory.js

        lib/goog/net/xmlhttpfactory.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Interface for a factory for creating XMLHttpRequest objects
        17 * and metadata about them.
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.XmlHttpFactory');
        22
        23
        24
        25/**
        26 * Abstract base class for an XmlHttpRequest factory.
        27 * @constructor
        28 */
        29goog.net.XmlHttpFactory = function() {
        30};
        31
        32
        33/**
        34 * Cache of options - we only actually call internalGetOptions once.
        35 * @type {Object}
        36 * @private
        37 */
        38goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
        39
        40
        41/**
        42 * @return {!(XMLHttpRequest|GearsHttpRequest)} A new XMLHttpRequest instance.
        43 */
        44goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
        45
        46
        47/**
        48 * @return {Object} Options describing how xhr objects obtained from this
        49 * factory should be used.
        50 */
        51goog.net.XmlHttpFactory.prototype.getOptions = function() {
        52 return this.cachedOptions_ ||
        53 (this.cachedOptions_ = this.internalGetOptions());
        54};
        55
        56
        57/**
        58 * Override this method in subclasses to preserve the caching offered by
        59 * getOptions().
        60 * @return {Object} Options describing how xhr objects obtained from this
        61 * factory should be used.
        62 * @protected
        63 */
        64goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
        \ No newline at end of file +xmlhttpfactory.js

        lib/goog/net/xmlhttpfactory.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Interface for a factory for creating XMLHttpRequest objects
        17 * and metadata about them.
        18 * @author dbk@google.com (David Barrett-Kahn)
        19 */
        20
        21goog.provide('goog.net.XmlHttpFactory');
        22
        23/** @suppress {extraRequire} Typedef. */
        24goog.require('goog.net.XhrLike');
        25
        26
        27
        28/**
        29 * Abstract base class for an XmlHttpRequest factory.
        30 * @constructor
        31 */
        32goog.net.XmlHttpFactory = function() {
        33};
        34
        35
        36/**
        37 * Cache of options - we only actually call internalGetOptions once.
        38 * @type {Object}
        39 * @private
        40 */
        41goog.net.XmlHttpFactory.prototype.cachedOptions_ = null;
        42
        43
        44/**
        45 * @return {!goog.net.XhrLike.OrNative} A new XhrLike instance.
        46 */
        47goog.net.XmlHttpFactory.prototype.createInstance = goog.abstractMethod;
        48
        49
        50/**
        51 * @return {Object} Options describing how xhr objects obtained from this
        52 * factory should be used.
        53 */
        54goog.net.XmlHttpFactory.prototype.getOptions = function() {
        55 return this.cachedOptions_ ||
        56 (this.cachedOptions_ = this.internalGetOptions());
        57};
        58
        59
        60/**
        61 * Override this method in subclasses to preserve the caching offered by
        62 * getOptions().
        63 * @return {Object} Options describing how xhr objects obtained from this
        64 * factory should be used.
        65 * @protected
        66 */
        67goog.net.XmlHttpFactory.prototype.internalGetOptions = goog.abstractMethod;
        \ No newline at end of file diff --git a/docs/source/lib/goog/object/object.js.src.html b/docs/source/lib/goog/object/object.js.src.html index a9b8f59..7dfe649 100644 --- a/docs/source/lib/goog/object/object.js.src.html +++ b/docs/source/lib/goog/object/object.js.src.html @@ -1 +1 @@ -object.js

        lib/goog/object/object.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for manipulating objects/maps/hashes.
        17 */
        18
        19goog.provide('goog.object');
        20
        21
        22/**
        23 * Calls a function for each element in an object/map/hash.
        24 *
        25 * @param {Object.<K,V>} obj The object over which to iterate.
        26 * @param {function(this:T,V,?,Object.<K,V>):?} f The function to call
        27 * for every element. This function takes 3 arguments (the element, the
        28 * index and the object) and the return value is ignored.
        29 * @param {T=} opt_obj This is used as the 'this' object within f.
        30 * @template T,K,V
        31 */
        32goog.object.forEach = function(obj, f, opt_obj) {
        33 for (var key in obj) {
        34 f.call(opt_obj, obj[key], key, obj);
        35 }
        36};
        37
        38
        39/**
        40 * Calls a function for each element in an object/map/hash. If that call returns
        41 * true, adds the element to a new object.
        42 *
        43 * @param {Object.<K,V>} obj The object over which to iterate.
        44 * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to call
        45 * for every element. This
        46 * function takes 3 arguments (the element, the index and the object)
        47 * and should return a boolean. If the return value is true the
        48 * element is added to the result object. If it is false the
        49 * element is not included.
        50 * @param {T=} opt_obj This is used as the 'this' object within f.
        51 * @return {!Object.<K,V>} a new object in which only elements that passed the
        52 * test are present.
        53 * @template T,K,V
        54 */
        55goog.object.filter = function(obj, f, opt_obj) {
        56 var res = {};
        57 for (var key in obj) {
        58 if (f.call(opt_obj, obj[key], key, obj)) {
        59 res[key] = obj[key];
        60 }
        61 }
        62 return res;
        63};
        64
        65
        66/**
        67 * For every element in an object/map/hash calls a function and inserts the
        68 * result into a new object.
        69 *
        70 * @param {Object.<K,V>} obj The object over which to iterate.
        71 * @param {function(this:T,V,?,Object.<K,V>):R} f The function to call
        72 * for every element. This function
        73 * takes 3 arguments (the element, the index and the object)
        74 * and should return something. The result will be inserted
        75 * into a new object.
        76 * @param {T=} opt_obj This is used as the 'this' object within f.
        77 * @return {!Object.<K,R>} a new object with the results from f.
        78 * @template T,K,V,R
        79 */
        80goog.object.map = function(obj, f, opt_obj) {
        81 var res = {};
        82 for (var key in obj) {
        83 res[key] = f.call(opt_obj, obj[key], key, obj);
        84 }
        85 return res;
        86};
        87
        88
        89/**
        90 * Calls a function for each element in an object/map/hash. If any
        91 * call returns true, returns true (without checking the rest). If
        92 * all calls return false, returns false.
        93 *
        94 * @param {Object.<K,V>} obj The object to check.
        95 * @param {function(this:T,V,?,Object.<K,V>):boolean} f The function to
        96 * call for every element. This function
        97 * takes 3 arguments (the element, the index and the object) and should
        98 * return a boolean.
        99 * @param {T=} opt_obj This is used as the 'this' object within f.
        100 * @return {boolean} true if any element passes the test.
        101 * @template T,K,V
        102 */
        103goog.object.some = function(obj, f, opt_obj) {
        104 for (var key in obj) {
        105 if (f.call(opt_obj, obj[key], key, obj)) {
        106 return true;
        107 }
        108 }
        109 return false;
        110};
        111
        112
        113/**
        114 * Calls a function for each element in an object/map/hash. If
        115 * all calls return true, returns true. If any call returns false, returns
        116 * false at this point and does not continue to check the remaining elements.
        117 *
        118 * @param {Object.<K,V>} obj The object to check.
        119 * @param {?function(this:T,V,?,Object.<K,V>):boolean} f The function to
        120 * call for every element. This function
        121 * takes 3 arguments (the element, the index and the object) and should
        122 * return a boolean.
        123 * @param {T=} opt_obj This is used as the 'this' object within f.
        124 * @return {boolean} false if any element fails the test.
        125 * @template T,K,V
        126 */
        127goog.object.every = function(obj, f, opt_obj) {
        128 for (var key in obj) {
        129 if (!f.call(opt_obj, obj[key], key, obj)) {
        130 return false;
        131 }
        132 }
        133 return true;
        134};
        135
        136
        137/**
        138 * Returns the number of key-value pairs in the object map.
        139 *
        140 * @param {Object} obj The object for which to get the number of key-value
        141 * pairs.
        142 * @return {number} The number of key-value pairs in the object map.
        143 */
        144goog.object.getCount = function(obj) {
        145 // JS1.5 has __count__ but it has been deprecated so it raises a warning...
        146 // in other words do not use. Also __count__ only includes the fields on the
        147 // actual object and not in the prototype chain.
        148 var rv = 0;
        149 for (var key in obj) {
        150 rv++;
        151 }
        152 return rv;
        153};
        154
        155
        156/**
        157 * Returns one key from the object map, if any exists.
        158 * For map literals the returned key will be the first one in most of the
        159 * browsers (a know exception is Konqueror).
        160 *
        161 * @param {Object} obj The object to pick a key from.
        162 * @return {string|undefined} The key or undefined if the object is empty.
        163 */
        164goog.object.getAnyKey = function(obj) {
        165 for (var key in obj) {
        166 return key;
        167 }
        168};
        169
        170
        171/**
        172 * Returns one value from the object map, if any exists.
        173 * For map literals the returned value will be the first one in most of the
        174 * browsers (a know exception is Konqueror).
        175 *
        176 * @param {Object.<K,V>} obj The object to pick a value from.
        177 * @return {V|undefined} The value or undefined if the object is empty.
        178 * @template K,V
        179 */
        180goog.object.getAnyValue = function(obj) {
        181 for (var key in obj) {
        182 return obj[key];
        183 }
        184};
        185
        186
        187/**
        188 * Whether the object/hash/map contains the given object as a value.
        189 * An alias for goog.object.containsValue(obj, val).
        190 *
        191 * @param {Object.<K,V>} obj The object in which to look for val.
        192 * @param {V} val The object for which to check.
        193 * @return {boolean} true if val is present.
        194 * @template K,V
        195 */
        196goog.object.contains = function(obj, val) {
        197 return goog.object.containsValue(obj, val);
        198};
        199
        200
        201/**
        202 * Returns the values of the object/map/hash.
        203 *
        204 * @param {Object.<K,V>} obj The object from which to get the values.
        205 * @return {!Array.<V>} The values in the object/map/hash.
        206 * @template K,V
        207 */
        208goog.object.getValues = function(obj) {
        209 var res = [];
        210 var i = 0;
        211 for (var key in obj) {
        212 res[i++] = obj[key];
        213 }
        214 return res;
        215};
        216
        217
        218/**
        219 * Returns the keys of the object/map/hash.
        220 *
        221 * @param {Object} obj The object from which to get the keys.
        222 * @return {!Array.<string>} Array of property keys.
        223 */
        224goog.object.getKeys = function(obj) {
        225 var res = [];
        226 var i = 0;
        227 for (var key in obj) {
        228 res[i++] = key;
        229 }
        230 return res;
        231};
        232
        233
        234/**
        235 * Get a value from an object multiple levels deep. This is useful for
        236 * pulling values from deeply nested objects, such as JSON responses.
        237 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
        238 *
        239 * @param {!Object} obj An object to get the value from. Can be array-like.
        240 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
        241 * (as strings, or numbers, for array-like objects). Can also be
        242 * specified as a single array of keys.
        243 * @return {*} The resulting value. If, at any point, the value for a key
        244 * is undefined, returns undefined.
        245 */
        246goog.object.getValueByKeys = function(obj, var_args) {
        247 var isArrayLike = goog.isArrayLike(var_args);
        248 var keys = isArrayLike ? var_args : arguments;
        249
        250 // Start with the 2nd parameter for the variable parameters syntax.
        251 for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
        252 obj = obj[keys[i]];
        253 if (!goog.isDef(obj)) {
        254 break;
        255 }
        256 }
        257
        258 return obj;
        259};
        260
        261
        262/**
        263 * Whether the object/map/hash contains the given key.
        264 *
        265 * @param {Object} obj The object in which to look for key.
        266 * @param {*} key The key for which to check.
        267 * @return {boolean} true If the map contains the key.
        268 */
        269goog.object.containsKey = function(obj, key) {
        270 return key in obj;
        271};
        272
        273
        274/**
        275 * Whether the object/map/hash contains the given value. This is O(n).
        276 *
        277 * @param {Object.<K,V>} obj The object in which to look for val.
        278 * @param {V} val The value for which to check.
        279 * @return {boolean} true If the map contains the value.
        280 * @template K,V
        281 */
        282goog.object.containsValue = function(obj, val) {
        283 for (var key in obj) {
        284 if (obj[key] == val) {
        285 return true;
        286 }
        287 }
        288 return false;
        289};
        290
        291
        292/**
        293 * Searches an object for an element that satisfies the given condition and
        294 * returns its key.
        295 * @param {Object.<K,V>} obj The object to search in.
        296 * @param {function(this:T,V,string,Object.<K,V>):boolean} f The
        297 * function to call for every element. Takes 3 arguments (the value,
        298 * the key and the object) and should return a boolean.
        299 * @param {T=} opt_this An optional "this" context for the function.
        300 * @return {string|undefined} The key of an element for which the function
        301 * returns true or undefined if no such element is found.
        302 * @template T,K,V
        303 */
        304goog.object.findKey = function(obj, f, opt_this) {
        305 for (var key in obj) {
        306 if (f.call(opt_this, obj[key], key, obj)) {
        307 return key;
        308 }
        309 }
        310 return undefined;
        311};
        312
        313
        314/**
        315 * Searches an object for an element that satisfies the given condition and
        316 * returns its value.
        317 * @param {Object.<K,V>} obj The object to search in.
        318 * @param {function(this:T,V,string,Object.<K,V>):boolean} f The function
        319 * to call for every element. Takes 3 arguments (the value, the key
        320 * and the object) and should return a boolean.
        321 * @param {T=} opt_this An optional "this" context for the function.
        322 * @return {V} The value of an element for which the function returns true or
        323 * undefined if no such element is found.
        324 * @template T,K,V
        325 */
        326goog.object.findValue = function(obj, f, opt_this) {
        327 var key = goog.object.findKey(obj, f, opt_this);
        328 return key && obj[key];
        329};
        330
        331
        332/**
        333 * Whether the object/map/hash is empty.
        334 *
        335 * @param {Object} obj The object to test.
        336 * @return {boolean} true if obj is empty.
        337 */
        338goog.object.isEmpty = function(obj) {
        339 for (var key in obj) {
        340 return false;
        341 }
        342 return true;
        343};
        344
        345
        346/**
        347 * Removes all key value pairs from the object/map/hash.
        348 *
        349 * @param {Object} obj The object to clear.
        350 */
        351goog.object.clear = function(obj) {
        352 for (var i in obj) {
        353 delete obj[i];
        354 }
        355};
        356
        357
        358/**
        359 * Removes a key-value pair based on the key.
        360 *
        361 * @param {Object} obj The object from which to remove the key.
        362 * @param {*} key The key to remove.
        363 * @return {boolean} Whether an element was removed.
        364 */
        365goog.object.remove = function(obj, key) {
        366 var rv;
        367 if ((rv = key in obj)) {
        368 delete obj[key];
        369 }
        370 return rv;
        371};
        372
        373
        374/**
        375 * Adds a key-value pair to the object. Throws an exception if the key is
        376 * already in use. Use set if you want to change an existing pair.
        377 *
        378 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
        379 * @param {string} key The key to add.
        380 * @param {V} val The value to add.
        381 * @template K,V
        382 */
        383goog.object.add = function(obj, key, val) {
        384 if (key in obj) {
        385 throw Error('The object already contains the key "' + key + '"');
        386 }
        387 goog.object.set(obj, key, val);
        388};
        389
        390
        391/**
        392 * Returns the value for the given key.
        393 *
        394 * @param {Object.<K,V>} obj The object from which to get the value.
        395 * @param {string} key The key for which to get the value.
        396 * @param {R=} opt_val The value to return if no item is found for the given
        397 * key (default is undefined).
        398 * @return {V|R|undefined} The value for the given key.
        399 * @template K,V,R
        400 */
        401goog.object.get = function(obj, key, opt_val) {
        402 if (key in obj) {
        403 return obj[key];
        404 }
        405 return opt_val;
        406};
        407
        408
        409/**
        410 * Adds a key-value pair to the object/map/hash.
        411 *
        412 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
        413 * @param {string} key The key to add.
        414 * @param {V} value The value to add.
        415 * @template K,V
        416 */
        417goog.object.set = function(obj, key, value) {
        418 obj[key] = value;
        419};
        420
        421
        422/**
        423 * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
        424 *
        425 * @param {Object.<K,V>} obj The object to which to add the key-value pair.
        426 * @param {string} key The key to add.
        427 * @param {V} value The value to add if the key wasn't present.
        428 * @return {V} The value of the entry at the end of the function.
        429 * @template K,V
        430 */
        431goog.object.setIfUndefined = function(obj, key, value) {
        432 return key in obj ? obj[key] : (obj[key] = value);
        433};
        434
        435
        436/**
        437 * Does a flat clone of the object.
        438 *
        439 * @param {Object.<K,V>} obj Object to clone.
        440 * @return {!Object.<K,V>} Clone of the input object.
        441 * @template K,V
        442 */
        443goog.object.clone = function(obj) {
        444 // We cannot use the prototype trick because a lot of methods depend on where
        445 // the actual key is set.
        446
        447 var res = {};
        448 for (var key in obj) {
        449 res[key] = obj[key];
        450 }
        451 return res;
        452 // We could also use goog.mixin but I wanted this to be independent from that.
        453};
        454
        455
        456/**
        457 * Clones a value. The input may be an Object, Array, or basic type. Objects and
        458 * arrays will be cloned recursively.
        459 *
        460 * WARNINGS:
        461 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
        462 * that refer to themselves will cause infinite recursion.
        463 *
        464 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
        465 * copies UIDs created by <code>getUid</code> into cloned results.
        466 *
        467 * @param {*} obj The value to clone.
        468 * @return {*} A clone of the input value.
        469 */
        470goog.object.unsafeClone = function(obj) {
        471 var type = goog.typeOf(obj);
        472 if (type == 'object' || type == 'array') {
        473 if (obj.clone) {
        474 return obj.clone();
        475 }
        476 var clone = type == 'array' ? [] : {};
        477 for (var key in obj) {
        478 clone[key] = goog.object.unsafeClone(obj[key]);
        479 }
        480 return clone;
        481 }
        482
        483 return obj;
        484};
        485
        486
        487/**
        488 * Returns a new object in which all the keys and values are interchanged
        489 * (keys become values and values become keys). If multiple keys map to the
        490 * same value, the chosen transposed value is implementation-dependent.
        491 *
        492 * @param {Object} obj The object to transpose.
        493 * @return {!Object} The transposed object.
        494 */
        495goog.object.transpose = function(obj) {
        496 var transposed = {};
        497 for (var key in obj) {
        498 transposed[obj[key]] = key;
        499 }
        500 return transposed;
        501};
        502
        503
        504/**
        505 * The names of the fields that are defined on Object.prototype.
        506 * @type {Array.<string>}
        507 * @private
        508 */
        509goog.object.PROTOTYPE_FIELDS_ = [
        510 'constructor',
        511 'hasOwnProperty',
        512 'isPrototypeOf',
        513 'propertyIsEnumerable',
        514 'toLocaleString',
        515 'toString',
        516 'valueOf'
        517];
        518
        519
        520/**
        521 * Extends an object with another object.
        522 * This operates 'in-place'; it does not create a new Object.
        523 *
        524 * Example:
        525 * var o = {};
        526 * goog.object.extend(o, {a: 0, b: 1});
        527 * o; // {a: 0, b: 1}
        528 * goog.object.extend(o, {c: 2});
        529 * o; // {a: 0, b: 1, c: 2}
        530 *
        531 * @param {Object} target The object to modify.
        532 * @param {...Object} var_args The objects from which values will be copied.
        533 */
        534goog.object.extend = function(target, var_args) {
        535 var key, source;
        536 for (var i = 1; i < arguments.length; i++) {
        537 source = arguments[i];
        538 for (key in source) {
        539 target[key] = source[key];
        540 }
        541
        542 // For IE the for-in-loop does not contain any properties that are not
        543 // enumerable on the prototype object (for example isPrototypeOf from
        544 // Object.prototype) and it will also not include 'replace' on objects that
        545 // extend String and change 'replace' (not that it is common for anyone to
        546 // extend anything except Object).
        547
        548 for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
        549 key = goog.object.PROTOTYPE_FIELDS_[j];
        550 if (Object.prototype.hasOwnProperty.call(source, key)) {
        551 target[key] = source[key];
        552 }
        553 }
        554 }
        555};
        556
        557
        558/**
        559 * Creates a new object built from the key-value pairs provided as arguments.
        560 * @param {...*} var_args If only one argument is provided and it is an array
        561 * then this is used as the arguments, otherwise even arguments are used as
        562 * the property names and odd arguments are used as the property values.
        563 * @return {!Object} The new object.
        564 * @throws {Error} If there are uneven number of arguments or there is only one
        565 * non array argument.
        566 */
        567goog.object.create = function(var_args) {
        568 var argLength = arguments.length;
        569 if (argLength == 1 && goog.isArray(arguments[0])) {
        570 return goog.object.create.apply(null, arguments[0]);
        571 }
        572
        573 if (argLength % 2) {
        574 throw Error('Uneven number of arguments');
        575 }
        576
        577 var rv = {};
        578 for (var i = 0; i < argLength; i += 2) {
        579 rv[arguments[i]] = arguments[i + 1];
        580 }
        581 return rv;
        582};
        583
        584
        585/**
        586 * Creates a new object where the property names come from the arguments but
        587 * the value is always set to true
        588 * @param {...*} var_args If only one argument is provided and it is an array
        589 * then this is used as the arguments, otherwise the arguments are used
        590 * as the property names.
        591 * @return {!Object} The new object.
        592 */
        593goog.object.createSet = function(var_args) {
        594 var argLength = arguments.length;
        595 if (argLength == 1 && goog.isArray(arguments[0])) {
        596 return goog.object.createSet.apply(null, arguments[0]);
        597 }
        598
        599 var rv = {};
        600 for (var i = 0; i < argLength; i++) {
        601 rv[arguments[i]] = true;
        602 }
        603 return rv;
        604};
        605
        606
        607/**
        608 * Creates an immutable view of the underlying object, if the browser
        609 * supports immutable objects.
        610 *
        611 * In default mode, writes to this view will fail silently. In strict mode,
        612 * they will throw an error.
        613 *
        614 * @param {!Object.<K,V>} obj An object.
        615 * @return {!Object.<K,V>} An immutable view of that object, or the
        616 * original object if this browser does not support immutables.
        617 * @template K,V
        618 */
        619goog.object.createImmutableView = function(obj) {
        620 var result = obj;
        621 if (Object.isFrozen && !Object.isFrozen(obj)) {
        622 result = Object.create(obj);
        623 Object.freeze(result);
        624 }
        625 return result;
        626};
        627
        628
        629/**
        630 * @param {!Object} obj An object.
        631 * @return {boolean} Whether this is an immutable view of the object.
        632 */
        633goog.object.isImmutableView = function(obj) {
        634 return !!Object.isFrozen && Object.isFrozen(obj);
        635};
        \ No newline at end of file +object.js

        lib/goog/object/object.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for manipulating objects/maps/hashes.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20goog.provide('goog.object');
        21
        22
        23/**
        24 * Calls a function for each element in an object/map/hash.
        25 *
        26 * @param {Object<K,V>} obj The object over which to iterate.
        27 * @param {function(this:T,V,?,Object<K,V>):?} f The function to call
        28 * for every element. This function takes 3 arguments (the element, the
        29 * index and the object) and the return value is ignored.
        30 * @param {T=} opt_obj This is used as the 'this' object within f.
        31 * @template T,K,V
        32 */
        33goog.object.forEach = function(obj, f, opt_obj) {
        34 for (var key in obj) {
        35 f.call(opt_obj, obj[key], key, obj);
        36 }
        37};
        38
        39
        40/**
        41 * Calls a function for each element in an object/map/hash. If that call returns
        42 * true, adds the element to a new object.
        43 *
        44 * @param {Object<K,V>} obj The object over which to iterate.
        45 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to call
        46 * for every element. This
        47 * function takes 3 arguments (the element, the index and the object)
        48 * and should return a boolean. If the return value is true the
        49 * element is added to the result object. If it is false the
        50 * element is not included.
        51 * @param {T=} opt_obj This is used as the 'this' object within f.
        52 * @return {!Object<K,V>} a new object in which only elements that passed the
        53 * test are present.
        54 * @template T,K,V
        55 */
        56goog.object.filter = function(obj, f, opt_obj) {
        57 var res = {};
        58 for (var key in obj) {
        59 if (f.call(opt_obj, obj[key], key, obj)) {
        60 res[key] = obj[key];
        61 }
        62 }
        63 return res;
        64};
        65
        66
        67/**
        68 * For every element in an object/map/hash calls a function and inserts the
        69 * result into a new object.
        70 *
        71 * @param {Object<K,V>} obj The object over which to iterate.
        72 * @param {function(this:T,V,?,Object<K,V>):R} f The function to call
        73 * for every element. This function
        74 * takes 3 arguments (the element, the index and the object)
        75 * and should return something. The result will be inserted
        76 * into a new object.
        77 * @param {T=} opt_obj This is used as the 'this' object within f.
        78 * @return {!Object<K,R>} a new object with the results from f.
        79 * @template T,K,V,R
        80 */
        81goog.object.map = function(obj, f, opt_obj) {
        82 var res = {};
        83 for (var key in obj) {
        84 res[key] = f.call(opt_obj, obj[key], key, obj);
        85 }
        86 return res;
        87};
        88
        89
        90/**
        91 * Calls a function for each element in an object/map/hash. If any
        92 * call returns true, returns true (without checking the rest). If
        93 * all calls return false, returns false.
        94 *
        95 * @param {Object<K,V>} obj The object to check.
        96 * @param {function(this:T,V,?,Object<K,V>):boolean} f The function to
        97 * call for every element. This function
        98 * takes 3 arguments (the element, the index and the object) and should
        99 * return a boolean.
        100 * @param {T=} opt_obj This is used as the 'this' object within f.
        101 * @return {boolean} true if any element passes the test.
        102 * @template T,K,V
        103 */
        104goog.object.some = function(obj, f, opt_obj) {
        105 for (var key in obj) {
        106 if (f.call(opt_obj, obj[key], key, obj)) {
        107 return true;
        108 }
        109 }
        110 return false;
        111};
        112
        113
        114/**
        115 * Calls a function for each element in an object/map/hash. If
        116 * all calls return true, returns true. If any call returns false, returns
        117 * false at this point and does not continue to check the remaining elements.
        118 *
        119 * @param {Object<K,V>} obj The object to check.
        120 * @param {?function(this:T,V,?,Object<K,V>):boolean} f The function to
        121 * call for every element. This function
        122 * takes 3 arguments (the element, the index and the object) and should
        123 * return a boolean.
        124 * @param {T=} opt_obj This is used as the 'this' object within f.
        125 * @return {boolean} false if any element fails the test.
        126 * @template T,K,V
        127 */
        128goog.object.every = function(obj, f, opt_obj) {
        129 for (var key in obj) {
        130 if (!f.call(opt_obj, obj[key], key, obj)) {
        131 return false;
        132 }
        133 }
        134 return true;
        135};
        136
        137
        138/**
        139 * Returns the number of key-value pairs in the object map.
        140 *
        141 * @param {Object} obj The object for which to get the number of key-value
        142 * pairs.
        143 * @return {number} The number of key-value pairs in the object map.
        144 */
        145goog.object.getCount = function(obj) {
        146 // JS1.5 has __count__ but it has been deprecated so it raises a warning...
        147 // in other words do not use. Also __count__ only includes the fields on the
        148 // actual object and not in the prototype chain.
        149 var rv = 0;
        150 for (var key in obj) {
        151 rv++;
        152 }
        153 return rv;
        154};
        155
        156
        157/**
        158 * Returns one key from the object map, if any exists.
        159 * For map literals the returned key will be the first one in most of the
        160 * browsers (a know exception is Konqueror).
        161 *
        162 * @param {Object} obj The object to pick a key from.
        163 * @return {string|undefined} The key or undefined if the object is empty.
        164 */
        165goog.object.getAnyKey = function(obj) {
        166 for (var key in obj) {
        167 return key;
        168 }
        169};
        170
        171
        172/**
        173 * Returns one value from the object map, if any exists.
        174 * For map literals the returned value will be the first one in most of the
        175 * browsers (a know exception is Konqueror).
        176 *
        177 * @param {Object<K,V>} obj The object to pick a value from.
        178 * @return {V|undefined} The value or undefined if the object is empty.
        179 * @template K,V
        180 */
        181goog.object.getAnyValue = function(obj) {
        182 for (var key in obj) {
        183 return obj[key];
        184 }
        185};
        186
        187
        188/**
        189 * Whether the object/hash/map contains the given object as a value.
        190 * An alias for goog.object.containsValue(obj, val).
        191 *
        192 * @param {Object<K,V>} obj The object in which to look for val.
        193 * @param {V} val The object for which to check.
        194 * @return {boolean} true if val is present.
        195 * @template K,V
        196 */
        197goog.object.contains = function(obj, val) {
        198 return goog.object.containsValue(obj, val);
        199};
        200
        201
        202/**
        203 * Returns the values of the object/map/hash.
        204 *
        205 * @param {Object<K,V>} obj The object from which to get the values.
        206 * @return {!Array<V>} The values in the object/map/hash.
        207 * @template K,V
        208 */
        209goog.object.getValues = function(obj) {
        210 var res = [];
        211 var i = 0;
        212 for (var key in obj) {
        213 res[i++] = obj[key];
        214 }
        215 return res;
        216};
        217
        218
        219/**
        220 * Returns the keys of the object/map/hash.
        221 *
        222 * @param {Object} obj The object from which to get the keys.
        223 * @return {!Array<string>} Array of property keys.
        224 */
        225goog.object.getKeys = function(obj) {
        226 var res = [];
        227 var i = 0;
        228 for (var key in obj) {
        229 res[i++] = key;
        230 }
        231 return res;
        232};
        233
        234
        235/**
        236 * Get a value from an object multiple levels deep. This is useful for
        237 * pulling values from deeply nested objects, such as JSON responses.
        238 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
        239 *
        240 * @param {!Object} obj An object to get the value from. Can be array-like.
        241 * @param {...(string|number|!Array<number|string>)} var_args A number of keys
        242 * (as strings, or numbers, for array-like objects). Can also be
        243 * specified as a single array of keys.
        244 * @return {*} The resulting value. If, at any point, the value for a key
        245 * is undefined, returns undefined.
        246 */
        247goog.object.getValueByKeys = function(obj, var_args) {
        248 var isArrayLike = goog.isArrayLike(var_args);
        249 var keys = isArrayLike ? var_args : arguments;
        250
        251 // Start with the 2nd parameter for the variable parameters syntax.
        252 for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
        253 obj = obj[keys[i]];
        254 if (!goog.isDef(obj)) {
        255 break;
        256 }
        257 }
        258
        259 return obj;
        260};
        261
        262
        263/**
        264 * Whether the object/map/hash contains the given key.
        265 *
        266 * @param {Object} obj The object in which to look for key.
        267 * @param {*} key The key for which to check.
        268 * @return {boolean} true If the map contains the key.
        269 */
        270goog.object.containsKey = function(obj, key) {
        271 return key in obj;
        272};
        273
        274
        275/**
        276 * Whether the object/map/hash contains the given value. This is O(n).
        277 *
        278 * @param {Object<K,V>} obj The object in which to look for val.
        279 * @param {V} val The value for which to check.
        280 * @return {boolean} true If the map contains the value.
        281 * @template K,V
        282 */
        283goog.object.containsValue = function(obj, val) {
        284 for (var key in obj) {
        285 if (obj[key] == val) {
        286 return true;
        287 }
        288 }
        289 return false;
        290};
        291
        292
        293/**
        294 * Searches an object for an element that satisfies the given condition and
        295 * returns its key.
        296 * @param {Object<K,V>} obj The object to search in.
        297 * @param {function(this:T,V,string,Object<K,V>):boolean} f The
        298 * function to call for every element. Takes 3 arguments (the value,
        299 * the key and the object) and should return a boolean.
        300 * @param {T=} opt_this An optional "this" context for the function.
        301 * @return {string|undefined} The key of an element for which the function
        302 * returns true or undefined if no such element is found.
        303 * @template T,K,V
        304 */
        305goog.object.findKey = function(obj, f, opt_this) {
        306 for (var key in obj) {
        307 if (f.call(opt_this, obj[key], key, obj)) {
        308 return key;
        309 }
        310 }
        311 return undefined;
        312};
        313
        314
        315/**
        316 * Searches an object for an element that satisfies the given condition and
        317 * returns its value.
        318 * @param {Object<K,V>} obj The object to search in.
        319 * @param {function(this:T,V,string,Object<K,V>):boolean} f The function
        320 * to call for every element. Takes 3 arguments (the value, the key
        321 * and the object) and should return a boolean.
        322 * @param {T=} opt_this An optional "this" context for the function.
        323 * @return {V} The value of an element for which the function returns true or
        324 * undefined if no such element is found.
        325 * @template T,K,V
        326 */
        327goog.object.findValue = function(obj, f, opt_this) {
        328 var key = goog.object.findKey(obj, f, opt_this);
        329 return key && obj[key];
        330};
        331
        332
        333/**
        334 * Whether the object/map/hash is empty.
        335 *
        336 * @param {Object} obj The object to test.
        337 * @return {boolean} true if obj is empty.
        338 */
        339goog.object.isEmpty = function(obj) {
        340 for (var key in obj) {
        341 return false;
        342 }
        343 return true;
        344};
        345
        346
        347/**
        348 * Removes all key value pairs from the object/map/hash.
        349 *
        350 * @param {Object} obj The object to clear.
        351 */
        352goog.object.clear = function(obj) {
        353 for (var i in obj) {
        354 delete obj[i];
        355 }
        356};
        357
        358
        359/**
        360 * Removes a key-value pair based on the key.
        361 *
        362 * @param {Object} obj The object from which to remove the key.
        363 * @param {*} key The key to remove.
        364 * @return {boolean} Whether an element was removed.
        365 */
        366goog.object.remove = function(obj, key) {
        367 var rv;
        368 if ((rv = key in obj)) {
        369 delete obj[key];
        370 }
        371 return rv;
        372};
        373
        374
        375/**
        376 * Adds a key-value pair to the object. Throws an exception if the key is
        377 * already in use. Use set if you want to change an existing pair.
        378 *
        379 * @param {Object<K,V>} obj The object to which to add the key-value pair.
        380 * @param {string} key The key to add.
        381 * @param {V} val The value to add.
        382 * @template K,V
        383 */
        384goog.object.add = function(obj, key, val) {
        385 if (key in obj) {
        386 throw Error('The object already contains the key "' + key + '"');
        387 }
        388 goog.object.set(obj, key, val);
        389};
        390
        391
        392/**
        393 * Returns the value for the given key.
        394 *
        395 * @param {Object<K,V>} obj The object from which to get the value.
        396 * @param {string} key The key for which to get the value.
        397 * @param {R=} opt_val The value to return if no item is found for the given
        398 * key (default is undefined).
        399 * @return {V|R|undefined} The value for the given key.
        400 * @template K,V,R
        401 */
        402goog.object.get = function(obj, key, opt_val) {
        403 if (key in obj) {
        404 return obj[key];
        405 }
        406 return opt_val;
        407};
        408
        409
        410/**
        411 * Adds a key-value pair to the object/map/hash.
        412 *
        413 * @param {Object<K,V>} obj The object to which to add the key-value pair.
        414 * @param {string} key The key to add.
        415 * @param {V} value The value to add.
        416 * @template K,V
        417 */
        418goog.object.set = function(obj, key, value) {
        419 obj[key] = value;
        420};
        421
        422
        423/**
        424 * Adds a key-value pair to the object/map/hash if it doesn't exist yet.
        425 *
        426 * @param {Object<K,V>} obj The object to which to add the key-value pair.
        427 * @param {string} key The key to add.
        428 * @param {V} value The value to add if the key wasn't present.
        429 * @return {V} The value of the entry at the end of the function.
        430 * @template K,V
        431 */
        432goog.object.setIfUndefined = function(obj, key, value) {
        433 return key in obj ? obj[key] : (obj[key] = value);
        434};
        435
        436
        437/**
        438 * Sets a key and value to an object if the key is not set. The value will be
        439 * the return value of the given function. If the key already exists, the
        440 * object will not be changed and the function will not be called (the function
        441 * will be lazily evaluated -- only called if necessary).
        442 *
        443 * This function is particularly useful for use with a map used a as a cache.
        444 *
        445 * @param {!Object<K,V>} obj The object to which to add the key-value pair.
        446 * @param {string} key The key to add.
        447 * @param {function():V} f The value to add if the key wasn't present.
        448 * @return {V} The value of the entry at the end of the function.
        449 * @template K,V
        450 */
        451goog.object.setWithReturnValueIfNotSet = function(obj, key, f) {
        452 if (key in obj) {
        453 return obj[key];
        454 }
        455
        456 var val = f();
        457 obj[key] = val;
        458 return val;
        459};
        460
        461
        462/**
        463 * Compares two objects for equality using === on the values.
        464 *
        465 * @param {!Object<K,V>} a
        466 * @param {!Object<K,V>} b
        467 * @return {boolean}
        468 * @template K,V
        469 */
        470goog.object.equals = function(a, b) {
        471 for (var k in a) {
        472 if (!(k in b) || a[k] !== b[k]) {
        473 return false;
        474 }
        475 }
        476 for (var k in b) {
        477 if (!(k in a)) {
        478 return false;
        479 }
        480 }
        481 return true;
        482};
        483
        484
        485/**
        486 * Does a flat clone of the object.
        487 *
        488 * @param {Object<K,V>} obj Object to clone.
        489 * @return {!Object<K,V>} Clone of the input object.
        490 * @template K,V
        491 */
        492goog.object.clone = function(obj) {
        493 // We cannot use the prototype trick because a lot of methods depend on where
        494 // the actual key is set.
        495
        496 var res = {};
        497 for (var key in obj) {
        498 res[key] = obj[key];
        499 }
        500 return res;
        501 // We could also use goog.mixin but I wanted this to be independent from that.
        502};
        503
        504
        505/**
        506 * Clones a value. The input may be an Object, Array, or basic type. Objects and
        507 * arrays will be cloned recursively.
        508 *
        509 * WARNINGS:
        510 * <code>goog.object.unsafeClone</code> does not detect reference loops. Objects
        511 * that refer to themselves will cause infinite recursion.
        512 *
        513 * <code>goog.object.unsafeClone</code> is unaware of unique identifiers, and
        514 * copies UIDs created by <code>getUid</code> into cloned results.
        515 *
        516 * @param {*} obj The value to clone.
        517 * @return {*} A clone of the input value.
        518 */
        519goog.object.unsafeClone = function(obj) {
        520 var type = goog.typeOf(obj);
        521 if (type == 'object' || type == 'array') {
        522 if (obj.clone) {
        523 return obj.clone();
        524 }
        525 var clone = type == 'array' ? [] : {};
        526 for (var key in obj) {
        527 clone[key] = goog.object.unsafeClone(obj[key]);
        528 }
        529 return clone;
        530 }
        531
        532 return obj;
        533};
        534
        535
        536/**
        537 * Returns a new object in which all the keys and values are interchanged
        538 * (keys become values and values become keys). If multiple keys map to the
        539 * same value, the chosen transposed value is implementation-dependent.
        540 *
        541 * @param {Object} obj The object to transpose.
        542 * @return {!Object} The transposed object.
        543 */
        544goog.object.transpose = function(obj) {
        545 var transposed = {};
        546 for (var key in obj) {
        547 transposed[obj[key]] = key;
        548 }
        549 return transposed;
        550};
        551
        552
        553/**
        554 * The names of the fields that are defined on Object.prototype.
        555 * @type {Array<string>}
        556 * @private
        557 */
        558goog.object.PROTOTYPE_FIELDS_ = [
        559 'constructor',
        560 'hasOwnProperty',
        561 'isPrototypeOf',
        562 'propertyIsEnumerable',
        563 'toLocaleString',
        564 'toString',
        565 'valueOf'
        566];
        567
        568
        569/**
        570 * Extends an object with another object.
        571 * This operates 'in-place'; it does not create a new Object.
        572 *
        573 * Example:
        574 * var o = {};
        575 * goog.object.extend(o, {a: 0, b: 1});
        576 * o; // {a: 0, b: 1}
        577 * goog.object.extend(o, {b: 2, c: 3});
        578 * o; // {a: 0, b: 2, c: 3}
        579 *
        580 * @param {Object} target The object to modify. Existing properties will be
        581 * overwritten if they are also present in one of the objects in
        582 * {@code var_args}.
        583 * @param {...Object} var_args The objects from which values will be copied.
        584 */
        585goog.object.extend = function(target, var_args) {
        586 var key, source;
        587 for (var i = 1; i < arguments.length; i++) {
        588 source = arguments[i];
        589 for (key in source) {
        590 target[key] = source[key];
        591 }
        592
        593 // For IE the for-in-loop does not contain any properties that are not
        594 // enumerable on the prototype object (for example isPrototypeOf from
        595 // Object.prototype) and it will also not include 'replace' on objects that
        596 // extend String and change 'replace' (not that it is common for anyone to
        597 // extend anything except Object).
        598
        599 for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) {
        600 key = goog.object.PROTOTYPE_FIELDS_[j];
        601 if (Object.prototype.hasOwnProperty.call(source, key)) {
        602 target[key] = source[key];
        603 }
        604 }
        605 }
        606};
        607
        608
        609/**
        610 * Creates a new object built from the key-value pairs provided as arguments.
        611 * @param {...*} var_args If only one argument is provided and it is an array
        612 * then this is used as the arguments, otherwise even arguments are used as
        613 * the property names and odd arguments are used as the property values.
        614 * @return {!Object} The new object.
        615 * @throws {Error} If there are uneven number of arguments or there is only one
        616 * non array argument.
        617 */
        618goog.object.create = function(var_args) {
        619 var argLength = arguments.length;
        620 if (argLength == 1 && goog.isArray(arguments[0])) {
        621 return goog.object.create.apply(null, arguments[0]);
        622 }
        623
        624 if (argLength % 2) {
        625 throw Error('Uneven number of arguments');
        626 }
        627
        628 var rv = {};
        629 for (var i = 0; i < argLength; i += 2) {
        630 rv[arguments[i]] = arguments[i + 1];
        631 }
        632 return rv;
        633};
        634
        635
        636/**
        637 * Creates a new object where the property names come from the arguments but
        638 * the value is always set to true
        639 * @param {...*} var_args If only one argument is provided and it is an array
        640 * then this is used as the arguments, otherwise the arguments are used
        641 * as the property names.
        642 * @return {!Object} The new object.
        643 */
        644goog.object.createSet = function(var_args) {
        645 var argLength = arguments.length;
        646 if (argLength == 1 && goog.isArray(arguments[0])) {
        647 return goog.object.createSet.apply(null, arguments[0]);
        648 }
        649
        650 var rv = {};
        651 for (var i = 0; i < argLength; i++) {
        652 rv[arguments[i]] = true;
        653 }
        654 return rv;
        655};
        656
        657
        658/**
        659 * Creates an immutable view of the underlying object, if the browser
        660 * supports immutable objects.
        661 *
        662 * In default mode, writes to this view will fail silently. In strict mode,
        663 * they will throw an error.
        664 *
        665 * @param {!Object<K,V>} obj An object.
        666 * @return {!Object<K,V>} An immutable view of that object, or the
        667 * original object if this browser does not support immutables.
        668 * @template K,V
        669 */
        670goog.object.createImmutableView = function(obj) {
        671 var result = obj;
        672 if (Object.isFrozen && !Object.isFrozen(obj)) {
        673 result = Object.create(obj);
        674 Object.freeze(result);
        675 }
        676 return result;
        677};
        678
        679
        680/**
        681 * @param {!Object} obj An object.
        682 * @return {boolean} Whether this is an immutable view of the object.
        683 */
        684goog.object.isImmutableView = function(obj) {
        685 return !!Object.isFrozen && Object.isFrozen(obj);
        686};
        \ No newline at end of file diff --git a/docs/source/lib/goog/promise/promise.js.src.html b/docs/source/lib/goog/promise/promise.js.src.html new file mode 100644 index 0000000..f1c2dce --- /dev/null +++ b/docs/source/lib/goog/promise/promise.js.src.html @@ -0,0 +1 @@ +promise.js

        lib/goog/promise/promise.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.Promise');
        16
        17goog.require('goog.Thenable');
        18goog.require('goog.asserts');
        19goog.require('goog.async.FreeList');
        20goog.require('goog.async.run');
        21goog.require('goog.async.throwException');
        22goog.require('goog.debug.Error');
        23goog.require('goog.promise.Resolver');
        24
        25
        26
        27/**
        28 * Promises provide a result that may be resolved asynchronously. A Promise may
        29 * be resolved by being fulfilled with a fulfillment value, rejected with a
        30 * rejection reason, or blocked by another Promise. A Promise is said to be
        31 * settled if it is either fulfilled or rejected. Once settled, the Promise
        32 * result is immutable.
        33 *
        34 * Promises may represent results of any type, including undefined. Rejection
        35 * reasons are typically Errors, but may also be of any type. Closure Promises
        36 * allow for optional type annotations that enforce that fulfillment values are
        37 * of the appropriate types at compile time.
        38 *
        39 * The result of a Promise is accessible by calling {@code then} and registering
        40 * {@code onFulfilled} and {@code onRejected} callbacks. Once the Promise
        41 * is settled, the relevant callbacks are invoked with the fulfillment value or
        42 * rejection reason as argument. Callbacks are always invoked in the order they
        43 * were registered, even when additional {@code then} calls are made from inside
        44 * another callback. A callback is always run asynchronously sometime after the
        45 * scope containing the registering {@code then} invocation has returned.
        46 *
        47 * If a Promise is resolved with another Promise, the first Promise will block
        48 * until the second is settled, and then assumes the same result as the second
        49 * Promise. This allows Promises to depend on the results of other Promises,
        50 * linking together multiple asynchronous operations.
        51 *
        52 * This implementation is compatible with the Promises/A+ specification and
        53 * passes that specification's conformance test suite. A Closure Promise may be
        54 * resolved with a Promise instance (or sufficiently compatible Promise-like
        55 * object) created by other Promise implementations. From the specification,
        56 * Promise-like objects are known as "Thenables".
        57 *
        58 * @see http://promisesaplus.com/
        59 *
        60 * @param {function(
        61 * this:RESOLVER_CONTEXT,
        62 * function((TYPE|IThenable<TYPE>|Thenable)=),
        63 * function(*=)): void} resolver
        64 * Initialization function that is invoked immediately with {@code resolve}
        65 * and {@code reject} functions as arguments. The Promise is resolved or
        66 * rejected with the first argument passed to either function.
        67 * @param {RESOLVER_CONTEXT=} opt_context An optional context for executing the
        68 * resolver function. If unspecified, the resolver function will be executed
        69 * in the default scope.
        70 * @constructor
        71 * @struct
        72 * @final
        73 * @implements {goog.Thenable<TYPE>}
        74 * @template TYPE,RESOLVER_CONTEXT
        75 */
        76goog.Promise = function(resolver, opt_context) {
        77 /**
        78 * The internal state of this Promise. Either PENDING, FULFILLED, REJECTED, or
        79 * BLOCKED.
        80 * @private {goog.Promise.State_}
        81 */
        82 this.state_ = goog.Promise.State_.PENDING;
        83
        84 /**
        85 * The settled result of the Promise. Immutable once set with either a
        86 * fulfillment value or rejection reason.
        87 * @private {*}
        88 */
        89 this.result_ = undefined;
        90
        91 /**
        92 * For Promises created by calling {@code then()}, the originating parent.
        93 * @private {goog.Promise}
        94 */
        95 this.parent_ = null;
        96
        97 /**
        98 * The linked list of {@code onFulfilled} and {@code onRejected} callbacks
        99 * added to this Promise by calls to {@code then()}.
        100 * @private {?goog.Promise.CallbackEntry_}
        101 */
        102 this.callbackEntries_ = null;
        103
        104 /**
        105 * The tail of the linked list of {@code onFulfilled} and {@code onRejected}
        106 * callbacks added to this Promise by calls to {@code then()}.
        107 * @private {?goog.Promise.CallbackEntry_}
        108 */
        109 this.callbackEntriesTail_ = null;
        110
        111 /**
        112 * Whether the Promise is in the queue of Promises to execute.
        113 * @private {boolean}
        114 */
        115 this.executing_ = false;
        116
        117 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
        118 /**
        119 * A timeout ID used when the {@code UNHANDLED_REJECTION_DELAY} is greater
        120 * than 0 milliseconds. The ID is set when the Promise is rejected, and
        121 * cleared only if an {@code onRejected} callback is invoked for the
        122 * Promise (or one of its descendants) before the delay is exceeded.
        123 *
        124 * If the rejection is not handled before the timeout completes, the
        125 * rejection reason is passed to the unhandled rejection handler.
        126 * @private {number}
        127 */
        128 this.unhandledRejectionId_ = 0;
        129 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
        130 /**
        131 * When the {@code UNHANDLED_REJECTION_DELAY} is set to 0 milliseconds, a
        132 * boolean that is set if the Promise is rejected, and reset to false if an
        133 * {@code onRejected} callback is invoked for the Promise (or one of its
        134 * descendants). If the rejection is not handled before the next timestep,
        135 * the rejection reason is passed to the unhandled rejection handler.
        136 * @private {boolean}
        137 */
        138 this.hadUnhandledRejection_ = false;
        139 }
        140
        141 if (goog.Promise.LONG_STACK_TRACES) {
        142 /**
        143 * A list of stack trace frames pointing to the locations where this Promise
        144 * was created or had callbacks added to it. Saved to add additional context
        145 * to stack traces when an exception is thrown.
        146 * @private {!Array<string>}
        147 */
        148 this.stack_ = [];
        149 this.addStackTrace_(new Error('created'));
        150
        151 /**
        152 * Index of the most recently executed stack frame entry.
        153 * @private {number}
        154 */
        155 this.currentStep_ = 0;
        156 }
        157
        158 if (resolver == goog.Promise.RESOLVE_FAST_PATH_) {
        159 // If the special sentinel resolver value is passed (from
        160 // goog.Promise.resolve) we short cut to immediately resolve the promise
        161 // using the value passed as opt_context. Don't try this at home.
        162 this.resolve_(goog.Promise.State_.FULFILLED, opt_context);
        163 } else {
        164 try {
        165 var self = this;
        166 resolver.call(
        167 opt_context,
        168 function(value) {
        169 self.resolve_(goog.Promise.State_.FULFILLED, value);
        170 },
        171 function(reason) {
        172 if (goog.DEBUG &&
        173 !(reason instanceof goog.Promise.CancellationError)) {
        174 try {
        175 // Promise was rejected. Step up one call frame to see why.
        176 if (reason instanceof Error) {
        177 throw reason;
        178 } else {
        179 throw new Error('Promise rejected.');
        180 }
        181 } catch (e) {
        182 // Only thrown so browser dev tools can catch rejections of
        183 // promises when the option to break on caught exceptions is
        184 // activated.
        185 }
        186 }
        187 self.resolve_(goog.Promise.State_.REJECTED, reason);
        188 });
        189 } catch (e) {
        190 this.resolve_(goog.Promise.State_.REJECTED, e);
        191 }
        192 }
        193};
        194
        195
        196/**
        197 * @define {boolean} Whether traces of {@code then} calls should be included in
        198 * exceptions thrown
        199 */
        200goog.define('goog.Promise.LONG_STACK_TRACES', false);
        201
        202
        203/**
        204 * @define {number} The delay in milliseconds before a rejected Promise's reason
        205 * is passed to the rejection handler. By default, the rejection handler
        206 * rethrows the rejection reason so that it appears in the developer console or
        207 * {@code window.onerror} handler.
        208 *
        209 * Rejections are rethrown as quickly as possible by default. A negative value
        210 * disables rejection handling entirely.
        211 */
        212goog.define('goog.Promise.UNHANDLED_REJECTION_DELAY', 0);
        213
        214
        215/**
        216 * The possible internal states for a Promise. These states are not directly
        217 * observable to external callers.
        218 * @enum {number}
        219 * @private
        220 */
        221goog.Promise.State_ = {
        222 /** The Promise is waiting for resolution. */
        223 PENDING: 0,
        224
        225 /** The Promise is blocked waiting for the result of another Thenable. */
        226 BLOCKED: 1,
        227
        228 /** The Promise has been resolved with a fulfillment value. */
        229 FULFILLED: 2,
        230
        231 /** The Promise has been resolved with a rejection reason. */
        232 REJECTED: 3
        233};
        234
        235
        236
        237/**
        238 * Entries in the callback chain. Each call to {@code then},
        239 * {@code thenCatch}, or {@code thenAlways} creates an entry containing the
        240 * functions that may be invoked once the Promise is settled.
        241 *
        242 * @private @final @struct @constructor
        243 */
        244goog.Promise.CallbackEntry_ = function() {
        245 /** @type {?goog.Promise} */
        246 this.child = null;
        247 /** @type {Function} */
        248 this.onFulfilled = null;
        249 /** @type {Function} */
        250 this.onRejected = null;
        251 /** @type {?} */
        252 this.context = null;
        253 /** @type {?goog.Promise.CallbackEntry_} */
        254 this.next = null;
        255
        256 /**
        257 * A boolean value to indicate this is a "thenAlways" callback entry.
        258 * Unlike a normal "then/thenVoid" a "thenAlways doesn't participate
        259 * in "cancel" considerations but is simply an observer and requires
        260 * special handling.
        261 * @type {boolean}
        262 */
        263 this.always = false;
        264};
        265
        266
        267/** clear the object prior to reuse */
        268goog.Promise.CallbackEntry_.prototype.reset = function() {
        269 this.child = null;
        270 this.onFulfilled = null;
        271 this.onRejected = null;
        272 this.context = null;
        273 this.always = false;
        274};
        275
        276
        277/**
        278 * @define {number} The number of currently unused objects to keep around for
        279 * reuse.
        280 */
        281goog.define('goog.Promise.DEFAULT_MAX_UNUSED', 100);
        282
        283
        284/** @const @private {goog.async.FreeList<!goog.Promise.CallbackEntry_>} */
        285goog.Promise.freelist_ = new goog.async.FreeList(
        286 function() {
        287 return new goog.Promise.CallbackEntry_();
        288 },
        289 function(item) {
        290 item.reset();
        291 },
        292 goog.Promise.DEFAULT_MAX_UNUSED);
        293
        294
        295/**
        296 * @param {Function} onFulfilled
        297 * @param {Function} onRejected
        298 * @param {?} context
        299 * @return {!goog.Promise.CallbackEntry_}
        300 * @private
        301 */
        302goog.Promise.getCallbackEntry_ = function(onFulfilled, onRejected, context) {
        303 var entry = goog.Promise.freelist_.get();
        304 entry.onFulfilled = onFulfilled;
        305 entry.onRejected = onRejected;
        306 entry.context = context;
        307 return entry;
        308};
        309
        310
        311/**
        312 * @param {!goog.Promise.CallbackEntry_} entry
        313 * @private
        314 */
        315goog.Promise.returnEntry_ = function(entry) {
        316 goog.Promise.freelist_.put(entry);
        317};
        318
        319
        320/**
        321 * If this passed as the first argument to the {@link goog.Promise} constructor
        322 * the the opt_context is (against its primary use) used to immediately resolve
        323 * the promise. This is used from {@link goog.Promise.resolve} as an
        324 * optimization to avoid allocating 3 closures that are never really needed.
        325 * @private @const {!Function}
        326 */
        327goog.Promise.RESOLVE_FAST_PATH_ = function() {};
        328
        329
        330/**
        331 * @param {(TYPE|goog.Thenable<TYPE>|Thenable)=} opt_value
        332 * @return {!goog.Promise<TYPE>} A new Promise that is immediately resolved
        333 * with the given value. If the input value is already a goog.Promise, it
        334 * will be returned immediately without creating a new instance.
        335 * @template TYPE
        336 */
        337goog.Promise.resolve = function(opt_value) {
        338 if (opt_value instanceof goog.Promise) {
        339 // Avoid creating a new object if we already have a promise object
        340 // of the correct type.
        341 return opt_value;
        342 }
        343
        344 // Passes the value as the context, which is a special fast pass when
        345 // goog.Promise.RESOLVE_FAST_PATH_ is passed as the first argument.
        346 return new goog.Promise(goog.Promise.RESOLVE_FAST_PATH_, opt_value);
        347};
        348
        349
        350/**
        351 * @param {*=} opt_reason
        352 * @return {!goog.Promise} A new Promise that is immediately rejected with the
        353 * given reason.
        354 */
        355goog.Promise.reject = function(opt_reason) {
        356 return new goog.Promise(function(resolve, reject) {
        357 reject(opt_reason);
        358 });
        359};
        360
        361
        362/**
        363 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
        364 * @return {!goog.Promise<TYPE>} A Promise that receives the result of the
        365 * first Promise (or Promise-like) input to settle immediately after it
        366 * settles.
        367 * @template TYPE
        368 */
        369goog.Promise.race = function(promises) {
        370 return new goog.Promise(function(resolve, reject) {
        371 if (!promises.length) {
        372 resolve(undefined);
        373 }
        374 for (var i = 0, promise; promise = promises[i]; i++) {
        375 goog.Promise.maybeThenVoid_(promise, resolve, reject);
        376 }
        377 });
        378};
        379
        380
        381/**
        382 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
        383 * @return {!goog.Promise<!Array<TYPE>>} A Promise that receives a list of
        384 * every fulfilled value once every input Promise (or Promise-like) is
        385 * successfully fulfilled, or is rejected with the first rejection reason
        386 * immediately after it is rejected.
        387 * @template TYPE
        388 */
        389goog.Promise.all = function(promises) {
        390 return new goog.Promise(function(resolve, reject) {
        391 var toFulfill = promises.length;
        392 var values = [];
        393
        394 if (!toFulfill) {
        395 resolve(values);
        396 return;
        397 }
        398
        399 var onFulfill = function(index, value) {
        400 toFulfill--;
        401 values[index] = value;
        402 if (toFulfill == 0) {
        403 resolve(values);
        404 }
        405 };
        406
        407 var onReject = function(reason) {
        408 reject(reason);
        409 };
        410
        411 for (var i = 0, promise; promise = promises[i]; i++) {
        412 goog.Promise.maybeThenVoid_(
        413 promise, goog.partial(onFulfill, i), onReject);
        414 }
        415 });
        416};
        417
        418
        419/**
        420 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
        421 * @return {!goog.Promise<!Array<{
        422 * fulfilled: boolean,
        423 * value: (TYPE|undefined),
        424 * reason: (*|undefined)}>>} A Promise that resolves with a list of
        425 * result objects once all input Promises (or Promise-like) have
        426 * settled. Each result object contains a 'fulfilled' boolean indicating
        427 * whether an input Promise was fulfilled or rejected. For fulfilled
        428 * Promises, the resulting value is stored in the 'value' field. For
        429 * rejected Promises, the rejection reason is stored in the 'reason'
        430 * field.
        431 * @template TYPE
        432 */
        433goog.Promise.allSettled = function(promises) {
        434 return new goog.Promise(function(resolve, reject) {
        435 var toSettle = promises.length;
        436 var results = [];
        437
        438 if (!toSettle) {
        439 resolve(results);
        440 return;
        441 }
        442
        443 var onSettled = function(index, fulfilled, result) {
        444 toSettle--;
        445 results[index] = fulfilled ?
        446 {fulfilled: true, value: result} :
        447 {fulfilled: false, reason: result};
        448 if (toSettle == 0) {
        449 resolve(results);
        450 }
        451 };
        452
        453 for (var i = 0, promise; promise = promises[i]; i++) {
        454 goog.Promise.maybeThenVoid_(promise,
        455 goog.partial(onSettled, i, true /* fulfilled */),
        456 goog.partial(onSettled, i, false /* fulfilled */));
        457 }
        458 });
        459};
        460
        461
        462/**
        463 * @param {!Array<!(goog.Thenable<TYPE>|Thenable)>} promises
        464 * @return {!goog.Promise<TYPE>} A Promise that receives the value of the first
        465 * input to be fulfilled, or is rejected with a list of every rejection
        466 * reason if all inputs are rejected.
        467 * @template TYPE
        468 */
        469goog.Promise.firstFulfilled = function(promises) {
        470 return new goog.Promise(function(resolve, reject) {
        471 var toReject = promises.length;
        472 var reasons = [];
        473
        474 if (!toReject) {
        475 resolve(undefined);
        476 return;
        477 }
        478
        479 var onFulfill = function(value) {
        480 resolve(value);
        481 };
        482
        483 var onReject = function(index, reason) {
        484 toReject--;
        485 reasons[index] = reason;
        486 if (toReject == 0) {
        487 reject(reasons);
        488 }
        489 };
        490
        491 for (var i = 0, promise; promise = promises[i]; i++) {
        492 goog.Promise.maybeThenVoid_(
        493 promise, onFulfill, goog.partial(onReject, i));
        494 }
        495 });
        496};
        497
        498
        499/**
        500 * @return {!goog.promise.Resolver<TYPE>} Resolver wrapping the promise and its
        501 * resolve / reject functions. Resolving or rejecting the resolver
        502 * resolves or rejects the promise.
        503 * @template TYPE
        504 */
        505goog.Promise.withResolver = function() {
        506 var resolve, reject;
        507 var promise = new goog.Promise(function(rs, rj) {
        508 resolve = rs;
        509 reject = rj;
        510 });
        511 return new goog.Promise.Resolver_(promise, resolve, reject);
        512};
        513
        514
        515/**
        516 * Adds callbacks that will operate on the result of the Promise, returning a
        517 * new child Promise.
        518 *
        519 * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
        520 * with the fulfillment value as argument, and the child Promise will be
        521 * fulfilled with the return value of the callback. If the callback throws an
        522 * exception, the child Promise will be rejected with the thrown value instead.
        523 *
        524 * If the Promise is rejected, the {@code onRejected} callback will be invoked
        525 * with the rejection reason as argument, and the child Promise will be resolved
        526 * with the return value or rejected with the thrown value of the callback.
        527 *
        528 * @override
        529 */
        530goog.Promise.prototype.then = function(
        531 opt_onFulfilled, opt_onRejected, opt_context) {
        532
        533 if (opt_onFulfilled != null) {
        534 goog.asserts.assertFunction(opt_onFulfilled,
        535 'opt_onFulfilled should be a function.');
        536 }
        537 if (opt_onRejected != null) {
        538 goog.asserts.assertFunction(opt_onRejected,
        539 'opt_onRejected should be a function. Did you pass opt_context ' +
        540 'as the second argument instead of the third?');
        541 }
        542
        543 if (goog.Promise.LONG_STACK_TRACES) {
        544 this.addStackTrace_(new Error('then'));
        545 }
        546
        547 return this.addChildPromise_(
        548 goog.isFunction(opt_onFulfilled) ? opt_onFulfilled : null,
        549 goog.isFunction(opt_onRejected) ? opt_onRejected : null,
        550 opt_context);
        551};
        552goog.Thenable.addImplementation(goog.Promise);
        553
        554
        555/**
        556 * Adds callbacks that will operate on the result of the Promise without
        557 * returning a child Promise (unlike "then").
        558 *
        559 * If the Promise is fulfilled, the {@code onFulfilled} callback will be invoked
        560 * with the fulfillment value as argument.
        561 *
        562 * If the Promise is rejected, the {@code onRejected} callback will be invoked
        563 * with the rejection reason as argument.
        564 *
        565 * @param {?(function(this:THIS, TYPE):?)=} opt_onFulfilled A
        566 * function that will be invoked with the fulfillment value if the Promise
        567 * is fulfilled.
        568 * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
        569 * be invoked with the rejection reason if the Promise is rejected.
        570 * @param {THIS=} opt_context An optional context object that will be the
        571 * execution context for the callbacks. By default, functions are executed
        572 * with the default this.
        573 * @package
        574 * @template THIS
        575 */
        576goog.Promise.prototype.thenVoid = function(
        577 opt_onFulfilled, opt_onRejected, opt_context) {
        578
        579 if (opt_onFulfilled != null) {
        580 goog.asserts.assertFunction(opt_onFulfilled,
        581 'opt_onFulfilled should be a function.');
        582 }
        583 if (opt_onRejected != null) {
        584 goog.asserts.assertFunction(opt_onRejected,
        585 'opt_onRejected should be a function. Did you pass opt_context ' +
        586 'as the second argument instead of the third?');
        587 }
        588
        589 if (goog.Promise.LONG_STACK_TRACES) {
        590 this.addStackTrace_(new Error('then'));
        591 }
        592
        593 // Note: no default rejection handler is provided here as we need to
        594 // distinguish unhandled rejections.
        595 this.addCallbackEntry_(goog.Promise.getCallbackEntry_(
        596 opt_onFulfilled || goog.nullFunction,
        597 opt_onRejected || null,
        598 opt_context));
        599};
        600
        601
        602/**
        603 * Calls "thenVoid" if possible to avoid allocating memory. Otherwise calls
        604 * "then".
        605 * @param {(goog.Thenable<TYPE>|Thenable)} promise
        606 * @param {function(this:THIS, TYPE): ?} onFulfilled
        607 * @param {function(this:THIS, *): *} onRejected
        608 * @param {THIS=} opt_context
        609 * @template THIS,TYPE
        610 * @private
        611 */
        612goog.Promise.maybeThenVoid_ = function(
        613 promise, onFulfilled, onRejected, opt_context) {
        614 if (promise instanceof goog.Promise) {
        615 promise.thenVoid(onFulfilled, onRejected, opt_context);
        616 } else {
        617 promise.then(onFulfilled, onRejected, opt_context);
        618 }
        619};
        620
        621
        622/**
        623 * Adds a callback that will be invoked when the Promise is settled (fulfilled
        624 * or rejected). The callback receives no argument, and no new child Promise is
        625 * created. This is useful for ensuring that cleanup takes place after certain
        626 * asynchronous operations. Callbacks added with {@code thenAlways} will be
        627 * executed in the same order with other calls to {@code then},
        628 * {@code thenAlways}, or {@code thenCatch}.
        629 *
        630 * Since it does not produce a new child Promise, cancellation propagation is
        631 * not prevented by adding callbacks with {@code thenAlways}. A Promise that has
        632 * a cleanup handler added with {@code thenAlways} will be canceled if all of
        633 * its children created by {@code then} (or {@code thenCatch}) are canceled.
        634 * Additionally, since any rejections are not passed to the callback, it does
        635 * not stop the unhandled rejection handler from running.
        636 *
        637 * @param {function(this:THIS): void} onSettled A function that will be invoked
        638 * when the Promise is settled (fulfilled or rejected).
        639 * @param {THIS=} opt_context An optional context object that will be the
        640 * execution context for the callbacks. By default, functions are executed
        641 * in the global scope.
        642 * @return {!goog.Promise<TYPE>} This Promise, for chaining additional calls.
        643 * @template THIS
        644 */
        645goog.Promise.prototype.thenAlways = function(onSettled, opt_context) {
        646 if (goog.Promise.LONG_STACK_TRACES) {
        647 this.addStackTrace_(new Error('thenAlways'));
        648 }
        649
        650 var entry = goog.Promise.getCallbackEntry_(onSettled, onSettled, opt_context);
        651 entry.always = true;
        652 this.addCallbackEntry_(entry);
        653 return this;
        654};
        655
        656
        657/**
        658 * Adds a callback that will be invoked only if the Promise is rejected. This
        659 * is equivalent to {@code then(null, onRejected)}.
        660 *
        661 * @param {!function(this:THIS, *): *} onRejected A function that will be
        662 * invoked with the rejection reason if the Promise is rejected.
        663 * @param {THIS=} opt_context An optional context object that will be the
        664 * execution context for the callbacks. By default, functions are executed
        665 * in the global scope.
        666 * @return {!goog.Promise} A new Promise that will receive the result of the
        667 * callback.
        668 * @template THIS
        669 */
        670goog.Promise.prototype.thenCatch = function(onRejected, opt_context) {
        671 if (goog.Promise.LONG_STACK_TRACES) {
        672 this.addStackTrace_(new Error('thenCatch'));
        673 }
        674 return this.addChildPromise_(null, onRejected, opt_context);
        675};
        676
        677
        678/**
        679 * Cancels the Promise if it is still pending by rejecting it with a cancel
        680 * Error. No action is performed if the Promise is already resolved.
        681 *
        682 * All child Promises of the canceled Promise will be rejected with the same
        683 * cancel error, as with normal Promise rejection. If the Promise to be canceled
        684 * is the only child of a pending Promise, the parent Promise will also be
        685 * canceled. Cancellation may propagate upward through multiple generations.
        686 *
        687 * @param {string=} opt_message An optional debugging message for describing the
        688 * cancellation reason.
        689 */
        690goog.Promise.prototype.cancel = function(opt_message) {
        691 if (this.state_ == goog.Promise.State_.PENDING) {
        692 goog.async.run(function() {
        693 var err = new goog.Promise.CancellationError(opt_message);
        694 this.cancelInternal_(err);
        695 }, this);
        696 }
        697};
        698
        699
        700/**
        701 * Cancels this Promise with the given error.
        702 *
        703 * @param {!Error} err The cancellation error.
        704 * @private
        705 */
        706goog.Promise.prototype.cancelInternal_ = function(err) {
        707 if (this.state_ == goog.Promise.State_.PENDING) {
        708 if (this.parent_) {
        709 // Cancel the Promise and remove it from the parent's child list.
        710 this.parent_.cancelChild_(this, err);
        711 this.parent_ = null;
        712 } else {
        713 this.resolve_(goog.Promise.State_.REJECTED, err);
        714 }
        715 }
        716};
        717
        718
        719/**
        720 * Cancels a child Promise from the list of callback entries. If the Promise has
        721 * not already been resolved, reject it with a cancel error. If there are no
        722 * other children in the list of callback entries, propagate the cancellation
        723 * by canceling this Promise as well.
        724 *
        725 * @param {!goog.Promise} childPromise The Promise to cancel.
        726 * @param {!Error} err The cancel error to use for rejecting the Promise.
        727 * @private
        728 */
        729goog.Promise.prototype.cancelChild_ = function(childPromise, err) {
        730 if (!this.callbackEntries_) {
        731 return;
        732 }
        733 var childCount = 0;
        734 var childEntry = null;
        735 var beforeChildEntry = null;
        736
        737 // Find the callback entry for the childPromise, and count whether there are
        738 // additional child Promises.
        739 for (var entry = this.callbackEntries_; entry; entry = entry.next) {
        740 if (!entry.always) {
        741 childCount++;
        742 if (entry.child == childPromise) {
        743 childEntry = entry;
        744 }
        745 if (childEntry && childCount > 1) {
        746 break;
        747 }
        748 }
        749 if (!childEntry) {
        750 beforeChildEntry = entry;
        751 }
        752 }
        753
        754 // Can a child entry be missing?
        755
        756 // If the child Promise was the only child, cancel this Promise as well.
        757 // Otherwise, reject only the child Promise with the cancel error.
        758 if (childEntry) {
        759 if (this.state_ == goog.Promise.State_.PENDING && childCount == 1) {
        760 this.cancelInternal_(err);
        761 } else {
        762 if (beforeChildEntry) {
        763 this.removeEntryAfter_(beforeChildEntry);
        764 } else {
        765 this.popEntry_();
        766 }
        767
        768 this.executeCallback_(
        769 childEntry, goog.Promise.State_.REJECTED, err);
        770 }
        771 }
        772};
        773
        774
        775/**
        776 * Adds a callback entry to the current Promise, and schedules callback
        777 * execution if the Promise has already been settled.
        778 *
        779 * @param {goog.Promise.CallbackEntry_} callbackEntry Record containing
        780 * {@code onFulfilled} and {@code onRejected} callbacks to execute after
        781 * the Promise is settled.
        782 * @private
        783 */
        784goog.Promise.prototype.addCallbackEntry_ = function(callbackEntry) {
        785 if (!this.hasEntry_() &&
        786 (this.state_ == goog.Promise.State_.FULFILLED ||
        787 this.state_ == goog.Promise.State_.REJECTED)) {
        788 this.scheduleCallbacks_();
        789 }
        790 this.queueEntry_(callbackEntry);
        791};
        792
        793
        794/**
        795 * Creates a child Promise and adds it to the callback entry list. The result of
        796 * the child Promise is determined by the state of the parent Promise and the
        797 * result of the {@code onFulfilled} or {@code onRejected} callbacks as
        798 * specified in the Promise resolution procedure.
        799 *
        800 * @see http://promisesaplus.com/#the__method
        801 *
        802 * @param {?function(this:THIS, TYPE):
        803 * (RESULT|goog.Promise<RESULT>|Thenable)} onFulfilled A callback that
        804 * will be invoked if the Promise is fullfilled, or null.
        805 * @param {?function(this:THIS, *): *} onRejected A callback that will be
        806 * invoked if the Promise is rejected, or null.
        807 * @param {THIS=} opt_context An optional execution context for the callbacks.
        808 * in the default calling context.
        809 * @return {!goog.Promise} The child Promise.
        810 * @template RESULT,THIS
        811 * @private
        812 */
        813goog.Promise.prototype.addChildPromise_ = function(
        814 onFulfilled, onRejected, opt_context) {
        815
        816 /** @type {goog.Promise.CallbackEntry_} */
        817 var callbackEntry = goog.Promise.getCallbackEntry_(null, null, null);
        818
        819 callbackEntry.child = new goog.Promise(function(resolve, reject) {
        820 // Invoke onFulfilled, or resolve with the parent's value if absent.
        821 callbackEntry.onFulfilled = onFulfilled ? function(value) {
        822 try {
        823 var result = onFulfilled.call(opt_context, value);
        824 resolve(result);
        825 } catch (err) {
        826 reject(err);
        827 }
        828 } : resolve;
        829
        830 // Invoke onRejected, or reject with the parent's reason if absent.
        831 callbackEntry.onRejected = onRejected ? function(reason) {
        832 try {
        833 var result = onRejected.call(opt_context, reason);
        834 if (!goog.isDef(result) &&
        835 reason instanceof goog.Promise.CancellationError) {
        836 // Propagate cancellation to children if no other result is returned.
        837 reject(reason);
        838 } else {
        839 resolve(result);
        840 }
        841 } catch (err) {
        842 reject(err);
        843 }
        844 } : reject;
        845 });
        846
        847 callbackEntry.child.parent_ = this;
        848 this.addCallbackEntry_(callbackEntry);
        849 return callbackEntry.child;
        850};
        851
        852
        853/**
        854 * Unblocks the Promise and fulfills it with the given value.
        855 *
        856 * @param {TYPE} value
        857 * @private
        858 */
        859goog.Promise.prototype.unblockAndFulfill_ = function(value) {
        860 goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
        861 this.state_ = goog.Promise.State_.PENDING;
        862 this.resolve_(goog.Promise.State_.FULFILLED, value);
        863};
        864
        865
        866/**
        867 * Unblocks the Promise and rejects it with the given rejection reason.
        868 *
        869 * @param {*} reason
        870 * @private
        871 */
        872goog.Promise.prototype.unblockAndReject_ = function(reason) {
        873 goog.asserts.assert(this.state_ == goog.Promise.State_.BLOCKED);
        874 this.state_ = goog.Promise.State_.PENDING;
        875 this.resolve_(goog.Promise.State_.REJECTED, reason);
        876};
        877
        878
        879/**
        880 * Attempts to resolve a Promise with a given resolution state and value. This
        881 * is a no-op if the given Promise has already been resolved.
        882 *
        883 * If the given result is a Thenable (such as another Promise), the Promise will
        884 * be settled with the same state and result as the Thenable once it is itself
        885 * settled.
        886 *
        887 * If the given result is not a Thenable, the Promise will be settled (fulfilled
        888 * or rejected) with that result based on the given state.
        889 *
        890 * @see http://promisesaplus.com/#the_promise_resolution_procedure
        891 *
        892 * @param {goog.Promise.State_} state
        893 * @param {*} x The result to apply to the Promise.
        894 * @private
        895 */
        896goog.Promise.prototype.resolve_ = function(state, x) {
        897 if (this.state_ != goog.Promise.State_.PENDING) {
        898 return;
        899 }
        900
        901 if (this == x) {
        902 state = goog.Promise.State_.REJECTED;
        903 x = new TypeError('Promise cannot resolve to itself');
        904
        905 } else if (goog.Thenable.isImplementedBy(x)) {
        906 x = /** @type {!goog.Thenable} */ (x);
        907 this.state_ = goog.Promise.State_.BLOCKED;
        908 goog.Promise.maybeThenVoid_(
        909 x, this.unblockAndFulfill_, this.unblockAndReject_, this);
        910 return;
        911 } else if (goog.isObject(x)) {
        912 try {
        913 var then = x['then'];
        914 if (goog.isFunction(then)) {
        915 this.tryThen_(x, then);
        916 return;
        917 }
        918 } catch (e) {
        919 state = goog.Promise.State_.REJECTED;
        920 x = e;
        921 }
        922 }
        923
        924 this.result_ = x;
        925 this.state_ = state;
        926 // Since we can no longer be cancelled, remove link to parent, so that the
        927 // child promise does not keep the parent promise alive.
        928 this.parent_ = null;
        929 this.scheduleCallbacks_();
        930
        931 if (state == goog.Promise.State_.REJECTED &&
        932 !(x instanceof goog.Promise.CancellationError)) {
        933 goog.Promise.addUnhandledRejection_(this, x);
        934 }
        935};
        936
        937
        938/**
        939 * Attempts to call the {@code then} method on an object in the hopes that it is
        940 * a Promise-compatible instance. This allows interoperation between different
        941 * Promise implementations, however a non-compliant object may cause a Promise
        942 * to hang indefinitely. If the {@code then} method throws an exception, the
        943 * dependent Promise will be rejected with the thrown value.
        944 *
        945 * @see http://promisesaplus.com/#point-70
        946 *
        947 * @param {Thenable} thenable An object with a {@code then} method that may be
        948 * compatible with the Promise/A+ specification.
        949 * @param {!Function} then The {@code then} method of the Thenable object.
        950 * @private
        951 */
        952goog.Promise.prototype.tryThen_ = function(thenable, then) {
        953 this.state_ = goog.Promise.State_.BLOCKED;
        954 var promise = this;
        955 var called = false;
        956
        957 var resolve = function(value) {
        958 if (!called) {
        959 called = true;
        960 promise.unblockAndFulfill_(value);
        961 }
        962 };
        963
        964 var reject = function(reason) {
        965 if (!called) {
        966 called = true;
        967 promise.unblockAndReject_(reason);
        968 }
        969 };
        970
        971 try {
        972 then.call(thenable, resolve, reject);
        973 } catch (e) {
        974 reject(e);
        975 }
        976};
        977
        978
        979/**
        980 * Executes the pending callbacks of a settled Promise after a timeout.
        981 *
        982 * Section 2.2.4 of the Promises/A+ specification requires that Promise
        983 * callbacks must only be invoked from a call stack that only contains Promise
        984 * implementation code, which we accomplish by invoking callback execution after
        985 * a timeout. If {@code startExecution_} is called multiple times for the same
        986 * Promise, the callback chain will be evaluated only once. Additional callbacks
        987 * may be added during the evaluation phase, and will be executed in the same
        988 * event loop.
        989 *
        990 * All Promises added to the waiting list during the same browser event loop
        991 * will be executed in one batch to avoid using a separate timeout per Promise.
        992 *
        993 * @private
        994 */
        995goog.Promise.prototype.scheduleCallbacks_ = function() {
        996 if (!this.executing_) {
        997 this.executing_ = true;
        998 goog.async.run(this.executeCallbacks_, this);
        999 }
        1000};
        1001
        1002
        1003/**
        1004 * @return {boolean} Whether there are any pending callbacks queued.
        1005 * @private
        1006 */
        1007goog.Promise.prototype.hasEntry_ = function() {
        1008 return !!this.callbackEntries_;
        1009};
        1010
        1011
        1012/**
        1013 * @param {goog.Promise.CallbackEntry_} entry
        1014 * @private
        1015 */
        1016goog.Promise.prototype.queueEntry_ = function(entry) {
        1017 goog.asserts.assert(entry.onFulfilled != null);
        1018
        1019 if (this.callbackEntriesTail_) {
        1020 this.callbackEntriesTail_.next = entry;
        1021 this.callbackEntriesTail_ = entry;
        1022 } else {
        1023 // It the work queue was empty set the head too.
        1024 this.callbackEntries_ = entry;
        1025 this.callbackEntriesTail_ = entry;
        1026 }
        1027};
        1028
        1029
        1030/**
        1031 * @return {goog.Promise.CallbackEntry_} entry
        1032 * @private
        1033 */
        1034goog.Promise.prototype.popEntry_ = function() {
        1035 var entry = null;
        1036 if (this.callbackEntries_) {
        1037 entry = this.callbackEntries_;
        1038 this.callbackEntries_ = entry.next;
        1039 entry.next = null;
        1040 }
        1041 // It the work queue is empty clear the tail too.
        1042 if (!this.callbackEntries_) {
        1043 this.callbackEntriesTail_ = null;
        1044 }
        1045
        1046 if (entry != null) {
        1047 goog.asserts.assert(entry.onFulfilled != null);
        1048 }
        1049 return entry;
        1050};
        1051
        1052
        1053/**
        1054 * @param {goog.Promise.CallbackEntry_} previous
        1055 * @private
        1056 */
        1057goog.Promise.prototype.removeEntryAfter_ = function(previous) {
        1058 goog.asserts.assert(this.callbackEntries_);
        1059 goog.asserts.assert(previous != null);
        1060 // If the last entry is being removed, update the tail
        1061 if (previous.next == this.callbackEntriesTail_) {
        1062 this.callbackEntriesTail_ = previous;
        1063 }
        1064
        1065 previous.next = previous.next.next;
        1066};
        1067
        1068
        1069/**
        1070 * Executes all pending callbacks for this Promise.
        1071 *
        1072 * @private
        1073 */
        1074goog.Promise.prototype.executeCallbacks_ = function() {
        1075 var entry = null;
        1076 while (entry = this.popEntry_()) {
        1077 if (goog.Promise.LONG_STACK_TRACES) {
        1078 this.currentStep_++;
        1079 }
        1080 this.executeCallback_(entry, this.state_, this.result_);
        1081 }
        1082 this.executing_ = false;
        1083};
        1084
        1085
        1086/**
        1087 * Executes a pending callback for this Promise. Invokes an {@code onFulfilled}
        1088 * or {@code onRejected} callback based on the settled state of the Promise.
        1089 *
        1090 * @param {!goog.Promise.CallbackEntry_} callbackEntry An entry containing the
        1091 * onFulfilled and/or onRejected callbacks for this step.
        1092 * @param {goog.Promise.State_} state The resolution status of the Promise,
        1093 * either FULFILLED or REJECTED.
        1094 * @param {*} result The settled result of the Promise.
        1095 * @private
        1096 */
        1097goog.Promise.prototype.executeCallback_ = function(
        1098 callbackEntry, state, result) {
        1099 // Cancel an unhandled rejection if the then/thenVoid call had an onRejected.
        1100 if (state == goog.Promise.State_.REJECTED &&
        1101 callbackEntry.onRejected && !callbackEntry.always) {
        1102 this.removeUnhandledRejection_();
        1103 }
        1104
        1105 if (callbackEntry.child) {
        1106 // When the parent is settled, the child no longer needs to hold on to it,
        1107 // as the parent can no longer be canceled.
        1108 callbackEntry.child.parent_ = null;
        1109 goog.Promise.invokeCallback_(callbackEntry, state, result);
        1110 } else {
        1111 // Callbacks created with thenAlways or thenVoid do not have the rejection
        1112 // handling code normally set up in the child Promise.
        1113 try {
        1114 callbackEntry.always ?
        1115 callbackEntry.onFulfilled.call(callbackEntry.context) :
        1116 goog.Promise.invokeCallback_(callbackEntry, state, result);
        1117 } catch (err) {
        1118 goog.Promise.handleRejection_.call(null, err);
        1119 }
        1120 }
        1121 goog.Promise.returnEntry_(callbackEntry);
        1122};
        1123
        1124
        1125/**
        1126 * Executes the onFulfilled or onRejected callback for a callbackEntry.
        1127 *
        1128 * @param {!goog.Promise.CallbackEntry_} callbackEntry
        1129 * @param {goog.Promise.State_} state
        1130 * @param {*} result
        1131 * @private
        1132 */
        1133goog.Promise.invokeCallback_ = function(callbackEntry, state, result) {
        1134 if (state == goog.Promise.State_.FULFILLED) {
        1135 callbackEntry.onFulfilled.call(callbackEntry.context, result);
        1136 } else if (callbackEntry.onRejected) {
        1137 callbackEntry.onRejected.call(callbackEntry.context, result);
        1138 }
        1139};
        1140
        1141
        1142/**
        1143 * Records a stack trace entry for functions that call {@code then} or the
        1144 * Promise constructor. May be disabled by unsetting {@code LONG_STACK_TRACES}.
        1145 *
        1146 * @param {!Error} err An Error object created by the calling function for
        1147 * providing a stack trace.
        1148 * @private
        1149 */
        1150goog.Promise.prototype.addStackTrace_ = function(err) {
        1151 if (goog.Promise.LONG_STACK_TRACES && goog.isString(err.stack)) {
        1152 // Extract the third line of the stack trace, which is the entry for the
        1153 // user function that called into Promise code.
        1154 var trace = err.stack.split('\n', 4)[3];
        1155 var message = err.message;
        1156
        1157 // Pad the message to align the traces.
        1158 message += Array(11 - message.length).join(' ');
        1159 this.stack_.push(message + trace);
        1160 }
        1161};
        1162
        1163
        1164/**
        1165 * Adds extra stack trace information to an exception for the list of
        1166 * asynchronous {@code then} calls that have been run for this Promise. Stack
        1167 * trace information is recorded in {@see #addStackTrace_}, and appended to
        1168 * rethrown errors when {@code LONG_STACK_TRACES} is enabled.
        1169 *
        1170 * @param {*} err An unhandled exception captured during callback execution.
        1171 * @private
        1172 */
        1173goog.Promise.prototype.appendLongStack_ = function(err) {
        1174 if (goog.Promise.LONG_STACK_TRACES &&
        1175 err && goog.isString(err.stack) && this.stack_.length) {
        1176 var longTrace = ['Promise trace:'];
        1177
        1178 for (var promise = this; promise; promise = promise.parent_) {
        1179 for (var i = this.currentStep_; i >= 0; i--) {
        1180 longTrace.push(promise.stack_[i]);
        1181 }
        1182 longTrace.push('Value: ' +
        1183 '[' + (promise.state_ == goog.Promise.State_.REJECTED ?
        1184 'REJECTED' : 'FULFILLED') + '] ' +
        1185 '<' + String(promise.result_) + '>');
        1186 }
        1187 err.stack += '\n\n' + longTrace.join('\n');
        1188 }
        1189};
        1190
        1191
        1192/**
        1193 * Marks this rejected Promise as having being handled. Also marks any parent
        1194 * Promises in the rejected state as handled. The rejection handler will no
        1195 * longer be invoked for this Promise (if it has not been called already).
        1196 *
        1197 * @private
        1198 */
        1199goog.Promise.prototype.removeUnhandledRejection_ = function() {
        1200 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
        1201 for (var p = this; p && p.unhandledRejectionId_; p = p.parent_) {
        1202 goog.global.clearTimeout(p.unhandledRejectionId_);
        1203 p.unhandledRejectionId_ = 0;
        1204 }
        1205 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
        1206 for (var p = this; p && p.hadUnhandledRejection_; p = p.parent_) {
        1207 p.hadUnhandledRejection_ = false;
        1208 }
        1209 }
        1210};
        1211
        1212
        1213/**
        1214 * Marks this rejected Promise as unhandled. If no {@code onRejected} callback
        1215 * is called for this Promise before the {@code UNHANDLED_REJECTION_DELAY}
        1216 * expires, the reason will be passed to the unhandled rejection handler. The
        1217 * handler typically rethrows the rejection reason so that it becomes visible in
        1218 * the developer console.
        1219 *
        1220 * @param {!goog.Promise} promise The rejected Promise.
        1221 * @param {*} reason The Promise rejection reason.
        1222 * @private
        1223 */
        1224goog.Promise.addUnhandledRejection_ = function(promise, reason) {
        1225 if (goog.Promise.UNHANDLED_REJECTION_DELAY > 0) {
        1226 promise.unhandledRejectionId_ = goog.global.setTimeout(function() {
        1227 promise.appendLongStack_(reason);
        1228 goog.Promise.handleRejection_.call(null, reason);
        1229 }, goog.Promise.UNHANDLED_REJECTION_DELAY);
        1230
        1231 } else if (goog.Promise.UNHANDLED_REJECTION_DELAY == 0) {
        1232 promise.hadUnhandledRejection_ = true;
        1233 goog.async.run(function() {
        1234 if (promise.hadUnhandledRejection_) {
        1235 promise.appendLongStack_(reason);
        1236 goog.Promise.handleRejection_.call(null, reason);
        1237 }
        1238 });
        1239 }
        1240};
        1241
        1242
        1243/**
        1244 * A method that is invoked with the rejection reasons for Promises that are
        1245 * rejected but have no {@code onRejected} callbacks registered yet.
        1246 * @type {function(*)}
        1247 * @private
        1248 */
        1249goog.Promise.handleRejection_ = goog.async.throwException;
        1250
        1251
        1252/**
        1253 * Sets a handler that will be called with reasons from unhandled rejected
        1254 * Promises. If the rejected Promise (or one of its descendants) has an
        1255 * {@code onRejected} callback registered, the rejection will be considered
        1256 * handled, and the rejection handler will not be called.
        1257 *
        1258 * By default, unhandled rejections are rethrown so that the error may be
        1259 * captured by the developer console or a {@code window.onerror} handler.
        1260 *
        1261 * @param {function(*)} handler A function that will be called with reasons from
        1262 * rejected Promises. Defaults to {@code goog.async.throwException}.
        1263 */
        1264goog.Promise.setUnhandledRejectionHandler = function(handler) {
        1265 goog.Promise.handleRejection_ = handler;
        1266};
        1267
        1268
        1269
        1270/**
        1271 * Error used as a rejection reason for canceled Promises.
        1272 *
        1273 * @param {string=} opt_message
        1274 * @constructor
        1275 * @extends {goog.debug.Error}
        1276 * @final
        1277 */
        1278goog.Promise.CancellationError = function(opt_message) {
        1279 goog.Promise.CancellationError.base(this, 'constructor', opt_message);
        1280};
        1281goog.inherits(goog.Promise.CancellationError, goog.debug.Error);
        1282
        1283
        1284/** @override */
        1285goog.Promise.CancellationError.prototype.name = 'cancel';
        1286
        1287
        1288
        1289/**
        1290 * Internal implementation of the resolver interface.
        1291 *
        1292 * @param {!goog.Promise<TYPE>} promise
        1293 * @param {function((TYPE|goog.Promise<TYPE>|Thenable)=)} resolve
        1294 * @param {function(*=): void} reject
        1295 * @implements {goog.promise.Resolver<TYPE>}
        1296 * @final @struct
        1297 * @constructor
        1298 * @private
        1299 * @template TYPE
        1300 */
        1301goog.Promise.Resolver_ = function(promise, resolve, reject) {
        1302 /** @const */
        1303 this.promise = promise;
        1304
        1305 /** @const */
        1306 this.resolve = resolve;
        1307
        1308 /** @const */
        1309 this.reject = reject;
        1310};
        \ No newline at end of file diff --git a/docs/source/lib/goog/promise/resolver.js.src.html b/docs/source/lib/goog/promise/resolver.js.src.html new file mode 100644 index 0000000..2c307f5 --- /dev/null +++ b/docs/source/lib/goog/promise/resolver.js.src.html @@ -0,0 +1 @@ +resolver.js

        lib/goog/promise/resolver.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.promise.Resolver');
        16
        17
        18
        19/**
        20 * Resolver interface for promises. The resolver is a convenience interface that
        21 * bundles the promise and its associated resolve and reject functions together,
        22 * for cases where the resolver needs to be persisted internally.
        23 *
        24 * @interface
        25 * @template TYPE
        26 */
        27goog.promise.Resolver = function() {};
        28
        29
        30/**
        31 * The promise that created this resolver.
        32 * @type {!goog.Promise<TYPE>}
        33 */
        34goog.promise.Resolver.prototype.promise;
        35
        36
        37/**
        38 * Resolves this resolver with the specified value.
        39 * @type {function((TYPE|goog.Promise<TYPE>|Thenable)=)}
        40 */
        41goog.promise.Resolver.prototype.resolve;
        42
        43
        44/**
        45 * Rejects this resolver with the specified reason.
        46 * @type {function(*=): void}
        47 */
        48goog.promise.Resolver.prototype.reject;
        \ No newline at end of file diff --git a/docs/source/lib/goog/promise/thenable.js.src.html b/docs/source/lib/goog/promise/thenable.js.src.html new file mode 100644 index 0000000..8fe1d98 --- /dev/null +++ b/docs/source/lib/goog/promise/thenable.js.src.html @@ -0,0 +1 @@ +thenable.js

        lib/goog/promise/thenable.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.Thenable');
        16
        17
        18
        19/**
        20 * Provides a more strict interface for Thenables in terms of
        21 * http://promisesaplus.com for interop with {@see goog.Promise}.
        22 *
        23 * @interface
        24 * @extends {IThenable<TYPE>}
        25 * @template TYPE
        26 */
        27goog.Thenable = function() {};
        28
        29
        30/**
        31 * Adds callbacks that will operate on the result of the Thenable, returning a
        32 * new child Promise.
        33 *
        34 * If the Thenable is fulfilled, the {@code onFulfilled} callback will be
        35 * invoked with the fulfillment value as argument, and the child Promise will
        36 * be fulfilled with the return value of the callback. If the callback throws
        37 * an exception, the child Promise will be rejected with the thrown value
        38 * instead.
        39 *
        40 * If the Thenable is rejected, the {@code onRejected} callback will be invoked
        41 * with the rejection reason as argument, and the child Promise will be rejected
        42 * with the return value of the callback or thrown value.
        43 *
        44 * @param {?(function(this:THIS, TYPE): VALUE)=} opt_onFulfilled A
        45 * function that will be invoked with the fulfillment value if the Promise
        46 * is fullfilled.
        47 * @param {?(function(this:THIS, *): *)=} opt_onRejected A function that will
        48 * be invoked with the rejection reason if the Promise is rejected.
        49 * @param {THIS=} opt_context An optional context object that will be the
        50 * execution context for the callbacks. By default, functions are executed
        51 * with the default this.
        52 *
        53 * @return {RESULT} A new Promise that will receive the result
        54 * of the fulfillment or rejection callback.
        55 * @template VALUE
        56 * @template THIS
        57 *
        58 * When a Promise (or thenable) is returned from the fulfilled callback,
        59 * the result is the payload of that promise, not the promise itself.
        60 *
        61 * @template RESULT := type('goog.Promise',
        62 * cond(isUnknown(VALUE), unknown(),
        63 * mapunion(VALUE, (V) =>
        64 * cond(isTemplatized(V) && sub(rawTypeOf(V), 'IThenable'),
        65 * templateTypeOf(V, 0),
        66 * cond(sub(V, 'Thenable'),
        67 * unknown(),
        68 * V)))))
        69 * =:
        70 *
        71 */
        72goog.Thenable.prototype.then = function(opt_onFulfilled, opt_onRejected,
        73 opt_context) {};
        74
        75
        76/**
        77 * An expando property to indicate that an object implements
        78 * {@code goog.Thenable}.
        79 *
        80 * {@see addImplementation}.
        81 *
        82 * @const
        83 */
        84goog.Thenable.IMPLEMENTED_BY_PROP = '$goog_Thenable';
        85
        86
        87/**
        88 * Marks a given class (constructor) as an implementation of Thenable, so
        89 * that we can query that fact at runtime. The class must have already
        90 * implemented the interface.
        91 * Exports a 'then' method on the constructor prototype, so that the objects
        92 * also implement the extern {@see goog.Thenable} interface for interop with
        93 * other Promise implementations.
        94 * @param {function(new:goog.Thenable,...?)} ctor The class constructor. The
        95 * corresponding class must have already implemented the interface.
        96 */
        97goog.Thenable.addImplementation = function(ctor) {
        98 goog.exportProperty(ctor.prototype, 'then', ctor.prototype.then);
        99 if (COMPILED) {
        100 ctor.prototype[goog.Thenable.IMPLEMENTED_BY_PROP] = true;
        101 } else {
        102 // Avoids dictionary access in uncompiled mode.
        103 ctor.prototype.$goog_Thenable = true;
        104 }
        105};
        106
        107
        108/**
        109 * @param {*} object
        110 * @return {boolean} Whether a given instance implements {@code goog.Thenable}.
        111 * The class/superclass of the instance must call {@code addImplementation}.
        112 */
        113goog.Thenable.isImplementedBy = function(object) {
        114 if (!object) {
        115 return false;
        116 }
        117 try {
        118 if (COMPILED) {
        119 return !!object[goog.Thenable.IMPLEMENTED_BY_PROP];
        120 }
        121 return !!object.$goog_Thenable;
        122 } catch (e) {
        123 // Property access seems to be forbidden.
        124 return false;
        125 }
        126};
        \ No newline at end of file diff --git a/docs/source/lib/goog/reflect/reflect.js.src.html b/docs/source/lib/goog/reflect/reflect.js.src.html new file mode 100644 index 0000000..0d50a56 --- /dev/null +++ b/docs/source/lib/goog/reflect/reflect.js.src.html @@ -0,0 +1 @@ +reflect.js

        lib/goog/reflect/reflect.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Useful compiler idioms.
        17 *
        18 * @author johnlenz@google.com (John Lenz)
        19 */
        20
        21goog.provide('goog.reflect');
        22
        23
        24/**
        25 * Syntax for object literal casts.
        26 * @see http://go/jscompiler-renaming
        27 * @see http://code.google.com/p/closure-compiler/wiki/
        28 * ExperimentalTypeBasedPropertyRenaming
        29 *
        30 * Use this if you have an object literal whose keys need to have the same names
        31 * as the properties of some class even after they are renamed by the compiler.
        32 *
        33 * @param {!Function} type Type to cast to.
        34 * @param {Object} object Object literal to cast.
        35 * @return {Object} The object literal.
        36 */
        37goog.reflect.object = function(type, object) {
        38 return object;
        39};
        40
        41
        42/**
        43 * To assert to the compiler that an operation is needed when it would
        44 * otherwise be stripped. For example:
        45 * <code>
        46 * // Force a layout
        47 * goog.reflect.sinkValue(dialog.offsetHeight);
        48 * </code>
        49 * @type {!Function}
        50 */
        51goog.reflect.sinkValue = function(x) {
        52 goog.reflect.sinkValue[' '](x);
        53 return x;
        54};
        55
        56
        57/**
        58 * The compiler should optimize this function away iff no one ever uses
        59 * goog.reflect.sinkValue.
        60 */
        61goog.reflect.sinkValue[' '] = goog.nullFunction;
        62
        63
        64/**
        65 * Check if a property can be accessed without throwing an exception.
        66 * @param {Object} obj The owner of the property.
        67 * @param {string} prop The property name.
        68 * @return {boolean} Whether the property is accessible. Will also return true
        69 * if obj is null.
        70 */
        71goog.reflect.canAccessProperty = function(obj, prop) {
        72 /** @preserveTry */
        73 try {
        74 goog.reflect.sinkValue(obj[prop]);
        75 return true;
        76 } catch (e) {}
        77 return false;
        78};
        \ No newline at end of file diff --git a/docs/source/lib/goog/string/const.js.src.html b/docs/source/lib/goog/string/const.js.src.html new file mode 100644 index 0000000..09902da --- /dev/null +++ b/docs/source/lib/goog/string/const.js.src.html @@ -0,0 +1 @@ +const.js

        lib/goog/string/const.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.string.Const');
        16
        17goog.require('goog.asserts');
        18goog.require('goog.string.TypedString');
        19
        20
        21
        22/**
        23 * Wrapper for compile-time-constant strings.
        24 *
        25 * Const is a wrapper for strings that can only be created from program
        26 * constants (i.e., string literals). This property relies on a custom Closure
        27 * compiler check that {@code goog.string.Const.from} is only invoked on
        28 * compile-time-constant expressions.
        29 *
        30 * Const is useful in APIs whose correct and secure use requires that certain
        31 * arguments are not attacker controlled: Compile-time constants are inherently
        32 * under the control of the application and not under control of external
        33 * attackers, and hence are safe to use in such contexts.
        34 *
        35 * Instances of this type must be created via its factory method
        36 * {@code goog.string.Const.from} and not by invoking its constructor. The
        37 * constructor intentionally takes no parameters and the type is immutable;
        38 * hence only a default instance corresponding to the empty string can be
        39 * obtained via constructor invocation.
        40 *
        41 * @see goog.string.Const#from
        42 * @constructor
        43 * @final
        44 * @struct
        45 * @implements {goog.string.TypedString}
        46 */
        47goog.string.Const = function() {
        48 /**
        49 * The wrapped value of this Const object. The field has a purposely ugly
        50 * name to make (non-compiled) code that attempts to directly access this
        51 * field stand out.
        52 * @private {string}
        53 */
        54 this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ = '';
        55
        56 /**
        57 * A type marker used to implement additional run-time type checking.
        58 * @see goog.string.Const#unwrap
        59 * @const
        60 * @private
        61 */
        62 this.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ =
        63 goog.string.Const.TYPE_MARKER_;
        64};
        65
        66
        67/**
        68 * @override
        69 * @const
        70 */
        71goog.string.Const.prototype.implementsGoogStringTypedString = true;
        72
        73
        74/**
        75 * Returns this Const's value a string.
        76 *
        77 * IMPORTANT: In code where it is security-relevant that an object's type is
        78 * indeed {@code goog.string.Const}, use {@code goog.string.Const.unwrap}
        79 * instead of this method.
        80 *
        81 * @see goog.string.Const#unwrap
        82 * @override
        83 */
        84goog.string.Const.prototype.getTypedStringValue = function() {
        85 return this.stringConstValueWithSecurityContract__googStringSecurityPrivate_;
        86};
        87
        88
        89/**
        90 * Returns a debug-string representation of this value.
        91 *
        92 * To obtain the actual string value wrapped inside an object of this type,
        93 * use {@code goog.string.Const.unwrap}.
        94 *
        95 * @see goog.string.Const#unwrap
        96 * @override
        97 */
        98goog.string.Const.prototype.toString = function() {
        99 return 'Const{' +
        100 this.stringConstValueWithSecurityContract__googStringSecurityPrivate_ +
        101 '}';
        102};
        103
        104
        105/**
        106 * Performs a runtime check that the provided object is indeed an instance
        107 * of {@code goog.string.Const}, and returns its value.
        108 * @param {!goog.string.Const} stringConst The object to extract from.
        109 * @return {string} The Const object's contained string, unless the run-time
        110 * type check fails. In that case, {@code unwrap} returns an innocuous
        111 * string, or, if assertions are enabled, throws
        112 * {@code goog.asserts.AssertionError}.
        113 */
        114goog.string.Const.unwrap = function(stringConst) {
        115 // Perform additional run-time type-checking to ensure that stringConst is
        116 // indeed an instance of the expected type. This provides some additional
        117 // protection against security bugs due to application code that disables type
        118 // checks.
        119 if (stringConst instanceof goog.string.Const &&
        120 stringConst.constructor === goog.string.Const &&
        121 stringConst.STRING_CONST_TYPE_MARKER__GOOG_STRING_SECURITY_PRIVATE_ ===
        122 goog.string.Const.TYPE_MARKER_) {
        123 return stringConst.
        124 stringConstValueWithSecurityContract__googStringSecurityPrivate_;
        125 } else {
        126 goog.asserts.fail('expected object of type Const, got \'' +
        127 stringConst + '\'');
        128 return 'type_error:Const';
        129 }
        130};
        131
        132
        133/**
        134 * Creates a Const object from a compile-time constant string.
        135 *
        136 * It is illegal to invoke this function on an expression whose
        137 * compile-time-contant value cannot be determined by the Closure compiler.
        138 *
        139 * Correct invocations include,
        140 * <pre>
        141 * var s = goog.string.Const.from('hello');
        142 * var t = goog.string.Const.from('hello' + 'world');
        143 * </pre>
        144 *
        145 * In contrast, the following are illegal:
        146 * <pre>
        147 * var s = goog.string.Const.from(getHello());
        148 * var t = goog.string.Const.from('hello' + world);
        149 * </pre>
        150 *
        151 * TODO(user): Compile-time checks that this function is only called
        152 * with compile-time constant expressions.
        153 *
        154 * @param {string} s A constant string from which to create a Const.
        155 * @return {!goog.string.Const} A Const object initialized to stringConst.
        156 */
        157goog.string.Const.from = function(s) {
        158 return goog.string.Const.create__googStringSecurityPrivate_(s);
        159};
        160
        161
        162/**
        163 * Type marker for the Const type, used to implement additional run-time
        164 * type checking.
        165 * @const
        166 * @private
        167 */
        168goog.string.Const.TYPE_MARKER_ = {};
        169
        170
        171/**
        172 * Utility method to create Const instances.
        173 * @param {string} s The string to initialize the Const object with.
        174 * @return {!goog.string.Const} The initialized Const object.
        175 * @private
        176 */
        177goog.string.Const.create__googStringSecurityPrivate_ = function(s) {
        178 var stringConst = new goog.string.Const();
        179 stringConst.stringConstValueWithSecurityContract__googStringSecurityPrivate_ =
        180 s;
        181 return stringConst;
        182};
        \ No newline at end of file diff --git a/docs/source/lib/goog/string/string.js.src.html b/docs/source/lib/goog/string/string.js.src.html index 76c8210..340e35a 100644 --- a/docs/source/lib/goog/string/string.js.src.html +++ b/docs/source/lib/goog/string/string.js.src.html @@ -1 +1 @@ -string.js

        lib/goog/string/string.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for string manipulation.
        17 */
        18
        19
        20/**
        21 * Namespace for string utilities
        22 */
        23goog.provide('goog.string');
        24goog.provide('goog.string.Unicode');
        25
        26
        27/**
        28 * Common Unicode string characters.
        29 * @enum {string}
        30 */
        31goog.string.Unicode = {
        32 NBSP: '\xa0'
        33};
        34
        35
        36/**
        37 * Fast prefix-checker.
        38 * @param {string} str The string to check.
        39 * @param {string} prefix A string to look for at the start of {@code str}.
        40 * @return {boolean} True if {@code str} begins with {@code prefix}.
        41 */
        42goog.string.startsWith = function(str, prefix) {
        43 return str.lastIndexOf(prefix, 0) == 0;
        44};
        45
        46
        47/**
        48 * Fast suffix-checker.
        49 * @param {string} str The string to check.
        50 * @param {string} suffix A string to look for at the end of {@code str}.
        51 * @return {boolean} True if {@code str} ends with {@code suffix}.
        52 */
        53goog.string.endsWith = function(str, suffix) {
        54 var l = str.length - suffix.length;
        55 return l >= 0 && str.indexOf(suffix, l) == l;
        56};
        57
        58
        59/**
        60 * Case-insensitive prefix-checker.
        61 * @param {string} str The string to check.
        62 * @param {string} prefix A string to look for at the end of {@code str}.
        63 * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
        64 * case).
        65 */
        66goog.string.caseInsensitiveStartsWith = function(str, prefix) {
        67 return goog.string.caseInsensitiveCompare(
        68 prefix, str.substr(0, prefix.length)) == 0;
        69};
        70
        71
        72/**
        73 * Case-insensitive suffix-checker.
        74 * @param {string} str The string to check.
        75 * @param {string} suffix A string to look for at the end of {@code str}.
        76 * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
        77 * case).
        78 */
        79goog.string.caseInsensitiveEndsWith = function(str, suffix) {
        80 return goog.string.caseInsensitiveCompare(
        81 suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
        82};
        83
        84
        85/**
        86 * Case-insensitive equality checker.
        87 * @param {string} str1 First string to check.
        88 * @param {string} str2 Second string to check.
        89 * @return {boolean} True if {@code str1} and {@code str2} are the same string,
        90 * ignoring case.
        91 */
        92goog.string.caseInsensitiveEquals = function(str1, str2) {
        93 return str1.toLowerCase() == str2.toLowerCase();
        94};
        95
        96
        97/**
        98 * Does simple python-style string substitution.
        99 * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
        100 * @param {string} str The string containing the pattern.
        101 * @param {...*} var_args The items to substitute into the pattern.
        102 * @return {string} A copy of {@code str} in which each occurrence of
        103 * {@code %s} has been replaced an argument from {@code var_args}.
        104 */
        105goog.string.subs = function(str, var_args) {
        106 var splitParts = str.split('%s');
        107 var returnString = '';
        108
        109 var subsArguments = Array.prototype.slice.call(arguments, 1);
        110 while (subsArguments.length &&
        111 // Replace up to the last split part. We are inserting in the
        112 // positions between split parts.
        113 splitParts.length > 1) {
        114 returnString += splitParts.shift() + subsArguments.shift();
        115 }
        116
        117 return returnString + splitParts.join('%s'); // Join unused '%s'
        118};
        119
        120
        121/**
        122 * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
        123 * and tabs) to a single space, and strips leading and trailing whitespace.
        124 * @param {string} str Input string.
        125 * @return {string} A copy of {@code str} with collapsed whitespace.
        126 */
        127goog.string.collapseWhitespace = function(str) {
        128 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        129 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        130 // include it in the regexp to enforce consistent cross-browser behavior.
        131 return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
        132};
        133
        134
        135/**
        136 * Checks if a string is empty or contains only whitespaces.
        137 * @param {string} str The string to check.
        138 * @return {boolean} True if {@code str} is empty or whitespace only.
        139 */
        140goog.string.isEmpty = function(str) {
        141 // testing length == 0 first is actually slower in all browsers (about the
        142 // same in Opera).
        143 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        144 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        145 // include it in the regexp to enforce consistent cross-browser behavior.
        146 return /^[\s\xa0]*$/.test(str);
        147};
        148
        149
        150/**
        151 * Checks if a string is null, undefined, empty or contains only whitespaces.
        152 * @param {*} str The string to check.
        153 * @return {boolean} True if{@code str} is null, undefined, empty, or
        154 * whitespace only.
        155 */
        156goog.string.isEmptySafe = function(str) {
        157 return goog.string.isEmpty(goog.string.makeSafe(str));
        158};
        159
        160
        161/**
        162 * Checks if a string is all breaking whitespace.
        163 * @param {string} str The string to check.
        164 * @return {boolean} Whether the string is all breaking whitespace.
        165 */
        166goog.string.isBreakingWhitespace = function(str) {
        167 return !/[^\t\n\r ]/.test(str);
        168};
        169
        170
        171/**
        172 * Checks if a string contains all letters.
        173 * @param {string} str string to check.
        174 * @return {boolean} True if {@code str} consists entirely of letters.
        175 */
        176goog.string.isAlpha = function(str) {
        177 return !/[^a-zA-Z]/.test(str);
        178};
        179
        180
        181/**
        182 * Checks if a string contains only numbers.
        183 * @param {*} str string to check. If not a string, it will be
        184 * casted to one.
        185 * @return {boolean} True if {@code str} is numeric.
        186 */
        187goog.string.isNumeric = function(str) {
        188 return !/[^0-9]/.test(str);
        189};
        190
        191
        192/**
        193 * Checks if a string contains only numbers or letters.
        194 * @param {string} str string to check.
        195 * @return {boolean} True if {@code str} is alphanumeric.
        196 */
        197goog.string.isAlphaNumeric = function(str) {
        198 return !/[^a-zA-Z0-9]/.test(str);
        199};
        200
        201
        202/**
        203 * Checks if a character is a space character.
        204 * @param {string} ch Character to check.
        205 * @return {boolean} True if {code ch} is a space.
        206 */
        207goog.string.isSpace = function(ch) {
        208 return ch == ' ';
        209};
        210
        211
        212/**
        213 * Checks if a character is a valid unicode character.
        214 * @param {string} ch Character to check.
        215 * @return {boolean} True if {code ch} is a valid unicode character.
        216 */
        217goog.string.isUnicodeChar = function(ch) {
        218 return ch.length == 1 && ch >= ' ' && ch <= '~' ||
        219 ch >= '\u0080' && ch <= '\uFFFD';
        220};
        221
        222
        223/**
        224 * Takes a string and replaces newlines with a space. Multiple lines are
        225 * replaced with a single space.
        226 * @param {string} str The string from which to strip newlines.
        227 * @return {string} A copy of {@code str} stripped of newlines.
        228 */
        229goog.string.stripNewlines = function(str) {
        230 return str.replace(/(\r\n|\r|\n)+/g, ' ');
        231};
        232
        233
        234/**
        235 * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
        236 * @param {string} str The string to in which to canonicalize newlines.
        237 * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
        238 */
        239goog.string.canonicalizeNewlines = function(str) {
        240 return str.replace(/(\r\n|\r|\n)/g, '\n');
        241};
        242
        243
        244/**
        245 * Normalizes whitespace in a string, replacing all whitespace chars with
        246 * a space.
        247 * @param {string} str The string in which to normalize whitespace.
        248 * @return {string} A copy of {@code str} with all whitespace normalized.
        249 */
        250goog.string.normalizeWhitespace = function(str) {
        251 return str.replace(/\xa0|\s/g, ' ');
        252};
        253
        254
        255/**
        256 * Normalizes spaces in a string, replacing all consecutive spaces and tabs
        257 * with a single space. Replaces non-breaking space with a space.
        258 * @param {string} str The string in which to normalize spaces.
        259 * @return {string} A copy of {@code str} with all consecutive spaces and tabs
        260 * replaced with a single space.
        261 */
        262goog.string.normalizeSpaces = function(str) {
        263 return str.replace(/\xa0|[ \t]+/g, ' ');
        264};
        265
        266
        267/**
        268 * Removes the breaking spaces from the left and right of the string and
        269 * collapses the sequences of breaking spaces in the middle into single spaces.
        270 * The original and the result strings render the same way in HTML.
        271 * @param {string} str A string in which to collapse spaces.
        272 * @return {string} Copy of the string with normalized breaking spaces.
        273 */
        274goog.string.collapseBreakingSpaces = function(str) {
        275 return str.replace(/[\t\r\n ]+/g, ' ').replace(
        276 /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
        277};
        278
        279
        280/**
        281 * Trims white spaces to the left and right of a string.
        282 * @param {string} str The string to trim.
        283 * @return {string} A trimmed copy of {@code str}.
        284 */
        285goog.string.trim = function(str) {
        286 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        287 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        288 // include it in the regexp to enforce consistent cross-browser behavior.
        289 return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
        290};
        291
        292
        293/**
        294 * Trims whitespaces at the left end of a string.
        295 * @param {string} str The string to left trim.
        296 * @return {string} A trimmed copy of {@code str}.
        297 */
        298goog.string.trimLeft = function(str) {
        299 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        300 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        301 // include it in the regexp to enforce consistent cross-browser behavior.
        302 return str.replace(/^[\s\xa0]+/, '');
        303};
        304
        305
        306/**
        307 * Trims whitespaces at the right end of a string.
        308 * @param {string} str The string to right trim.
        309 * @return {string} A trimmed copy of {@code str}.
        310 */
        311goog.string.trimRight = function(str) {
        312 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        313 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        314 // include it in the regexp to enforce consistent cross-browser behavior.
        315 return str.replace(/[\s\xa0]+$/, '');
        316};
        317
        318
        319/**
        320 * A string comparator that ignores case.
        321 * -1 = str1 less than str2
        322 * 0 = str1 equals str2
        323 * 1 = str1 greater than str2
        324 *
        325 * @param {string} str1 The string to compare.
        326 * @param {string} str2 The string to compare {@code str1} to.
        327 * @return {number} The comparator result, as described above.
        328 */
        329goog.string.caseInsensitiveCompare = function(str1, str2) {
        330 var test1 = String(str1).toLowerCase();
        331 var test2 = String(str2).toLowerCase();
        332
        333 if (test1 < test2) {
        334 return -1;
        335 } else if (test1 == test2) {
        336 return 0;
        337 } else {
        338 return 1;
        339 }
        340};
        341
        342
        343/**
        344 * Regular expression used for splitting a string into substrings of fractional
        345 * numbers, integers, and non-numeric characters.
        346 * @type {RegExp}
        347 * @private
        348 */
        349goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
        350
        351
        352/**
        353 * String comparison function that handles numbers in a way humans might expect.
        354 * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
        355 * comparison is mostly case-insensitive, though strings that are identical
        356 * except for case are sorted with the upper-case strings before lower-case.
        357 *
        358 * This comparison function is significantly slower (about 500x) than either
        359 * the default or the case-insensitive compare. It should not be used in
        360 * time-critical code, but should be fast enough to sort several hundred short
        361 * strings (like filenames) with a reasonable delay.
        362 *
        363 * @param {string} str1 The string to compare in a numerically sensitive way.
        364 * @param {string} str2 The string to compare {@code str1} to.
        365 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
        366 * 0 if str1 > str2.
        367 */
        368goog.string.numerateCompare = function(str1, str2) {
        369 if (str1 == str2) {
        370 return 0;
        371 }
        372 if (!str1) {
        373 return -1;
        374 }
        375 if (!str2) {
        376 return 1;
        377 }
        378
        379 // Using match to split the entire string ahead of time turns out to be faster
        380 // for most inputs than using RegExp.exec or iterating over each character.
        381 var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
        382 var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
        383
        384 var count = Math.min(tokens1.length, tokens2.length);
        385
        386 for (var i = 0; i < count; i++) {
        387 var a = tokens1[i];
        388 var b = tokens2[i];
        389
        390 // Compare pairs of tokens, returning if one token sorts before the other.
        391 if (a != b) {
        392
        393 // Only if both tokens are integers is a special comparison required.
        394 // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
        395 var num1 = parseInt(a, 10);
        396 if (!isNaN(num1)) {
        397 var num2 = parseInt(b, 10);
        398 if (!isNaN(num2) && num1 - num2) {
        399 return num1 - num2;
        400 }
        401 }
        402 return a < b ? -1 : 1;
        403 }
        404 }
        405
        406 // If one string is a substring of the other, the shorter string sorts first.
        407 if (tokens1.length != tokens2.length) {
        408 return tokens1.length - tokens2.length;
        409 }
        410
        411 // The two strings must be equivalent except for case (perfect equality is
        412 // tested at the head of the function.) Revert to default ASCII-betical string
        413 // comparison to stablize the sort.
        414 return str1 < str2 ? -1 : 1;
        415};
        416
        417
        418/**
        419 * URL-encodes a string
        420 * @param {*} str The string to url-encode.
        421 * @return {string} An encoded copy of {@code str} that is safe for urls.
        422 * Note that '#', ':', and other characters used to delimit portions
        423 * of URLs *will* be encoded.
        424 */
        425goog.string.urlEncode = function(str) {
        426 return encodeURIComponent(String(str));
        427};
        428
        429
        430/**
        431 * URL-decodes the string. We need to specially handle '+'s because
        432 * the javascript library doesn't convert them to spaces.
        433 * @param {string} str The string to url decode.
        434 * @return {string} The decoded {@code str}.
        435 */
        436goog.string.urlDecode = function(str) {
        437 return decodeURIComponent(str.replace(/\+/g, ' '));
        438};
        439
        440
        441/**
        442 * Converts \n to <br>s or <br />s.
        443 * @param {string} str The string in which to convert newlines.
        444 * @param {boolean=} opt_xml Whether to use XML compatible tags.
        445 * @return {string} A copy of {@code str} with converted newlines.
        446 */
        447goog.string.newLineToBr = function(str, opt_xml) {
        448 return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
        449};
        450
        451
        452/**
        453 * Escape double quote '"' characters in addition to '&', '<', and '>' so that a
        454 * string can be included in an HTML tag attribute value within double quotes.
        455 *
        456 * It should be noted that > doesn't need to be escaped for the HTML or XML to
        457 * be valid, but it has been decided to escape it for consistency with other
        458 * implementations.
        459 *
        460 * NOTE(user):
        461 * HtmlEscape is often called during the generation of large blocks of HTML.
        462 * Using statics for the regular expressions and strings is an optimization
        463 * that can more than half the amount of time IE spends in this function for
        464 * large apps, since strings and regexes both contribute to GC allocations.
        465 *
        466 * Testing for the presence of a character before escaping increases the number
        467 * of function calls, but actually provides a speed increase for the average
        468 * case -- since the average case often doesn't require the escaping of all 4
        469 * characters and indexOf() is much cheaper than replace().
        470 * The worst case does suffer slightly from the additional calls, therefore the
        471 * opt_isLikelyToContainHtmlChars option has been included for situations
        472 * where all 4 HTML entities are very likely to be present and need escaping.
        473 *
        474 * Some benchmarks (times tended to fluctuate +-0.05ms):
        475 * FireFox IE6
        476 * (no chars / average (mix of cases) / all 4 chars)
        477 * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
        478 * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
        479 * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
        480 *
        481 * An additional advantage of checking if replace actually needs to be called
        482 * is a reduction in the number of object allocations, so as the size of the
        483 * application grows the difference between the various methods would increase.
        484 *
        485 * @param {string} str string to be escaped.
        486 * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
        487 * if the character needs replacing - use this option if you expect each of
        488 * the characters to appear often. Leave false if you expect few html
        489 * characters to occur in your strings, such as if you are escaping HTML.
        490 * @return {string} An escaped copy of {@code str}.
        491 */
        492goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
        493
        494 if (opt_isLikelyToContainHtmlChars) {
        495 return str.replace(goog.string.amperRe_, '&amp;')
        496 .replace(goog.string.ltRe_, '&lt;')
        497 .replace(goog.string.gtRe_, '&gt;')
        498 .replace(goog.string.quotRe_, '&quot;');
        499
        500 } else {
        501 // quick test helps in the case when there are no chars to replace, in
        502 // worst case this makes barely a difference to the time taken
        503 if (!goog.string.allRe_.test(str)) return str;
        504
        505 // str.indexOf is faster than regex.test in this case
        506 if (str.indexOf('&') != -1) {
        507 str = str.replace(goog.string.amperRe_, '&amp;');
        508 }
        509 if (str.indexOf('<') != -1) {
        510 str = str.replace(goog.string.ltRe_, '&lt;');
        511 }
        512 if (str.indexOf('>') != -1) {
        513 str = str.replace(goog.string.gtRe_, '&gt;');
        514 }
        515 if (str.indexOf('"') != -1) {
        516 str = str.replace(goog.string.quotRe_, '&quot;');
        517 }
        518 return str;
        519 }
        520};
        521
        522
        523/**
        524 * Regular expression that matches an ampersand, for use in escaping.
        525 * @type {RegExp}
        526 * @private
        527 */
        528goog.string.amperRe_ = /&/g;
        529
        530
        531/**
        532 * Regular expression that matches a less than sign, for use in escaping.
        533 * @type {RegExp}
        534 * @private
        535 */
        536goog.string.ltRe_ = /</g;
        537
        538
        539/**
        540 * Regular expression that matches a greater than sign, for use in escaping.
        541 * @type {RegExp}
        542 * @private
        543 */
        544goog.string.gtRe_ = />/g;
        545
        546
        547/**
        548 * Regular expression that matches a double quote, for use in escaping.
        549 * @type {RegExp}
        550 * @private
        551 */
        552goog.string.quotRe_ = /\"/g;
        553
        554
        555/**
        556 * Regular expression that matches any character that needs to be escaped.
        557 * @type {RegExp}
        558 * @private
        559 */
        560goog.string.allRe_ = /[&<>\"]/;
        561
        562
        563/**
        564 * Unescapes an HTML string.
        565 *
        566 * @param {string} str The string to unescape.
        567 * @return {string} An unescaped copy of {@code str}.
        568 */
        569goog.string.unescapeEntities = function(str) {
        570 if (goog.string.contains(str, '&')) {
        571 // We are careful not to use a DOM if we do not have one. We use the []
        572 // notation so that the JSCompiler will not complain about these objects and
        573 // fields in the case where we have no DOM.
        574 if ('document' in goog.global) {
        575 return goog.string.unescapeEntitiesUsingDom_(str);
        576 } else {
        577 // Fall back on pure XML entities
        578 return goog.string.unescapePureXmlEntities_(str);
        579 }
        580 }
        581 return str;
        582};
        583
        584
        585/**
        586 * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
        587 * entities. This function is XSS-safe and whitespace-preserving.
        588 * @private
        589 * @param {string} str The string to unescape.
        590 * @return {string} The unescaped {@code str} string.
        591 */
        592goog.string.unescapeEntitiesUsingDom_ = function(str) {
        593 var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
        594 var div = document.createElement('div');
        595 // Match as many valid entity characters as possible. If the actual entity
        596 // happens to be shorter, it will still work as innerHTML will return the
        597 // trailing characters unchanged. Since the entity characters do not include
        598 // open angle bracket, there is no chance of XSS from the innerHTML use.
        599 // Since no whitespace is passed to innerHTML, whitespace is preserved.
        600 return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
        601 // Check for cached entity.
        602 var value = seen[s];
        603 if (value) {
        604 return value;
        605 }
        606 // Check for numeric entity.
        607 if (entity.charAt(0) == '#') {
        608 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
        609 var n = Number('0' + entity.substr(1));
        610 if (!isNaN(n)) {
        611 value = String.fromCharCode(n);
        612 }
        613 }
        614 // Fall back to innerHTML otherwise.
        615 if (!value) {
        616 // Append a non-entity character to avoid a bug in Webkit that parses
        617 // an invalid entity at the end of innerHTML text as the empty string.
        618 div.innerHTML = s + ' ';
        619 // Then remove the trailing character from the result.
        620 value = div.firstChild.nodeValue.slice(0, -1);
        621 }
        622 // Cache and return.
        623 return seen[s] = value;
        624 });
        625};
        626
        627
        628/**
        629 * Unescapes XML entities.
        630 * @private
        631 * @param {string} str The string to unescape.
        632 * @return {string} An unescaped copy of {@code str}.
        633 */
        634goog.string.unescapePureXmlEntities_ = function(str) {
        635 return str.replace(/&([^;]+);/g, function(s, entity) {
        636 switch (entity) {
        637 case 'amp':
        638 return '&';
        639 case 'lt':
        640 return '<';
        641 case 'gt':
        642 return '>';
        643 case 'quot':
        644 return '"';
        645 default:
        646 if (entity.charAt(0) == '#') {
        647 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
        648 var n = Number('0' + entity.substr(1));
        649 if (!isNaN(n)) {
        650 return String.fromCharCode(n);
        651 }
        652 }
        653 // For invalid entities we just return the entity
        654 return s;
        655 }
        656 });
        657};
        658
        659
        660/**
        661 * Regular expression that matches an HTML entity.
        662 * See also HTML5: Tokenization / Tokenizing character references.
        663 * @private
        664 * @type {!RegExp}
        665 */
        666goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
        667
        668
        669/**
        670 * Do escaping of whitespace to preserve spatial formatting. We use character
        671 * entity #160 to make it safer for xml.
        672 * @param {string} str The string in which to escape whitespace.
        673 * @param {boolean=} opt_xml Whether to use XML compatible tags.
        674 * @return {string} An escaped copy of {@code str}.
        675 */
        676goog.string.whitespaceEscape = function(str, opt_xml) {
        677 return goog.string.newLineToBr(str.replace(/ /g, ' &#160;'), opt_xml);
        678};
        679
        680
        681/**
        682 * Strip quote characters around a string. The second argument is a string of
        683 * characters to treat as quotes. This can be a single character or a string of
        684 * multiple character and in that case each of those are treated as possible
        685 * quote characters. For example:
        686 *
        687 * <pre>
        688 * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
        689 * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
        690 * </pre>
        691 *
        692 * @param {string} str The string to strip.
        693 * @param {string} quoteChars The quote characters to strip.
        694 * @return {string} A copy of {@code str} without the quotes.
        695 */
        696goog.string.stripQuotes = function(str, quoteChars) {
        697 var length = quoteChars.length;
        698 for (var i = 0; i < length; i++) {
        699 var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
        700 if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
        701 return str.substring(1, str.length - 1);
        702 }
        703 }
        704 return str;
        705};
        706
        707
        708/**
        709 * Truncates a string to a certain length and adds '...' if necessary. The
        710 * length also accounts for the ellipsis, so a maximum length of 10 and a string
        711 * 'Hello World!' produces 'Hello W...'.
        712 * @param {string} str The string to truncate.
        713 * @param {number} chars Max number of characters.
        714 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
        715 * characters from being cut off in the middle.
        716 * @return {string} The truncated {@code str} string.
        717 */
        718goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
        719 if (opt_protectEscapedCharacters) {
        720 str = goog.string.unescapeEntities(str);
        721 }
        722
        723 if (str.length > chars) {
        724 str = str.substring(0, chars - 3) + '...';
        725 }
        726
        727 if (opt_protectEscapedCharacters) {
        728 str = goog.string.htmlEscape(str);
        729 }
        730
        731 return str;
        732};
        733
        734
        735/**
        736 * Truncate a string in the middle, adding "..." if necessary,
        737 * and favoring the beginning of the string.
        738 * @param {string} str The string to truncate the middle of.
        739 * @param {number} chars Max number of characters.
        740 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
        741 * characters from being cutoff in the middle.
        742 * @param {number=} opt_trailingChars Optional number of trailing characters to
        743 * leave at the end of the string, instead of truncating as close to the
        744 * middle as possible.
        745 * @return {string} A truncated copy of {@code str}.
        746 */
        747goog.string.truncateMiddle = function(str, chars,
        748 opt_protectEscapedCharacters, opt_trailingChars) {
        749 if (opt_protectEscapedCharacters) {
        750 str = goog.string.unescapeEntities(str);
        751 }
        752
        753 if (opt_trailingChars && str.length > chars) {
        754 if (opt_trailingChars > chars) {
        755 opt_trailingChars = chars;
        756 }
        757 var endPoint = str.length - opt_trailingChars;
        758 var startPoint = chars - opt_trailingChars;
        759 str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
        760 } else if (str.length > chars) {
        761 // Favor the beginning of the string:
        762 var half = Math.floor(chars / 2);
        763 var endPos = str.length - half;
        764 half += chars % 2;
        765 str = str.substring(0, half) + '...' + str.substring(endPos);
        766 }
        767
        768 if (opt_protectEscapedCharacters) {
        769 str = goog.string.htmlEscape(str);
        770 }
        771
        772 return str;
        773};
        774
        775
        776/**
        777 * Special chars that need to be escaped for goog.string.quote.
        778 * @private
        779 * @type {Object}
        780 */
        781goog.string.specialEscapeChars_ = {
        782 '\0': '\\0',
        783 '\b': '\\b',
        784 '\f': '\\f',
        785 '\n': '\\n',
        786 '\r': '\\r',
        787 '\t': '\\t',
        788 '\x0B': '\\x0B', // '\v' is not supported in JScript
        789 '"': '\\"',
        790 '\\': '\\\\'
        791};
        792
        793
        794/**
        795 * Character mappings used internally for goog.string.escapeChar.
        796 * @private
        797 * @type {Object}
        798 */
        799goog.string.jsEscapeCache_ = {
        800 '\'': '\\\''
        801};
        802
        803
        804/**
        805 * Encloses a string in double quotes and escapes characters so that the
        806 * string is a valid JS string.
        807 * @param {string} s The string to quote.
        808 * @return {string} A copy of {@code s} surrounded by double quotes.
        809 */
        810goog.string.quote = function(s) {
        811 s = String(s);
        812 if (s.quote) {
        813 return s.quote();
        814 } else {
        815 var sb = ['"'];
        816 for (var i = 0; i < s.length; i++) {
        817 var ch = s.charAt(i);
        818 var cc = ch.charCodeAt(0);
        819 sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
        820 ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
        821 }
        822 sb.push('"');
        823 return sb.join('');
        824 }
        825};
        826
        827
        828/**
        829 * Takes a string and returns the escaped string for that character.
        830 * @param {string} str The string to escape.
        831 * @return {string} An escaped string representing {@code str}.
        832 */
        833goog.string.escapeString = function(str) {
        834 var sb = [];
        835 for (var i = 0; i < str.length; i++) {
        836 sb[i] = goog.string.escapeChar(str.charAt(i));
        837 }
        838 return sb.join('');
        839};
        840
        841
        842/**
        843 * Takes a character and returns the escaped string for that character. For
        844 * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
        845 * @param {string} c The character to escape.
        846 * @return {string} An escaped string representing {@code c}.
        847 */
        848goog.string.escapeChar = function(c) {
        849 if (c in goog.string.jsEscapeCache_) {
        850 return goog.string.jsEscapeCache_[c];
        851 }
        852
        853 if (c in goog.string.specialEscapeChars_) {
        854 return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
        855 }
        856
        857 var rv = c;
        858 var cc = c.charCodeAt(0);
        859 if (cc > 31 && cc < 127) {
        860 rv = c;
        861 } else {
        862 // tab is 9 but handled above
        863 if (cc < 256) {
        864 rv = '\\x';
        865 if (cc < 16 || cc > 256) {
        866 rv += '0';
        867 }
        868 } else {
        869 rv = '\\u';
        870 if (cc < 4096) { // \u1000
        871 rv += '0';
        872 }
        873 }
        874 rv += cc.toString(16).toUpperCase();
        875 }
        876
        877 return goog.string.jsEscapeCache_[c] = rv;
        878};
        879
        880
        881/**
        882 * Takes a string and creates a map (Object) in which the keys are the
        883 * characters in the string. The value for the key is set to true. You can
        884 * then use goog.object.map or goog.array.map to change the values.
        885 * @param {string} s The string to build the map from.
        886 * @return {Object} The map of characters used.
        887 */
        888// TODO(arv): It seems like we should have a generic goog.array.toMap. But do
        889// we want a dependency on goog.array in goog.string?
        890goog.string.toMap = function(s) {
        891 var rv = {};
        892 for (var i = 0; i < s.length; i++) {
        893 rv[s.charAt(i)] = true;
        894 }
        895 return rv;
        896};
        897
        898
        899/**
        900 * Checks whether a string contains a given substring.
        901 * @param {string} s The string to test.
        902 * @param {string} ss The substring to test for.
        903 * @return {boolean} True if {@code s} contains {@code ss}.
        904 */
        905goog.string.contains = function(s, ss) {
        906 return s.indexOf(ss) != -1;
        907};
        908
        909
        910/**
        911 * Returns the non-overlapping occurrences of ss in s.
        912 * If either s or ss evalutes to false, then returns zero.
        913 * @param {string} s The string to look in.
        914 * @param {string} ss The string to look for.
        915 * @return {number} Number of occurrences of ss in s.
        916 */
        917goog.string.countOf = function(s, ss) {
        918 return s && ss ? s.split(ss).length - 1 : 0;
        919};
        920
        921
        922/**
        923 * Removes a substring of a specified length at a specific
        924 * index in a string.
        925 * @param {string} s The base string from which to remove.
        926 * @param {number} index The index at which to remove the substring.
        927 * @param {number} stringLength The length of the substring to remove.
        928 * @return {string} A copy of {@code s} with the substring removed or the full
        929 * string if nothing is removed or the input is invalid.
        930 */
        931goog.string.removeAt = function(s, index, stringLength) {
        932 var resultStr = s;
        933 // If the index is greater or equal to 0 then remove substring
        934 if (index >= 0 && index < s.length && stringLength > 0) {
        935 resultStr = s.substr(0, index) +
        936 s.substr(index + stringLength, s.length - index - stringLength);
        937 }
        938 return resultStr;
        939};
        940
        941
        942/**
        943 * Removes the first occurrence of a substring from a string.
        944 * @param {string} s The base string from which to remove.
        945 * @param {string} ss The string to remove.
        946 * @return {string} A copy of {@code s} with {@code ss} removed or the full
        947 * string if nothing is removed.
        948 */
        949goog.string.remove = function(s, ss) {
        950 var re = new RegExp(goog.string.regExpEscape(ss), '');
        951 return s.replace(re, '');
        952};
        953
        954
        955/**
        956 * Removes all occurrences of a substring from a string.
        957 * @param {string} s The base string from which to remove.
        958 * @param {string} ss The string to remove.
        959 * @return {string} A copy of {@code s} with {@code ss} removed or the full
        960 * string if nothing is removed.
        961 */
        962goog.string.removeAll = function(s, ss) {
        963 var re = new RegExp(goog.string.regExpEscape(ss), 'g');
        964 return s.replace(re, '');
        965};
        966
        967
        968/**
        969 * Escapes characters in the string that are not safe to use in a RegExp.
        970 * @param {*} s The string to escape. If not a string, it will be casted
        971 * to one.
        972 * @return {string} A RegExp safe, escaped copy of {@code s}.
        973 */
        974goog.string.regExpEscape = function(s) {
        975 return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
        976 replace(/\x08/g, '\\x08');
        977};
        978
        979
        980/**
        981 * Repeats a string n times.
        982 * @param {string} string The string to repeat.
        983 * @param {number} length The number of times to repeat.
        984 * @return {string} A string containing {@code length} repetitions of
        985 * {@code string}.
        986 */
        987goog.string.repeat = function(string, length) {
        988 return new Array(length + 1).join(string);
        989};
        990
        991
        992/**
        993 * Pads number to given length and optionally rounds it to a given precision.
        994 * For example:
        995 * <pre>padNumber(1.25, 2, 3) -> '01.250'
        996 * padNumber(1.25, 2) -> '01.25'
        997 * padNumber(1.25, 2, 1) -> '01.3'
        998 * padNumber(1.25, 0) -> '1.25'</pre>
        999 *
        1000 * @param {number} num The number to pad.
        1001 * @param {number} length The desired length.
        1002 * @param {number=} opt_precision The desired precision.
        1003 * @return {string} {@code num} as a string with the given options.
        1004 */
        1005goog.string.padNumber = function(num, length, opt_precision) {
        1006 var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
        1007 var index = s.indexOf('.');
        1008 if (index == -1) {
        1009 index = s.length;
        1010 }
        1011 return goog.string.repeat('0', Math.max(0, length - index)) + s;
        1012};
        1013
        1014
        1015/**
        1016 * Returns a string representation of the given object, with
        1017 * null and undefined being returned as the empty string.
        1018 *
        1019 * @param {*} obj The object to convert.
        1020 * @return {string} A string representation of the {@code obj}.
        1021 */
        1022goog.string.makeSafe = function(obj) {
        1023 return obj == null ? '' : String(obj);
        1024};
        1025
        1026
        1027/**
        1028 * Concatenates string expressions. This is useful
        1029 * since some browsers are very inefficient when it comes to using plus to
        1030 * concat strings. Be careful when using null and undefined here since
        1031 * these will not be included in the result. If you need to represent these
        1032 * be sure to cast the argument to a String first.
        1033 * For example:
        1034 * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
        1035 * buildString(null, undefined) -> ''
        1036 * </pre>
        1037 * @param {...*} var_args A list of strings to concatenate. If not a string,
        1038 * it will be casted to one.
        1039 * @return {string} The concatenation of {@code var_args}.
        1040 */
        1041goog.string.buildString = function(var_args) {
        1042 return Array.prototype.join.call(arguments, '');
        1043};
        1044
        1045
        1046/**
        1047 * Returns a string with at least 64-bits of randomness.
        1048 *
        1049 * Doesn't trust Javascript's random function entirely. Uses a combination of
        1050 * random and current timestamp, and then encodes the string in base-36 to
        1051 * make it shorter.
        1052 *
        1053 * @return {string} A random string, e.g. sn1s7vb4gcic.
        1054 */
        1055goog.string.getRandomString = function() {
        1056 var x = 2147483648;
        1057 return Math.floor(Math.random() * x).toString(36) +
        1058 Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
        1059};
        1060
        1061
        1062/**
        1063 * Compares two version numbers.
        1064 *
        1065 * @param {string|number} version1 Version of first item.
        1066 * @param {string|number} version2 Version of second item.
        1067 *
        1068 * @return {number} 1 if {@code version1} is higher.
        1069 * 0 if arguments are equal.
        1070 * -1 if {@code version2} is higher.
        1071 */
        1072goog.string.compareVersions = function(version1, version2) {
        1073 var order = 0;
        1074 // Trim leading and trailing whitespace and split the versions into
        1075 // subversions.
        1076 var v1Subs = goog.string.trim(String(version1)).split('.');
        1077 var v2Subs = goog.string.trim(String(version2)).split('.');
        1078 var subCount = Math.max(v1Subs.length, v2Subs.length);
        1079
        1080 // Iterate over the subversions, as long as they appear to be equivalent.
        1081 for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
        1082 var v1Sub = v1Subs[subIdx] || '';
        1083 var v2Sub = v2Subs[subIdx] || '';
        1084
        1085 // Split the subversions into pairs of numbers and qualifiers (like 'b').
        1086 // Two different RegExp objects are needed because they are both using
        1087 // the 'g' flag.
        1088 var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
        1089 var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
        1090 do {
        1091 var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
        1092 var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
        1093 // Break if there are no more matches.
        1094 if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
        1095 break;
        1096 }
        1097
        1098 // Parse the numeric part of the subversion. A missing number is
        1099 // equivalent to 0.
        1100 var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
        1101 var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
        1102
        1103 // Compare the subversion components. The number has the highest
        1104 // precedence. Next, if the numbers are equal, a subversion without any
        1105 // qualifier is always higher than a subversion with any qualifier. Next,
        1106 // the qualifiers are compared as strings.
        1107 order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
        1108 goog.string.compareElements_(v1Comp[2].length == 0,
        1109 v2Comp[2].length == 0) ||
        1110 goog.string.compareElements_(v1Comp[2], v2Comp[2]);
        1111 // Stop as soon as an inequality is discovered.
        1112 } while (order == 0);
        1113 }
        1114
        1115 return order;
        1116};
        1117
        1118
        1119/**
        1120 * Compares elements of a version number.
        1121 *
        1122 * @param {string|number|boolean} left An element from a version number.
        1123 * @param {string|number|boolean} right An element from a version number.
        1124 *
        1125 * @return {number} 1 if {@code left} is higher.
        1126 * 0 if arguments are equal.
        1127 * -1 if {@code right} is higher.
        1128 * @private
        1129 */
        1130goog.string.compareElements_ = function(left, right) {
        1131 if (left < right) {
        1132 return -1;
        1133 } else if (left > right) {
        1134 return 1;
        1135 }
        1136 return 0;
        1137};
        1138
        1139
        1140/**
        1141 * Maximum value of #goog.string.hashCode, exclusive. 2^32.
        1142 * @type {number}
        1143 * @private
        1144 */
        1145goog.string.HASHCODE_MAX_ = 0x100000000;
        1146
        1147
        1148/**
        1149 * String hash function similar to java.lang.String.hashCode().
        1150 * The hash code for a string is computed as
        1151 * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
        1152 * where s[i] is the ith character of the string and n is the length of
        1153 * the string. We mod the result to make it between 0 (inclusive) and 2^32
        1154 * (exclusive).
        1155 * @param {string} str A string.
        1156 * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
        1157 * (exclusive). The empty string returns 0.
        1158 */
        1159goog.string.hashCode = function(str) {
        1160 var result = 0;
        1161 for (var i = 0; i < str.length; ++i) {
        1162 result = 31 * result + str.charCodeAt(i);
        1163 // Normalize to 4 byte range, 0 ... 2^32.
        1164 result %= goog.string.HASHCODE_MAX_;
        1165 }
        1166 return result;
        1167};
        1168
        1169
        1170/**
        1171 * The most recent unique ID. |0 is equivalent to Math.floor in this case.
        1172 * @type {number}
        1173 * @private
        1174 */
        1175goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
        1176
        1177
        1178/**
        1179 * Generates and returns a string which is unique in the current document.
        1180 * This is useful, for example, to create unique IDs for DOM elements.
        1181 * @return {string} A unique id.
        1182 */
        1183goog.string.createUniqueString = function() {
        1184 return 'goog_' + goog.string.uniqueStringCounter_++;
        1185};
        1186
        1187
        1188/**
        1189 * Converts the supplied string to a number, which may be Ininity or NaN.
        1190 * This function strips whitespace: (toNumber(' 123') === 123)
        1191 * This function accepts scientific notation: (toNumber('1e1') === 10)
        1192 *
        1193 * This is better than Javascript's built-in conversions because, sadly:
        1194 * (Number(' ') === 0) and (parseFloat('123a') === 123)
        1195 *
        1196 * @param {string} str The string to convert.
        1197 * @return {number} The number the supplied string represents, or NaN.
        1198 */
        1199goog.string.toNumber = function(str) {
        1200 var num = Number(str);
        1201 if (num == 0 && goog.string.isEmpty(str)) {
        1202 return NaN;
        1203 }
        1204 return num;
        1205};
        1206
        1207
        1208/**
        1209 * Returns whether the given string is lower camel case (e.g. "isFooBar").
        1210 *
        1211 * Note that this assumes the string is entirely letters.
        1212 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
        1213 *
        1214 * @param {string} str String to test.
        1215 * @return {boolean} Whether the string is lower camel case.
        1216 */
        1217goog.string.isLowerCamelCase = function(str) {
        1218 return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
        1219};
        1220
        1221
        1222/**
        1223 * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
        1224 *
        1225 * Note that this assumes the string is entirely letters.
        1226 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
        1227 *
        1228 * @param {string} str String to test.
        1229 * @return {boolean} Whether the string is upper camel case.
        1230 */
        1231goog.string.isUpperCamelCase = function(str) {
        1232 return /^([A-Z][a-z]*)+$/.test(str);
        1233};
        1234
        1235
        1236/**
        1237 * Converts a string from selector-case to camelCase (e.g. from
        1238 * "multi-part-string" to "multiPartString"), useful for converting
        1239 * CSS selectors and HTML dataset keys to their equivalent JS properties.
        1240 * @param {string} str The string in selector-case form.
        1241 * @return {string} The string in camelCase form.
        1242 */
        1243goog.string.toCamelCase = function(str) {
        1244 return String(str).replace(/\-([a-z])/g, function(all, match) {
        1245 return match.toUpperCase();
        1246 });
        1247};
        1248
        1249
        1250/**
        1251 * Converts a string from camelCase to selector-case (e.g. from
        1252 * "multiPartString" to "multi-part-string"), useful for converting JS
        1253 * style and dataset properties to equivalent CSS selectors and HTML keys.
        1254 * @param {string} str The string in camelCase form.
        1255 * @return {string} The string in selector-case form.
        1256 */
        1257goog.string.toSelectorCase = function(str) {
        1258 return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
        1259};
        1260
        1261
        1262/**
        1263 * Converts a string into TitleCase. First character of the string is always
        1264 * capitalized in addition to the first letter of every subsequent word.
        1265 * Words are delimited by one or more whitespaces by default. Custom delimiters
        1266 * can optionally be specified to replace the default, which doesn't preserve
        1267 * whitespace delimiters and instead must be explicitly included if needed.
        1268 *
        1269 * Default delimiter => " ":
        1270 * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree'
        1271 * goog.string.toTitleCase('one two three') => 'One Two Three'
        1272 * goog.string.toTitleCase(' one two ') => ' One Two '
        1273 * goog.string.toTitleCase('one_two_three') => 'One_two_three'
        1274 * goog.string.toTitleCase('one-two-three') => 'One-two-three'
        1275 *
        1276 * Custom delimiter => "_-.":
        1277 * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree'
        1278 * goog.string.toTitleCase('one two three', '_-.') => 'One two three'
        1279 * goog.string.toTitleCase(' one two ', '_-.') => ' one two '
        1280 * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three'
        1281 * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three'
        1282 * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
        1283 * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three'
        1284 * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'
        1285 *
        1286 * @param {string} str String value in camelCase form.
        1287 * @param {string=} opt_delimiters Custom delimiter character set used to
        1288 * distinguish words in the string value. Each character represents a
        1289 * single delimiter. When provided, default whitespace delimiter is
        1290 * overridden and must be explicitly included if needed.
        1291 * @return {string} String value in TitleCase form.
        1292 */
        1293goog.string.toTitleCase = function(str, opt_delimiters) {
        1294 var delimiters = goog.isString(opt_delimiters) ?
        1295 goog.string.regExpEscape(opt_delimiters) : '\\s';
        1296
        1297 // For IE8, we need to prevent using an empty character set. Otherwise,
        1298 // incorrect matching will occur.
        1299 delimiters = delimiters ? '|[' + delimiters + ']+' : '';
        1300
        1301 var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
        1302 return str.replace(regexp, function(all, p1, p2) {
        1303 return p1 + p2.toUpperCase();
        1304 });
        1305};
        1306
        1307
        1308/**
        1309 * Parse a string in decimal or hexidecimal ('0xFFFF') form.
        1310 *
        1311 * To parse a particular radix, please use parseInt(string, radix) directly. See
        1312 * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
        1313 *
        1314 * This is a wrapper for the built-in parseInt function that will only parse
        1315 * numbers as base 10 or base 16. Some JS implementations assume strings
        1316 * starting with "0" are intended to be octal. ES3 allowed but discouraged
        1317 * this behavior. ES5 forbids it. This function emulates the ES5 behavior.
        1318 *
        1319 * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
        1320 *
        1321 * @param {string|number|null|undefined} value The value to be parsed.
        1322 * @return {number} The number, parsed. If the string failed to parse, this
        1323 * will be NaN.
        1324 */
        1325goog.string.parseInt = function(value) {
        1326 // Force finite numbers to strings.
        1327 if (isFinite(value)) {
        1328 value = String(value);
        1329 }
        1330
        1331 if (goog.isString(value)) {
        1332 // If the string starts with '0x' or '-0x', parse as hex.
        1333 return /^\s*-?0x/i.test(value) ?
        1334 parseInt(value, 16) : parseInt(value, 10);
        1335 }
        1336
        1337 return NaN;
        1338};
        1339
        1340
        1341/**
        1342 * Splits a string on a separator a limited number of times.
        1343 *
        1344 * This implementation is more similar to Python or Java, where the limit
        1345 * parameter specifies the maximum number of splits rather than truncating
        1346 * the number of results.
        1347 *
        1348 * See http://docs.python.org/2/library/stdtypes.html#str.split
        1349 * See JavaDoc: http://goo.gl/F2AsY
        1350 * See Mozilla reference: http://goo.gl/dZdZs
        1351 *
        1352 * @param {string} str String to split.
        1353 * @param {string} separator The separator.
        1354 * @param {number} limit The limit to the number of splits. The resulting array
        1355 * will have a maximum length of limit+1. Negative numbers are the same
        1356 * as zero.
        1357 * @return {!Array.<string>} The string, split.
        1358 */
        1359
        1360goog.string.splitLimit = function(str, separator, limit) {
        1361 var parts = str.split(separator);
        1362 var returnVal = [];
        1363
        1364 // Only continue doing this while we haven't hit the limit and we have
        1365 // parts left.
        1366 while (limit > 0 && parts.length) {
        1367 returnVal.push(parts.shift());
        1368 limit--;
        1369 }
        1370
        1371 // If there are remaining parts, append them to the end.
        1372 if (parts.length) {
        1373 returnVal.push(parts.join(separator));
        1374 }
        1375
        1376 return returnVal;
        1377};
        1378
        \ No newline at end of file +string.js

        lib/goog/string/string.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for string manipulation.
        17 * @author arv@google.com (Erik Arvidsson)
        18 */
        19
        20
        21/**
        22 * Namespace for string utilities
        23 */
        24goog.provide('goog.string');
        25goog.provide('goog.string.Unicode');
        26
        27
        28/**
        29 * @define {boolean} Enables HTML escaping of lowercase letter "e" which helps
        30 * with detection of double-escaping as this letter is frequently used.
        31 */
        32goog.define('goog.string.DETECT_DOUBLE_ESCAPING', false);
        33
        34
        35/**
        36 * @define {boolean} Whether to force non-dom html unescaping.
        37 */
        38goog.define('goog.string.FORCE_NON_DOM_HTML_UNESCAPING', false);
        39
        40
        41/**
        42 * Common Unicode string characters.
        43 * @enum {string}
        44 */
        45goog.string.Unicode = {
        46 NBSP: '\xa0'
        47};
        48
        49
        50/**
        51 * Fast prefix-checker.
        52 * @param {string} str The string to check.
        53 * @param {string} prefix A string to look for at the start of {@code str}.
        54 * @return {boolean} True if {@code str} begins with {@code prefix}.
        55 */
        56goog.string.startsWith = function(str, prefix) {
        57 return str.lastIndexOf(prefix, 0) == 0;
        58};
        59
        60
        61/**
        62 * Fast suffix-checker.
        63 * @param {string} str The string to check.
        64 * @param {string} suffix A string to look for at the end of {@code str}.
        65 * @return {boolean} True if {@code str} ends with {@code suffix}.
        66 */
        67goog.string.endsWith = function(str, suffix) {
        68 var l = str.length - suffix.length;
        69 return l >= 0 && str.indexOf(suffix, l) == l;
        70};
        71
        72
        73/**
        74 * Case-insensitive prefix-checker.
        75 * @param {string} str The string to check.
        76 * @param {string} prefix A string to look for at the end of {@code str}.
        77 * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring
        78 * case).
        79 */
        80goog.string.caseInsensitiveStartsWith = function(str, prefix) {
        81 return goog.string.caseInsensitiveCompare(
        82 prefix, str.substr(0, prefix.length)) == 0;
        83};
        84
        85
        86/**
        87 * Case-insensitive suffix-checker.
        88 * @param {string} str The string to check.
        89 * @param {string} suffix A string to look for at the end of {@code str}.
        90 * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring
        91 * case).
        92 */
        93goog.string.caseInsensitiveEndsWith = function(str, suffix) {
        94 return goog.string.caseInsensitiveCompare(
        95 suffix, str.substr(str.length - suffix.length, suffix.length)) == 0;
        96};
        97
        98
        99/**
        100 * Case-insensitive equality checker.
        101 * @param {string} str1 First string to check.
        102 * @param {string} str2 Second string to check.
        103 * @return {boolean} True if {@code str1} and {@code str2} are the same string,
        104 * ignoring case.
        105 */
        106goog.string.caseInsensitiveEquals = function(str1, str2) {
        107 return str1.toLowerCase() == str2.toLowerCase();
        108};
        109
        110
        111/**
        112 * Does simple python-style string substitution.
        113 * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog".
        114 * @param {string} str The string containing the pattern.
        115 * @param {...*} var_args The items to substitute into the pattern.
        116 * @return {string} A copy of {@code str} in which each occurrence of
        117 * {@code %s} has been replaced an argument from {@code var_args}.
        118 */
        119goog.string.subs = function(str, var_args) {
        120 var splitParts = str.split('%s');
        121 var returnString = '';
        122
        123 var subsArguments = Array.prototype.slice.call(arguments, 1);
        124 while (subsArguments.length &&
        125 // Replace up to the last split part. We are inserting in the
        126 // positions between split parts.
        127 splitParts.length > 1) {
        128 returnString += splitParts.shift() + subsArguments.shift();
        129 }
        130
        131 return returnString + splitParts.join('%s'); // Join unused '%s'
        132};
        133
        134
        135/**
        136 * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines
        137 * and tabs) to a single space, and strips leading and trailing whitespace.
        138 * @param {string} str Input string.
        139 * @return {string} A copy of {@code str} with collapsed whitespace.
        140 */
        141goog.string.collapseWhitespace = function(str) {
        142 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        143 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        144 // include it in the regexp to enforce consistent cross-browser behavior.
        145 return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, '');
        146};
        147
        148
        149/**
        150 * Checks if a string is empty or contains only whitespaces.
        151 * @param {string} str The string to check.
        152 * @return {boolean} Whether {@code str} is empty or whitespace only.
        153 */
        154goog.string.isEmptyOrWhitespace = function(str) {
        155 // testing length == 0 first is actually slower in all browsers (about the
        156 // same in Opera).
        157 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        158 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        159 // include it in the regexp to enforce consistent cross-browser behavior.
        160 return /^[\s\xa0]*$/.test(str);
        161};
        162
        163
        164/**
        165 * Checks if a string is empty.
        166 * @param {string} str The string to check.
        167 * @return {boolean} Whether {@code str} is empty.
        168 */
        169goog.string.isEmptyString = function(str) {
        170 return str.length == 0;
        171};
        172
        173
        174/**
        175 * Checks if a string is empty or contains only whitespaces.
        176 *
        177 * TODO(user): Deprecate this when clients have been switched over to
        178 * goog.string.isEmptyOrWhitespace.
        179 *
        180 * @param {string} str The string to check.
        181 * @return {boolean} Whether {@code str} is empty or whitespace only.
        182 */
        183goog.string.isEmpty = goog.string.isEmptyOrWhitespace;
        184
        185
        186/**
        187 * Checks if a string is null, undefined, empty or contains only whitespaces.
        188 * @param {*} str The string to check.
        189 * @return {boolean} Whether {@code str} is null, undefined, empty, or
        190 * whitespace only.
        191 * @deprecated Use goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str))
        192 * instead.
        193 */
        194goog.string.isEmptyOrWhitespaceSafe = function(str) {
        195 return goog.string.isEmptyOrWhitespace(goog.string.makeSafe(str));
        196};
        197
        198
        199/**
        200 * Checks if a string is null, undefined, empty or contains only whitespaces.
        201 *
        202 * TODO(user): Deprecate this when clients have been switched over to
        203 * goog.string.isEmptyOrWhitespaceSafe.
        204 *
        205 * @param {*} str The string to check.
        206 * @return {boolean} Whether {@code str} is null, undefined, empty, or
        207 * whitespace only.
        208 */
        209goog.string.isEmptySafe = goog.string.isEmptyOrWhitespaceSafe;
        210
        211
        212/**
        213 * Checks if a string is all breaking whitespace.
        214 * @param {string} str The string to check.
        215 * @return {boolean} Whether the string is all breaking whitespace.
        216 */
        217goog.string.isBreakingWhitespace = function(str) {
        218 return !/[^\t\n\r ]/.test(str);
        219};
        220
        221
        222/**
        223 * Checks if a string contains all letters.
        224 * @param {string} str string to check.
        225 * @return {boolean} True if {@code str} consists entirely of letters.
        226 */
        227goog.string.isAlpha = function(str) {
        228 return !/[^a-zA-Z]/.test(str);
        229};
        230
        231
        232/**
        233 * Checks if a string contains only numbers.
        234 * @param {*} str string to check. If not a string, it will be
        235 * casted to one.
        236 * @return {boolean} True if {@code str} is numeric.
        237 */
        238goog.string.isNumeric = function(str) {
        239 return !/[^0-9]/.test(str);
        240};
        241
        242
        243/**
        244 * Checks if a string contains only numbers or letters.
        245 * @param {string} str string to check.
        246 * @return {boolean} True if {@code str} is alphanumeric.
        247 */
        248goog.string.isAlphaNumeric = function(str) {
        249 return !/[^a-zA-Z0-9]/.test(str);
        250};
        251
        252
        253/**
        254 * Checks if a character is a space character.
        255 * @param {string} ch Character to check.
        256 * @return {boolean} True if {@code ch} is a space.
        257 */
        258goog.string.isSpace = function(ch) {
        259 return ch == ' ';
        260};
        261
        262
        263/**
        264 * Checks if a character is a valid unicode character.
        265 * @param {string} ch Character to check.
        266 * @return {boolean} True if {@code ch} is a valid unicode character.
        267 */
        268goog.string.isUnicodeChar = function(ch) {
        269 return ch.length == 1 && ch >= ' ' && ch <= '~' ||
        270 ch >= '\u0080' && ch <= '\uFFFD';
        271};
        272
        273
        274/**
        275 * Takes a string and replaces newlines with a space. Multiple lines are
        276 * replaced with a single space.
        277 * @param {string} str The string from which to strip newlines.
        278 * @return {string} A copy of {@code str} stripped of newlines.
        279 */
        280goog.string.stripNewlines = function(str) {
        281 return str.replace(/(\r\n|\r|\n)+/g, ' ');
        282};
        283
        284
        285/**
        286 * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n.
        287 * @param {string} str The string to in which to canonicalize newlines.
        288 * @return {string} {@code str} A copy of {@code} with canonicalized newlines.
        289 */
        290goog.string.canonicalizeNewlines = function(str) {
        291 return str.replace(/(\r\n|\r|\n)/g, '\n');
        292};
        293
        294
        295/**
        296 * Normalizes whitespace in a string, replacing all whitespace chars with
        297 * a space.
        298 * @param {string} str The string in which to normalize whitespace.
        299 * @return {string} A copy of {@code str} with all whitespace normalized.
        300 */
        301goog.string.normalizeWhitespace = function(str) {
        302 return str.replace(/\xa0|\s/g, ' ');
        303};
        304
        305
        306/**
        307 * Normalizes spaces in a string, replacing all consecutive spaces and tabs
        308 * with a single space. Replaces non-breaking space with a space.
        309 * @param {string} str The string in which to normalize spaces.
        310 * @return {string} A copy of {@code str} with all consecutive spaces and tabs
        311 * replaced with a single space.
        312 */
        313goog.string.normalizeSpaces = function(str) {
        314 return str.replace(/\xa0|[ \t]+/g, ' ');
        315};
        316
        317
        318/**
        319 * Removes the breaking spaces from the left and right of the string and
        320 * collapses the sequences of breaking spaces in the middle into single spaces.
        321 * The original and the result strings render the same way in HTML.
        322 * @param {string} str A string in which to collapse spaces.
        323 * @return {string} Copy of the string with normalized breaking spaces.
        324 */
        325goog.string.collapseBreakingSpaces = function(str) {
        326 return str.replace(/[\t\r\n ]+/g, ' ').replace(
        327 /^[\t\r\n ]+|[\t\r\n ]+$/g, '');
        328};
        329
        330
        331/**
        332 * Trims white spaces to the left and right of a string.
        333 * @param {string} str The string to trim.
        334 * @return {string} A trimmed copy of {@code str}.
        335 */
        336goog.string.trim = (goog.TRUSTED_SITE && String.prototype.trim) ?
        337 function(str) {
        338 return str.trim();
        339 } :
        340 function(str) {
        341 // Since IE doesn't include non-breaking-space (0xa0) in their \s
        342 // character class (as required by section 7.2 of the ECMAScript spec),
        343 // we explicitly include it in the regexp to enforce consistent
        344 // cross-browser behavior.
        345 return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
        346 };
        347
        348
        349/**
        350 * Trims whitespaces at the left end of a string.
        351 * @param {string} str The string to left trim.
        352 * @return {string} A trimmed copy of {@code str}.
        353 */
        354goog.string.trimLeft = function(str) {
        355 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        356 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        357 // include it in the regexp to enforce consistent cross-browser behavior.
        358 return str.replace(/^[\s\xa0]+/, '');
        359};
        360
        361
        362/**
        363 * Trims whitespaces at the right end of a string.
        364 * @param {string} str The string to right trim.
        365 * @return {string} A trimmed copy of {@code str}.
        366 */
        367goog.string.trimRight = function(str) {
        368 // Since IE doesn't include non-breaking-space (0xa0) in their \s character
        369 // class (as required by section 7.2 of the ECMAScript spec), we explicitly
        370 // include it in the regexp to enforce consistent cross-browser behavior.
        371 return str.replace(/[\s\xa0]+$/, '');
        372};
        373
        374
        375/**
        376 * A string comparator that ignores case.
        377 * -1 = str1 less than str2
        378 * 0 = str1 equals str2
        379 * 1 = str1 greater than str2
        380 *
        381 * @param {string} str1 The string to compare.
        382 * @param {string} str2 The string to compare {@code str1} to.
        383 * @return {number} The comparator result, as described above.
        384 */
        385goog.string.caseInsensitiveCompare = function(str1, str2) {
        386 var test1 = String(str1).toLowerCase();
        387 var test2 = String(str2).toLowerCase();
        388
        389 if (test1 < test2) {
        390 return -1;
        391 } else if (test1 == test2) {
        392 return 0;
        393 } else {
        394 return 1;
        395 }
        396};
        397
        398
        399/**
        400 * Regular expression used for splitting a string into substrings of fractional
        401 * numbers, integers, and non-numeric characters.
        402 * @type {RegExp}
        403 * @private
        404 */
        405goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g;
        406
        407
        408/**
        409 * String comparison function that handles numbers in a way humans might expect.
        410 * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The
        411 * comparison is mostly case-insensitive, though strings that are identical
        412 * except for case are sorted with the upper-case strings before lower-case.
        413 *
        414 * This comparison function is significantly slower (about 500x) than either
        415 * the default or the case-insensitive compare. It should not be used in
        416 * time-critical code, but should be fast enough to sort several hundred short
        417 * strings (like filenames) with a reasonable delay.
        418 *
        419 * @param {string} str1 The string to compare in a numerically sensitive way.
        420 * @param {string} str2 The string to compare {@code str1} to.
        421 * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than
        422 * 0 if str1 > str2.
        423 */
        424goog.string.numerateCompare = function(str1, str2) {
        425 if (str1 == str2) {
        426 return 0;
        427 }
        428 if (!str1) {
        429 return -1;
        430 }
        431 if (!str2) {
        432 return 1;
        433 }
        434
        435 // Using match to split the entire string ahead of time turns out to be faster
        436 // for most inputs than using RegExp.exec or iterating over each character.
        437 var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_);
        438 var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_);
        439
        440 var count = Math.min(tokens1.length, tokens2.length);
        441
        442 for (var i = 0; i < count; i++) {
        443 var a = tokens1[i];
        444 var b = tokens2[i];
        445
        446 // Compare pairs of tokens, returning if one token sorts before the other.
        447 if (a != b) {
        448
        449 // Only if both tokens are integers is a special comparison required.
        450 // Decimal numbers are sorted as strings (e.g., '.09' < '.1').
        451 var num1 = parseInt(a, 10);
        452 if (!isNaN(num1)) {
        453 var num2 = parseInt(b, 10);
        454 if (!isNaN(num2) && num1 - num2) {
        455 return num1 - num2;
        456 }
        457 }
        458 return a < b ? -1 : 1;
        459 }
        460 }
        461
        462 // If one string is a substring of the other, the shorter string sorts first.
        463 if (tokens1.length != tokens2.length) {
        464 return tokens1.length - tokens2.length;
        465 }
        466
        467 // The two strings must be equivalent except for case (perfect equality is
        468 // tested at the head of the function.) Revert to default ASCII-betical string
        469 // comparison to stablize the sort.
        470 return str1 < str2 ? -1 : 1;
        471};
        472
        473
        474/**
        475 * URL-encodes a string
        476 * @param {*} str The string to url-encode.
        477 * @return {string} An encoded copy of {@code str} that is safe for urls.
        478 * Note that '#', ':', and other characters used to delimit portions
        479 * of URLs *will* be encoded.
        480 */
        481goog.string.urlEncode = function(str) {
        482 return encodeURIComponent(String(str));
        483};
        484
        485
        486/**
        487 * URL-decodes the string. We need to specially handle '+'s because
        488 * the javascript library doesn't convert them to spaces.
        489 * @param {string} str The string to url decode.
        490 * @return {string} The decoded {@code str}.
        491 */
        492goog.string.urlDecode = function(str) {
        493 return decodeURIComponent(str.replace(/\+/g, ' '));
        494};
        495
        496
        497/**
        498 * Converts \n to <br>s or <br />s.
        499 * @param {string} str The string in which to convert newlines.
        500 * @param {boolean=} opt_xml Whether to use XML compatible tags.
        501 * @return {string} A copy of {@code str} with converted newlines.
        502 */
        503goog.string.newLineToBr = function(str, opt_xml) {
        504 return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>');
        505};
        506
        507
        508/**
        509 * Escapes double quote '"' and single quote '\'' characters in addition to
        510 * '&', '<', and '>' so that a string can be included in an HTML tag attribute
        511 * value within double or single quotes.
        512 *
        513 * It should be noted that > doesn't need to be escaped for the HTML or XML to
        514 * be valid, but it has been decided to escape it for consistency with other
        515 * implementations.
        516 *
        517 * With goog.string.DETECT_DOUBLE_ESCAPING, this function escapes also the
        518 * lowercase letter "e".
        519 *
        520 * NOTE(user):
        521 * HtmlEscape is often called during the generation of large blocks of HTML.
        522 * Using statics for the regular expressions and strings is an optimization
        523 * that can more than half the amount of time IE spends in this function for
        524 * large apps, since strings and regexes both contribute to GC allocations.
        525 *
        526 * Testing for the presence of a character before escaping increases the number
        527 * of function calls, but actually provides a speed increase for the average
        528 * case -- since the average case often doesn't require the escaping of all 4
        529 * characters and indexOf() is much cheaper than replace().
        530 * The worst case does suffer slightly from the additional calls, therefore the
        531 * opt_isLikelyToContainHtmlChars option has been included for situations
        532 * where all 4 HTML entities are very likely to be present and need escaping.
        533 *
        534 * Some benchmarks (times tended to fluctuate +-0.05ms):
        535 * FireFox IE6
        536 * (no chars / average (mix of cases) / all 4 chars)
        537 * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80
        538 * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84
        539 * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85
        540 *
        541 * An additional advantage of checking if replace actually needs to be called
        542 * is a reduction in the number of object allocations, so as the size of the
        543 * application grows the difference between the various methods would increase.
        544 *
        545 * @param {string} str string to be escaped.
        546 * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see
        547 * if the character needs replacing - use this option if you expect each of
        548 * the characters to appear often. Leave false if you expect few html
        549 * characters to occur in your strings, such as if you are escaping HTML.
        550 * @return {string} An escaped copy of {@code str}.
        551 */
        552goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) {
        553
        554 if (opt_isLikelyToContainHtmlChars) {
        555 str = str.replace(goog.string.AMP_RE_, '&amp;')
        556 .replace(goog.string.LT_RE_, '&lt;')
        557 .replace(goog.string.GT_RE_, '&gt;')
        558 .replace(goog.string.QUOT_RE_, '&quot;')
        559 .replace(goog.string.SINGLE_QUOTE_RE_, '&#39;')
        560 .replace(goog.string.NULL_RE_, '&#0;');
        561 if (goog.string.DETECT_DOUBLE_ESCAPING) {
        562 str = str.replace(goog.string.E_RE_, '&#101;');
        563 }
        564 return str;
        565
        566 } else {
        567 // quick test helps in the case when there are no chars to replace, in
        568 // worst case this makes barely a difference to the time taken
        569 if (!goog.string.ALL_RE_.test(str)) return str;
        570
        571 // str.indexOf is faster than regex.test in this case
        572 if (str.indexOf('&') != -1) {
        573 str = str.replace(goog.string.AMP_RE_, '&amp;');
        574 }
        575 if (str.indexOf('<') != -1) {
        576 str = str.replace(goog.string.LT_RE_, '&lt;');
        577 }
        578 if (str.indexOf('>') != -1) {
        579 str = str.replace(goog.string.GT_RE_, '&gt;');
        580 }
        581 if (str.indexOf('"') != -1) {
        582 str = str.replace(goog.string.QUOT_RE_, '&quot;');
        583 }
        584 if (str.indexOf('\'') != -1) {
        585 str = str.replace(goog.string.SINGLE_QUOTE_RE_, '&#39;');
        586 }
        587 if (str.indexOf('\x00') != -1) {
        588 str = str.replace(goog.string.NULL_RE_, '&#0;');
        589 }
        590 if (goog.string.DETECT_DOUBLE_ESCAPING && str.indexOf('e') != -1) {
        591 str = str.replace(goog.string.E_RE_, '&#101;');
        592 }
        593 return str;
        594 }
        595};
        596
        597
        598/**
        599 * Regular expression that matches an ampersand, for use in escaping.
        600 * @const {!RegExp}
        601 * @private
        602 */
        603goog.string.AMP_RE_ = /&/g;
        604
        605
        606/**
        607 * Regular expression that matches a less than sign, for use in escaping.
        608 * @const {!RegExp}
        609 * @private
        610 */
        611goog.string.LT_RE_ = /</g;
        612
        613
        614/**
        615 * Regular expression that matches a greater than sign, for use in escaping.
        616 * @const {!RegExp}
        617 * @private
        618 */
        619goog.string.GT_RE_ = />/g;
        620
        621
        622/**
        623 * Regular expression that matches a double quote, for use in escaping.
        624 * @const {!RegExp}
        625 * @private
        626 */
        627goog.string.QUOT_RE_ = /"/g;
        628
        629
        630/**
        631 * Regular expression that matches a single quote, for use in escaping.
        632 * @const {!RegExp}
        633 * @private
        634 */
        635goog.string.SINGLE_QUOTE_RE_ = /'/g;
        636
        637
        638/**
        639 * Regular expression that matches null character, for use in escaping.
        640 * @const {!RegExp}
        641 * @private
        642 */
        643goog.string.NULL_RE_ = /\x00/g;
        644
        645
        646/**
        647 * Regular expression that matches a lowercase letter "e", for use in escaping.
        648 * @const {!RegExp}
        649 * @private
        650 */
        651goog.string.E_RE_ = /e/g;
        652
        653
        654/**
        655 * Regular expression that matches any character that needs to be escaped.
        656 * @const {!RegExp}
        657 * @private
        658 */
        659goog.string.ALL_RE_ = (goog.string.DETECT_DOUBLE_ESCAPING ?
        660 /[\x00&<>"'e]/ :
        661 /[\x00&<>"']/);
        662
        663
        664/**
        665 * Unescapes an HTML string.
        666 *
        667 * @param {string} str The string to unescape.
        668 * @return {string} An unescaped copy of {@code str}.
        669 */
        670goog.string.unescapeEntities = function(str) {
        671 if (goog.string.contains(str, '&')) {
        672 // We are careful not to use a DOM if we do not have one or we explicitly
        673 // requested non-DOM html unescaping.
        674 if (!goog.string.FORCE_NON_DOM_HTML_UNESCAPING &&
        675 'document' in goog.global) {
        676 return goog.string.unescapeEntitiesUsingDom_(str);
        677 } else {
        678 // Fall back on pure XML entities
        679 return goog.string.unescapePureXmlEntities_(str);
        680 }
        681 }
        682 return str;
        683};
        684
        685
        686/**
        687 * Unescapes a HTML string using the provided document.
        688 *
        689 * @param {string} str The string to unescape.
        690 * @param {!Document} document A document to use in escaping the string.
        691 * @return {string} An unescaped copy of {@code str}.
        692 */
        693goog.string.unescapeEntitiesWithDocument = function(str, document) {
        694 if (goog.string.contains(str, '&')) {
        695 return goog.string.unescapeEntitiesUsingDom_(str, document);
        696 }
        697 return str;
        698};
        699
        700
        701/**
        702 * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric
        703 * entities. This function is XSS-safe and whitespace-preserving.
        704 * @private
        705 * @param {string} str The string to unescape.
        706 * @param {Document=} opt_document An optional document to use for creating
        707 * elements. If this is not specified then the default window.document
        708 * will be used.
        709 * @return {string} The unescaped {@code str} string.
        710 */
        711goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) {
        712 /** @type {!Object<string, string>} */
        713 var seen = {'&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"'};
        714 var div;
        715 if (opt_document) {
        716 div = opt_document.createElement('div');
        717 } else {
        718 div = goog.global.document.createElement('div');
        719 }
        720 // Match as many valid entity characters as possible. If the actual entity
        721 // happens to be shorter, it will still work as innerHTML will return the
        722 // trailing characters unchanged. Since the entity characters do not include
        723 // open angle bracket, there is no chance of XSS from the innerHTML use.
        724 // Since no whitespace is passed to innerHTML, whitespace is preserved.
        725 return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) {
        726 // Check for cached entity.
        727 var value = seen[s];
        728 if (value) {
        729 return value;
        730 }
        731 // Check for numeric entity.
        732 if (entity.charAt(0) == '#') {
        733 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex numbers.
        734 var n = Number('0' + entity.substr(1));
        735 if (!isNaN(n)) {
        736 value = String.fromCharCode(n);
        737 }
        738 }
        739 // Fall back to innerHTML otherwise.
        740 if (!value) {
        741 // Append a non-entity character to avoid a bug in Webkit that parses
        742 // an invalid entity at the end of innerHTML text as the empty string.
        743 div.innerHTML = s + ' ';
        744 // Then remove the trailing character from the result.
        745 value = div.firstChild.nodeValue.slice(0, -1);
        746 }
        747 // Cache and return.
        748 return seen[s] = value;
        749 });
        750};
        751
        752
        753/**
        754 * Unescapes XML entities.
        755 * @private
        756 * @param {string} str The string to unescape.
        757 * @return {string} An unescaped copy of {@code str}.
        758 */
        759goog.string.unescapePureXmlEntities_ = function(str) {
        760 return str.replace(/&([^;]+);/g, function(s, entity) {
        761 switch (entity) {
        762 case 'amp':
        763 return '&';
        764 case 'lt':
        765 return '<';
        766 case 'gt':
        767 return '>';
        768 case 'quot':
        769 return '"';
        770 default:
        771 if (entity.charAt(0) == '#') {
        772 // Prefix with 0 so that hex entities (e.g. &#x10) parse as hex.
        773 var n = Number('0' + entity.substr(1));
        774 if (!isNaN(n)) {
        775 return String.fromCharCode(n);
        776 }
        777 }
        778 // For invalid entities we just return the entity
        779 return s;
        780 }
        781 });
        782};
        783
        784
        785/**
        786 * Regular expression that matches an HTML entity.
        787 * See also HTML5: Tokenization / Tokenizing character references.
        788 * @private
        789 * @type {!RegExp}
        790 */
        791goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g;
        792
        793
        794/**
        795 * Do escaping of whitespace to preserve spatial formatting. We use character
        796 * entity #160 to make it safer for xml.
        797 * @param {string} str The string in which to escape whitespace.
        798 * @param {boolean=} opt_xml Whether to use XML compatible tags.
        799 * @return {string} An escaped copy of {@code str}.
        800 */
        801goog.string.whitespaceEscape = function(str, opt_xml) {
        802 // This doesn't use goog.string.preserveSpaces for backwards compatibility.
        803 return goog.string.newLineToBr(str.replace(/ /g, ' &#160;'), opt_xml);
        804};
        805
        806
        807/**
        808 * Preserve spaces that would be otherwise collapsed in HTML by replacing them
        809 * with non-breaking space Unicode characters.
        810 * @param {string} str The string in which to preserve whitespace.
        811 * @return {string} A copy of {@code str} with preserved whitespace.
        812 */
        813goog.string.preserveSpaces = function(str) {
        814 return str.replace(/(^|[\n ]) /g, '$1' + goog.string.Unicode.NBSP);
        815};
        816
        817
        818/**
        819 * Strip quote characters around a string. The second argument is a string of
        820 * characters to treat as quotes. This can be a single character or a string of
        821 * multiple character and in that case each of those are treated as possible
        822 * quote characters. For example:
        823 *
        824 * <pre>
        825 * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
        826 * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
        827 * </pre>
        828 *
        829 * @param {string} str The string to strip.
        830 * @param {string} quoteChars The quote characters to strip.
        831 * @return {string} A copy of {@code str} without the quotes.
        832 */
        833goog.string.stripQuotes = function(str, quoteChars) {
        834 var length = quoteChars.length;
        835 for (var i = 0; i < length; i++) {
        836 var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i);
        837 if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) {
        838 return str.substring(1, str.length - 1);
        839 }
        840 }
        841 return str;
        842};
        843
        844
        845/**
        846 * Truncates a string to a certain length and adds '...' if necessary. The
        847 * length also accounts for the ellipsis, so a maximum length of 10 and a string
        848 * 'Hello World!' produces 'Hello W...'.
        849 * @param {string} str The string to truncate.
        850 * @param {number} chars Max number of characters.
        851 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
        852 * characters from being cut off in the middle.
        853 * @return {string} The truncated {@code str} string.
        854 */
        855goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) {
        856 if (opt_protectEscapedCharacters) {
        857 str = goog.string.unescapeEntities(str);
        858 }
        859
        860 if (str.length > chars) {
        861 str = str.substring(0, chars - 3) + '...';
        862 }
        863
        864 if (opt_protectEscapedCharacters) {
        865 str = goog.string.htmlEscape(str);
        866 }
        867
        868 return str;
        869};
        870
        871
        872/**
        873 * Truncate a string in the middle, adding "..." if necessary,
        874 * and favoring the beginning of the string.
        875 * @param {string} str The string to truncate the middle of.
        876 * @param {number} chars Max number of characters.
        877 * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped
        878 * characters from being cutoff in the middle.
        879 * @param {number=} opt_trailingChars Optional number of trailing characters to
        880 * leave at the end of the string, instead of truncating as close to the
        881 * middle as possible.
        882 * @return {string} A truncated copy of {@code str}.
        883 */
        884goog.string.truncateMiddle = function(str, chars,
        885 opt_protectEscapedCharacters, opt_trailingChars) {
        886 if (opt_protectEscapedCharacters) {
        887 str = goog.string.unescapeEntities(str);
        888 }
        889
        890 if (opt_trailingChars && str.length > chars) {
        891 if (opt_trailingChars > chars) {
        892 opt_trailingChars = chars;
        893 }
        894 var endPoint = str.length - opt_trailingChars;
        895 var startPoint = chars - opt_trailingChars;
        896 str = str.substring(0, startPoint) + '...' + str.substring(endPoint);
        897 } else if (str.length > chars) {
        898 // Favor the beginning of the string:
        899 var half = Math.floor(chars / 2);
        900 var endPos = str.length - half;
        901 half += chars % 2;
        902 str = str.substring(0, half) + '...' + str.substring(endPos);
        903 }
        904
        905 if (opt_protectEscapedCharacters) {
        906 str = goog.string.htmlEscape(str);
        907 }
        908
        909 return str;
        910};
        911
        912
        913/**
        914 * Special chars that need to be escaped for goog.string.quote.
        915 * @private {!Object<string, string>}
        916 */
        917goog.string.specialEscapeChars_ = {
        918 '\0': '\\0',
        919 '\b': '\\b',
        920 '\f': '\\f',
        921 '\n': '\\n',
        922 '\r': '\\r',
        923 '\t': '\\t',
        924 '\x0B': '\\x0B', // '\v' is not supported in JScript
        925 '"': '\\"',
        926 '\\': '\\\\'
        927};
        928
        929
        930/**
        931 * Character mappings used internally for goog.string.escapeChar.
        932 * @private {!Object<string, string>}
        933 */
        934goog.string.jsEscapeCache_ = {
        935 '\'': '\\\''
        936};
        937
        938
        939/**
        940 * Encloses a string in double quotes and escapes characters so that the
        941 * string is a valid JS string.
        942 * @param {string} s The string to quote.
        943 * @return {string} A copy of {@code s} surrounded by double quotes.
        944 */
        945goog.string.quote = function(s) {
        946 s = String(s);
        947 if (s.quote) {
        948 return s.quote();
        949 } else {
        950 var sb = ['"'];
        951 for (var i = 0; i < s.length; i++) {
        952 var ch = s.charAt(i);
        953 var cc = ch.charCodeAt(0);
        954 sb[i + 1] = goog.string.specialEscapeChars_[ch] ||
        955 ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch));
        956 }
        957 sb.push('"');
        958 return sb.join('');
        959 }
        960};
        961
        962
        963/**
        964 * Takes a string and returns the escaped string for that character.
        965 * @param {string} str The string to escape.
        966 * @return {string} An escaped string representing {@code str}.
        967 */
        968goog.string.escapeString = function(str) {
        969 var sb = [];
        970 for (var i = 0; i < str.length; i++) {
        971 sb[i] = goog.string.escapeChar(str.charAt(i));
        972 }
        973 return sb.join('');
        974};
        975
        976
        977/**
        978 * Takes a character and returns the escaped string for that character. For
        979 * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
        980 * @param {string} c The character to escape.
        981 * @return {string} An escaped string representing {@code c}.
        982 */
        983goog.string.escapeChar = function(c) {
        984 if (c in goog.string.jsEscapeCache_) {
        985 return goog.string.jsEscapeCache_[c];
        986 }
        987
        988 if (c in goog.string.specialEscapeChars_) {
        989 return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c];
        990 }
        991
        992 var rv = c;
        993 var cc = c.charCodeAt(0);
        994 if (cc > 31 && cc < 127) {
        995 rv = c;
        996 } else {
        997 // tab is 9 but handled above
        998 if (cc < 256) {
        999 rv = '\\x';
        1000 if (cc < 16 || cc > 256) {
        1001 rv += '0';
        1002 }
        1003 } else {
        1004 rv = '\\u';
        1005 if (cc < 4096) { // \u1000
        1006 rv += '0';
        1007 }
        1008 }
        1009 rv += cc.toString(16).toUpperCase();
        1010 }
        1011
        1012 return goog.string.jsEscapeCache_[c] = rv;
        1013};
        1014
        1015
        1016/**
        1017 * Determines whether a string contains a substring.
        1018 * @param {string} str The string to search.
        1019 * @param {string} subString The substring to search for.
        1020 * @return {boolean} Whether {@code str} contains {@code subString}.
        1021 */
        1022goog.string.contains = function(str, subString) {
        1023 return str.indexOf(subString) != -1;
        1024};
        1025
        1026
        1027/**
        1028 * Determines whether a string contains a substring, ignoring case.
        1029 * @param {string} str The string to search.
        1030 * @param {string} subString The substring to search for.
        1031 * @return {boolean} Whether {@code str} contains {@code subString}.
        1032 */
        1033goog.string.caseInsensitiveContains = function(str, subString) {
        1034 return goog.string.contains(str.toLowerCase(), subString.toLowerCase());
        1035};
        1036
        1037
        1038/**
        1039 * Returns the non-overlapping occurrences of ss in s.
        1040 * If either s or ss evalutes to false, then returns zero.
        1041 * @param {string} s The string to look in.
        1042 * @param {string} ss The string to look for.
        1043 * @return {number} Number of occurrences of ss in s.
        1044 */
        1045goog.string.countOf = function(s, ss) {
        1046 return s && ss ? s.split(ss).length - 1 : 0;
        1047};
        1048
        1049
        1050/**
        1051 * Removes a substring of a specified length at a specific
        1052 * index in a string.
        1053 * @param {string} s The base string from which to remove.
        1054 * @param {number} index The index at which to remove the substring.
        1055 * @param {number} stringLength The length of the substring to remove.
        1056 * @return {string} A copy of {@code s} with the substring removed or the full
        1057 * string if nothing is removed or the input is invalid.
        1058 */
        1059goog.string.removeAt = function(s, index, stringLength) {
        1060 var resultStr = s;
        1061 // If the index is greater or equal to 0 then remove substring
        1062 if (index >= 0 && index < s.length && stringLength > 0) {
        1063 resultStr = s.substr(0, index) +
        1064 s.substr(index + stringLength, s.length - index - stringLength);
        1065 }
        1066 return resultStr;
        1067};
        1068
        1069
        1070/**
        1071 * Removes the first occurrence of a substring from a string.
        1072 * @param {string} s The base string from which to remove.
        1073 * @param {string} ss The string to remove.
        1074 * @return {string} A copy of {@code s} with {@code ss} removed or the full
        1075 * string if nothing is removed.
        1076 */
        1077goog.string.remove = function(s, ss) {
        1078 var re = new RegExp(goog.string.regExpEscape(ss), '');
        1079 return s.replace(re, '');
        1080};
        1081
        1082
        1083/**
        1084 * Removes all occurrences of a substring from a string.
        1085 * @param {string} s The base string from which to remove.
        1086 * @param {string} ss The string to remove.
        1087 * @return {string} A copy of {@code s} with {@code ss} removed or the full
        1088 * string if nothing is removed.
        1089 */
        1090goog.string.removeAll = function(s, ss) {
        1091 var re = new RegExp(goog.string.regExpEscape(ss), 'g');
        1092 return s.replace(re, '');
        1093};
        1094
        1095
        1096/**
        1097 * Escapes characters in the string that are not safe to use in a RegExp.
        1098 * @param {*} s The string to escape. If not a string, it will be casted
        1099 * to one.
        1100 * @return {string} A RegExp safe, escaped copy of {@code s}.
        1101 */
        1102goog.string.regExpEscape = function(s) {
        1103 return String(s).replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
        1104 replace(/\x08/g, '\\x08');
        1105};
        1106
        1107
        1108/**
        1109 * Repeats a string n times.
        1110 * @param {string} string The string to repeat.
        1111 * @param {number} length The number of times to repeat.
        1112 * @return {string} A string containing {@code length} repetitions of
        1113 * {@code string}.
        1114 */
        1115goog.string.repeat = function(string, length) {
        1116 return new Array(length + 1).join(string);
        1117};
        1118
        1119
        1120/**
        1121 * Pads number to given length and optionally rounds it to a given precision.
        1122 * For example:
        1123 * <pre>padNumber(1.25, 2, 3) -> '01.250'
        1124 * padNumber(1.25, 2) -> '01.25'
        1125 * padNumber(1.25, 2, 1) -> '01.3'
        1126 * padNumber(1.25, 0) -> '1.25'</pre>
        1127 *
        1128 * @param {number} num The number to pad.
        1129 * @param {number} length The desired length.
        1130 * @param {number=} opt_precision The desired precision.
        1131 * @return {string} {@code num} as a string with the given options.
        1132 */
        1133goog.string.padNumber = function(num, length, opt_precision) {
        1134 var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num);
        1135 var index = s.indexOf('.');
        1136 if (index == -1) {
        1137 index = s.length;
        1138 }
        1139 return goog.string.repeat('0', Math.max(0, length - index)) + s;
        1140};
        1141
        1142
        1143/**
        1144 * Returns a string representation of the given object, with
        1145 * null and undefined being returned as the empty string.
        1146 *
        1147 * @param {*} obj The object to convert.
        1148 * @return {string} A string representation of the {@code obj}.
        1149 */
        1150goog.string.makeSafe = function(obj) {
        1151 return obj == null ? '' : String(obj);
        1152};
        1153
        1154
        1155/**
        1156 * Concatenates string expressions. This is useful
        1157 * since some browsers are very inefficient when it comes to using plus to
        1158 * concat strings. Be careful when using null and undefined here since
        1159 * these will not be included in the result. If you need to represent these
        1160 * be sure to cast the argument to a String first.
        1161 * For example:
        1162 * <pre>buildString('a', 'b', 'c', 'd') -> 'abcd'
        1163 * buildString(null, undefined) -> ''
        1164 * </pre>
        1165 * @param {...*} var_args A list of strings to concatenate. If not a string,
        1166 * it will be casted to one.
        1167 * @return {string} The concatenation of {@code var_args}.
        1168 */
        1169goog.string.buildString = function(var_args) {
        1170 return Array.prototype.join.call(arguments, '');
        1171};
        1172
        1173
        1174/**
        1175 * Returns a string with at least 64-bits of randomness.
        1176 *
        1177 * Doesn't trust Javascript's random function entirely. Uses a combination of
        1178 * random and current timestamp, and then encodes the string in base-36 to
        1179 * make it shorter.
        1180 *
        1181 * @return {string} A random string, e.g. sn1s7vb4gcic.
        1182 */
        1183goog.string.getRandomString = function() {
        1184 var x = 2147483648;
        1185 return Math.floor(Math.random() * x).toString(36) +
        1186 Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36);
        1187};
        1188
        1189
        1190/**
        1191 * Compares two version numbers.
        1192 *
        1193 * @param {string|number} version1 Version of first item.
        1194 * @param {string|number} version2 Version of second item.
        1195 *
        1196 * @return {number} 1 if {@code version1} is higher.
        1197 * 0 if arguments are equal.
        1198 * -1 if {@code version2} is higher.
        1199 */
        1200goog.string.compareVersions = function(version1, version2) {
        1201 var order = 0;
        1202 // Trim leading and trailing whitespace and split the versions into
        1203 // subversions.
        1204 var v1Subs = goog.string.trim(String(version1)).split('.');
        1205 var v2Subs = goog.string.trim(String(version2)).split('.');
        1206 var subCount = Math.max(v1Subs.length, v2Subs.length);
        1207
        1208 // Iterate over the subversions, as long as they appear to be equivalent.
        1209 for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) {
        1210 var v1Sub = v1Subs[subIdx] || '';
        1211 var v2Sub = v2Subs[subIdx] || '';
        1212
        1213 // Split the subversions into pairs of numbers and qualifiers (like 'b').
        1214 // Two different RegExp objects are needed because they are both using
        1215 // the 'g' flag.
        1216 var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g');
        1217 var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g');
        1218 do {
        1219 var v1Comp = v1CompParser.exec(v1Sub) || ['', '', ''];
        1220 var v2Comp = v2CompParser.exec(v2Sub) || ['', '', ''];
        1221 // Break if there are no more matches.
        1222 if (v1Comp[0].length == 0 && v2Comp[0].length == 0) {
        1223 break;
        1224 }
        1225
        1226 // Parse the numeric part of the subversion. A missing number is
        1227 // equivalent to 0.
        1228 var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10);
        1229 var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10);
        1230
        1231 // Compare the subversion components. The number has the highest
        1232 // precedence. Next, if the numbers are equal, a subversion without any
        1233 // qualifier is always higher than a subversion with any qualifier. Next,
        1234 // the qualifiers are compared as strings.
        1235 order = goog.string.compareElements_(v1CompNum, v2CompNum) ||
        1236 goog.string.compareElements_(v1Comp[2].length == 0,
        1237 v2Comp[2].length == 0) ||
        1238 goog.string.compareElements_(v1Comp[2], v2Comp[2]);
        1239 // Stop as soon as an inequality is discovered.
        1240 } while (order == 0);
        1241 }
        1242
        1243 return order;
        1244};
        1245
        1246
        1247/**
        1248 * Compares elements of a version number.
        1249 *
        1250 * @param {string|number|boolean} left An element from a version number.
        1251 * @param {string|number|boolean} right An element from a version number.
        1252 *
        1253 * @return {number} 1 if {@code left} is higher.
        1254 * 0 if arguments are equal.
        1255 * -1 if {@code right} is higher.
        1256 * @private
        1257 */
        1258goog.string.compareElements_ = function(left, right) {
        1259 if (left < right) {
        1260 return -1;
        1261 } else if (left > right) {
        1262 return 1;
        1263 }
        1264 return 0;
        1265};
        1266
        1267
        1268/**
        1269 * Maximum value of #goog.string.hashCode, exclusive. 2^32.
        1270 * @type {number}
        1271 * @private
        1272 */
        1273goog.string.HASHCODE_MAX_ = 0x100000000;
        1274
        1275
        1276/**
        1277 * String hash function similar to java.lang.String.hashCode().
        1278 * The hash code for a string is computed as
        1279 * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1],
        1280 * where s[i] is the ith character of the string and n is the length of
        1281 * the string. We mod the result to make it between 0 (inclusive) and 2^32
        1282 * (exclusive).
        1283 * @param {string} str A string.
        1284 * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32
        1285 * (exclusive). The empty string returns 0.
        1286 */
        1287goog.string.hashCode = function(str) {
        1288 var result = 0;
        1289 for (var i = 0; i < str.length; ++i) {
        1290 result = 31 * result + str.charCodeAt(i);
        1291 // Normalize to 4 byte range, 0 ... 2^32.
        1292 result %= goog.string.HASHCODE_MAX_;
        1293 }
        1294 return result;
        1295};
        1296
        1297
        1298/**
        1299 * The most recent unique ID. |0 is equivalent to Math.floor in this case.
        1300 * @type {number}
        1301 * @private
        1302 */
        1303goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0;
        1304
        1305
        1306/**
        1307 * Generates and returns a string which is unique in the current document.
        1308 * This is useful, for example, to create unique IDs for DOM elements.
        1309 * @return {string} A unique id.
        1310 */
        1311goog.string.createUniqueString = function() {
        1312 return 'goog_' + goog.string.uniqueStringCounter_++;
        1313};
        1314
        1315
        1316/**
        1317 * Converts the supplied string to a number, which may be Infinity or NaN.
        1318 * This function strips whitespace: (toNumber(' 123') === 123)
        1319 * This function accepts scientific notation: (toNumber('1e1') === 10)
        1320 *
        1321 * This is better than Javascript's built-in conversions because, sadly:
        1322 * (Number(' ') === 0) and (parseFloat('123a') === 123)
        1323 *
        1324 * @param {string} str The string to convert.
        1325 * @return {number} The number the supplied string represents, or NaN.
        1326 */
        1327goog.string.toNumber = function(str) {
        1328 var num = Number(str);
        1329 if (num == 0 && goog.string.isEmptyOrWhitespace(str)) {
        1330 return NaN;
        1331 }
        1332 return num;
        1333};
        1334
        1335
        1336/**
        1337 * Returns whether the given string is lower camel case (e.g. "isFooBar").
        1338 *
        1339 * Note that this assumes the string is entirely letters.
        1340 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
        1341 *
        1342 * @param {string} str String to test.
        1343 * @return {boolean} Whether the string is lower camel case.
        1344 */
        1345goog.string.isLowerCamelCase = function(str) {
        1346 return /^[a-z]+([A-Z][a-z]*)*$/.test(str);
        1347};
        1348
        1349
        1350/**
        1351 * Returns whether the given string is upper camel case (e.g. "FooBarBaz").
        1352 *
        1353 * Note that this assumes the string is entirely letters.
        1354 * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms
        1355 *
        1356 * @param {string} str String to test.
        1357 * @return {boolean} Whether the string is upper camel case.
        1358 */
        1359goog.string.isUpperCamelCase = function(str) {
        1360 return /^([A-Z][a-z]*)+$/.test(str);
        1361};
        1362
        1363
        1364/**
        1365 * Converts a string from selector-case to camelCase (e.g. from
        1366 * "multi-part-string" to "multiPartString"), useful for converting
        1367 * CSS selectors and HTML dataset keys to their equivalent JS properties.
        1368 * @param {string} str The string in selector-case form.
        1369 * @return {string} The string in camelCase form.
        1370 */
        1371goog.string.toCamelCase = function(str) {
        1372 return String(str).replace(/\-([a-z])/g, function(all, match) {
        1373 return match.toUpperCase();
        1374 });
        1375};
        1376
        1377
        1378/**
        1379 * Converts a string from camelCase to selector-case (e.g. from
        1380 * "multiPartString" to "multi-part-string"), useful for converting JS
        1381 * style and dataset properties to equivalent CSS selectors and HTML keys.
        1382 * @param {string} str The string in camelCase form.
        1383 * @return {string} The string in selector-case form.
        1384 */
        1385goog.string.toSelectorCase = function(str) {
        1386 return String(str).replace(/([A-Z])/g, '-$1').toLowerCase();
        1387};
        1388
        1389
        1390/**
        1391 * Converts a string into TitleCase. First character of the string is always
        1392 * capitalized in addition to the first letter of every subsequent word.
        1393 * Words are delimited by one or more whitespaces by default. Custom delimiters
        1394 * can optionally be specified to replace the default, which doesn't preserve
        1395 * whitespace delimiters and instead must be explicitly included if needed.
        1396 *
        1397 * Default delimiter => " ":
        1398 * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree'
        1399 * goog.string.toTitleCase('one two three') => 'One Two Three'
        1400 * goog.string.toTitleCase(' one two ') => ' One Two '
        1401 * goog.string.toTitleCase('one_two_three') => 'One_two_three'
        1402 * goog.string.toTitleCase('one-two-three') => 'One-two-three'
        1403 *
        1404 * Custom delimiter => "_-.":
        1405 * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree'
        1406 * goog.string.toTitleCase('one two three', '_-.') => 'One two three'
        1407 * goog.string.toTitleCase(' one two ', '_-.') => ' one two '
        1408 * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three'
        1409 * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three'
        1410 * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three'
        1411 * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three'
        1412 * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three'
        1413 *
        1414 * @param {string} str String value in camelCase form.
        1415 * @param {string=} opt_delimiters Custom delimiter character set used to
        1416 * distinguish words in the string value. Each character represents a
        1417 * single delimiter. When provided, default whitespace delimiter is
        1418 * overridden and must be explicitly included if needed.
        1419 * @return {string} String value in TitleCase form.
        1420 */
        1421goog.string.toTitleCase = function(str, opt_delimiters) {
        1422 var delimiters = goog.isString(opt_delimiters) ?
        1423 goog.string.regExpEscape(opt_delimiters) : '\\s';
        1424
        1425 // For IE8, we need to prevent using an empty character set. Otherwise,
        1426 // incorrect matching will occur.
        1427 delimiters = delimiters ? '|[' + delimiters + ']+' : '';
        1428
        1429 var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g');
        1430 return str.replace(regexp, function(all, p1, p2) {
        1431 return p1 + p2.toUpperCase();
        1432 });
        1433};
        1434
        1435
        1436/**
        1437 * Capitalizes a string, i.e. converts the first letter to uppercase
        1438 * and all other letters to lowercase, e.g.:
        1439 *
        1440 * goog.string.capitalize('one') => 'One'
        1441 * goog.string.capitalize('ONE') => 'One'
        1442 * goog.string.capitalize('one two') => 'One two'
        1443 *
        1444 * Note that this function does not trim initial whitespace.
        1445 *
        1446 * @param {string} str String value to capitalize.
        1447 * @return {string} String value with first letter in uppercase.
        1448 */
        1449goog.string.capitalize = function(str) {
        1450 return String(str.charAt(0)).toUpperCase() +
        1451 String(str.substr(1)).toLowerCase();
        1452};
        1453
        1454
        1455/**
        1456 * Parse a string in decimal or hexidecimal ('0xFFFF') form.
        1457 *
        1458 * To parse a particular radix, please use parseInt(string, radix) directly. See
        1459 * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt
        1460 *
        1461 * This is a wrapper for the built-in parseInt function that will only parse
        1462 * numbers as base 10 or base 16. Some JS implementations assume strings
        1463 * starting with "0" are intended to be octal. ES3 allowed but discouraged
        1464 * this behavior. ES5 forbids it. This function emulates the ES5 behavior.
        1465 *
        1466 * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj
        1467 *
        1468 * @param {string|number|null|undefined} value The value to be parsed.
        1469 * @return {number} The number, parsed. If the string failed to parse, this
        1470 * will be NaN.
        1471 */
        1472goog.string.parseInt = function(value) {
        1473 // Force finite numbers to strings.
        1474 if (isFinite(value)) {
        1475 value = String(value);
        1476 }
        1477
        1478 if (goog.isString(value)) {
        1479 // If the string starts with '0x' or '-0x', parse as hex.
        1480 return /^\s*-?0x/i.test(value) ?
        1481 parseInt(value, 16) : parseInt(value, 10);
        1482 }
        1483
        1484 return NaN;
        1485};
        1486
        1487
        1488/**
        1489 * Splits a string on a separator a limited number of times.
        1490 *
        1491 * This implementation is more similar to Python or Java, where the limit
        1492 * parameter specifies the maximum number of splits rather than truncating
        1493 * the number of results.
        1494 *
        1495 * See http://docs.python.org/2/library/stdtypes.html#str.split
        1496 * See JavaDoc: http://goo.gl/F2AsY
        1497 * See Mozilla reference: http://goo.gl/dZdZs
        1498 *
        1499 * @param {string} str String to split.
        1500 * @param {string} separator The separator.
        1501 * @param {number} limit The limit to the number of splits. The resulting array
        1502 * will have a maximum length of limit+1. Negative numbers are the same
        1503 * as zero.
        1504 * @return {!Array<string>} The string, split.
        1505 */
        1506
        1507goog.string.splitLimit = function(str, separator, limit) {
        1508 var parts = str.split(separator);
        1509 var returnVal = [];
        1510
        1511 // Only continue doing this while we haven't hit the limit and we have
        1512 // parts left.
        1513 while (limit > 0 && parts.length) {
        1514 returnVal.push(parts.shift());
        1515 limit--;
        1516 }
        1517
        1518 // If there are remaining parts, append them to the end.
        1519 if (parts.length) {
        1520 returnVal.push(parts.join(separator));
        1521 }
        1522
        1523 return returnVal;
        1524};
        1525
        1526
        1527/**
        1528 * Computes the Levenshtein edit distance between two strings.
        1529 * @param {string} a
        1530 * @param {string} b
        1531 * @return {number} The edit distance between the two strings.
        1532 */
        1533goog.string.editDistance = function(a, b) {
        1534 var v0 = [];
        1535 var v1 = [];
        1536
        1537 if (a == b) {
        1538 return 0;
        1539 }
        1540
        1541 if (!a.length || !b.length) {
        1542 return Math.max(a.length, b.length);
        1543 }
        1544
        1545 for (var i = 0; i < b.length + 1; i++) {
        1546 v0[i] = i;
        1547 }
        1548
        1549 for (var i = 0; i < a.length; i++) {
        1550 v1[0] = i + 1;
        1551
        1552 for (var j = 0; j < b.length; j++) {
        1553 var cost = a[i] != b[j];
        1554 // Cost for the substring is the minimum of adding one character, removing
        1555 // one character, or a swap.
        1556 v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
        1557 }
        1558
        1559 for (var j = 0; j < v0.length; j++) {
        1560 v0[j] = v1[j];
        1561 }
        1562 }
        1563
        1564 return v1[b.length];
        1565};
        \ No newline at end of file diff --git a/docs/source/lib/goog/string/typedstring.js.src.html b/docs/source/lib/goog/string/typedstring.js.src.html new file mode 100644 index 0000000..55103fb --- /dev/null +++ b/docs/source/lib/goog/string/typedstring.js.src.html @@ -0,0 +1 @@ +typedstring.js

        lib/goog/string/typedstring.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('goog.string.TypedString');
        16
        17
        18
        19/**
        20 * Wrapper for strings that conform to a data type or language.
        21 *
        22 * Implementations of this interface are wrappers for strings, and typically
        23 * associate a type contract with the wrapped string. Concrete implementations
        24 * of this interface may choose to implement additional run-time type checking,
        25 * see for example {@code goog.html.SafeHtml}. If available, client code that
        26 * needs to ensure type membership of an object should use the type's function
        27 * to assert type membership, such as {@code goog.html.SafeHtml.unwrap}.
        28 * @interface
        29 */
        30goog.string.TypedString = function() {};
        31
        32
        33/**
        34 * Interface marker of the TypedString interface.
        35 *
        36 * This property can be used to determine at runtime whether or not an object
        37 * implements this interface. All implementations of this interface set this
        38 * property to {@code true}.
        39 * @type {boolean}
        40 */
        41goog.string.TypedString.prototype.implementsGoogStringTypedString;
        42
        43
        44/**
        45 * Retrieves this wrapped string's value.
        46 * @return {!string} The wrapped string's value.
        47 */
        48goog.string.TypedString.prototype.getTypedStringValue;
        \ No newline at end of file diff --git a/docs/source/lib/goog/structs/collection.js.src.html b/docs/source/lib/goog/structs/collection.js.src.html new file mode 100644 index 0000000..59ede41 --- /dev/null +++ b/docs/source/lib/goog/structs/collection.js.src.html @@ -0,0 +1 @@ +collection.js

        lib/goog/structs/collection.js

        1// Copyright 2011 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines the collection interface.
        17 *
        18 * @author nnaze@google.com (Nathan Naze)
        19 */
        20
        21goog.provide('goog.structs.Collection');
        22
        23
        24
        25/**
        26 * An interface for a collection of values.
        27 * @interface
        28 * @template T
        29 */
        30goog.structs.Collection = function() {};
        31
        32
        33/**
        34 * @param {T} value Value to add to the collection.
        35 */
        36goog.structs.Collection.prototype.add;
        37
        38
        39/**
        40 * @param {T} value Value to remove from the collection.
        41 */
        42goog.structs.Collection.prototype.remove;
        43
        44
        45/**
        46 * @param {T} value Value to find in the collection.
        47 * @return {boolean} Whether the collection contains the specified value.
        48 */
        49goog.structs.Collection.prototype.contains;
        50
        51
        52/**
        53 * @return {number} The number of values stored in the collection.
        54 */
        55goog.structs.Collection.prototype.getCount;
        56
        \ No newline at end of file diff --git a/docs/source/lib/goog/structs/map.js.src.html b/docs/source/lib/goog/structs/map.js.src.html index c9ff1a7..5beaae0 100644 --- a/docs/source/lib/goog/structs/map.js.src.html +++ b/docs/source/lib/goog/structs/map.js.src.html @@ -1 +1 @@ -map.js

        lib/goog/structs/map.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Datastructure: Hash Map.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 * @author jonp@google.com (Jon Perlow) Optimized for IE6
        20 *
        21 * This file contains an implementation of a Map structure. It implements a lot
        22 * of the methods used in goog.structs so those functions work on hashes. This
        23 * is best suited for complex key types. For simple keys such as numbers and
        24 * strings, and where special names like __proto__ are not a concern, consider
        25 * using the lighter-weight utilities in goog.object.
        26 */
        27
        28
        29goog.provide('goog.structs.Map');
        30
        31goog.require('goog.iter.Iterator');
        32goog.require('goog.iter.StopIteration');
        33goog.require('goog.object');
        34
        35
        36
        37/**
        38 * Class for Hash Map datastructure.
        39 * @param {*=} opt_map Map or Object to initialize the map with.
        40 * @param {...*} var_args If 2 or more arguments are present then they
        41 * will be used as key-value pairs.
        42 * @constructor
        43 */
        44goog.structs.Map = function(opt_map, var_args) {
        45
        46 /**
        47 * Underlying JS object used to implement the map.
        48 * @type {!Object}
        49 * @private
        50 */
        51 this.map_ = {};
        52
        53 /**
        54 * An array of keys. This is necessary for two reasons:
        55 * 1. Iterating the keys using for (var key in this.map_) allocates an
        56 * object for every key in IE which is really bad for IE6 GC perf.
        57 * 2. Without a side data structure, we would need to escape all the keys
        58 * as that would be the only way we could tell during iteration if the
        59 * key was an internal key or a property of the object.
        60 *
        61 * This array can contain deleted keys so it's necessary to check the map
        62 * as well to see if the key is still in the map (this doesn't require a
        63 * memory allocation in IE).
        64 * @type {!Array.<string>}
        65 * @private
        66 */
        67 this.keys_ = [];
        68
        69 var argLength = arguments.length;
        70
        71 if (argLength > 1) {
        72 if (argLength % 2) {
        73 throw Error('Uneven number of arguments');
        74 }
        75 for (var i = 0; i < argLength; i += 2) {
        76 this.set(arguments[i], arguments[i + 1]);
        77 }
        78 } else if (opt_map) {
        79 this.addAll(/** @type {Object} */ (opt_map));
        80 }
        81};
        82
        83
        84/**
        85 * The number of key value pairs in the map.
        86 * @private
        87 * @type {number}
        88 */
        89goog.structs.Map.prototype.count_ = 0;
        90
        91
        92/**
        93 * Version used to detect changes while iterating.
        94 * @private
        95 * @type {number}
        96 */
        97goog.structs.Map.prototype.version_ = 0;
        98
        99
        100/**
        101 * @return {number} The number of key-value pairs in the map.
        102 */
        103goog.structs.Map.prototype.getCount = function() {
        104 return this.count_;
        105};
        106
        107
        108/**
        109 * Returns the values of the map.
        110 * @return {!Array} The values in the map.
        111 */
        112goog.structs.Map.prototype.getValues = function() {
        113 this.cleanupKeysArray_();
        114
        115 var rv = [];
        116 for (var i = 0; i < this.keys_.length; i++) {
        117 var key = this.keys_[i];
        118 rv.push(this.map_[key]);
        119 }
        120 return rv;
        121};
        122
        123
        124/**
        125 * Returns the keys of the map.
        126 * @return {!Array.<string>} Array of string values.
        127 */
        128goog.structs.Map.prototype.getKeys = function() {
        129 this.cleanupKeysArray_();
        130 return /** @type {!Array.<string>} */ (this.keys_.concat());
        131};
        132
        133
        134/**
        135 * Whether the map contains the given key.
        136 * @param {*} key The key to check for.
        137 * @return {boolean} Whether the map contains the key.
        138 */
        139goog.structs.Map.prototype.containsKey = function(key) {
        140 return goog.structs.Map.hasKey_(this.map_, key);
        141};
        142
        143
        144/**
        145 * Whether the map contains the given value. This is O(n).
        146 * @param {*} val The value to check for.
        147 * @return {boolean} Whether the map contains the value.
        148 */
        149goog.structs.Map.prototype.containsValue = function(val) {
        150 for (var i = 0; i < this.keys_.length; i++) {
        151 var key = this.keys_[i];
        152 if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
        153 return true;
        154 }
        155 }
        156 return false;
        157};
        158
        159
        160/**
        161 * Whether this map is equal to the argument map.
        162 * @param {goog.structs.Map} otherMap The map against which to test equality.
        163 * @param {function(?, ?) : boolean=} opt_equalityFn Optional equality function
        164 * to test equality of values. If not specified, this will test whether
        165 * the values contained in each map are identical objects.
        166 * @return {boolean} Whether the maps are equal.
        167 */
        168goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
        169 if (this === otherMap) {
        170 return true;
        171 }
        172
        173 if (this.count_ != otherMap.getCount()) {
        174 return false;
        175 }
        176
        177 var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
        178
        179 this.cleanupKeysArray_();
        180 for (var key, i = 0; key = this.keys_[i]; i++) {
        181 if (!equalityFn(this.get(key), otherMap.get(key))) {
        182 return false;
        183 }
        184 }
        185
        186 return true;
        187};
        188
        189
        190/**
        191 * Default equality test for values.
        192 * @param {*} a The first value.
        193 * @param {*} b The second value.
        194 * @return {boolean} Whether a and b reference the same object.
        195 */
        196goog.structs.Map.defaultEquals = function(a, b) {
        197 return a === b;
        198};
        199
        200
        201/**
        202 * @return {boolean} Whether the map is empty.
        203 */
        204goog.structs.Map.prototype.isEmpty = function() {
        205 return this.count_ == 0;
        206};
        207
        208
        209/**
        210 * Removes all key-value pairs from the map.
        211 */
        212goog.structs.Map.prototype.clear = function() {
        213 this.map_ = {};
        214 this.keys_.length = 0;
        215 this.count_ = 0;
        216 this.version_ = 0;
        217};
        218
        219
        220/**
        221 * Removes a key-value pair based on the key. This is O(logN) amortized due to
        222 * updating the keys array whenever the count becomes half the size of the keys
        223 * in the keys array.
        224 * @param {*} key The key to remove.
        225 * @return {boolean} Whether object was removed.
        226 */
        227goog.structs.Map.prototype.remove = function(key) {
        228 if (goog.structs.Map.hasKey_(this.map_, key)) {
        229 delete this.map_[key];
        230 this.count_--;
        231 this.version_++;
        232
        233 // clean up the keys array if the threshhold is hit
        234 if (this.keys_.length > 2 * this.count_) {
        235 this.cleanupKeysArray_();
        236 }
        237
        238 return true;
        239 }
        240 return false;
        241};
        242
        243
        244/**
        245 * Cleans up the temp keys array by removing entries that are no longer in the
        246 * map.
        247 * @private
        248 */
        249goog.structs.Map.prototype.cleanupKeysArray_ = function() {
        250 if (this.count_ != this.keys_.length) {
        251 // First remove keys that are no longer in the map.
        252 var srcIndex = 0;
        253 var destIndex = 0;
        254 while (srcIndex < this.keys_.length) {
        255 var key = this.keys_[srcIndex];
        256 if (goog.structs.Map.hasKey_(this.map_, key)) {
        257 this.keys_[destIndex++] = key;
        258 }
        259 srcIndex++;
        260 }
        261 this.keys_.length = destIndex;
        262 }
        263
        264 if (this.count_ != this.keys_.length) {
        265 // If the count still isn't correct, that means we have duplicates. This can
        266 // happen when the same key is added and removed multiple times. Now we have
        267 // to allocate one extra Object to remove the duplicates. This could have
        268 // been done in the first pass, but in the common case, we can avoid
        269 // allocating an extra object by only doing this when necessary.
        270 var seen = {};
        271 var srcIndex = 0;
        272 var destIndex = 0;
        273 while (srcIndex < this.keys_.length) {
        274 var key = this.keys_[srcIndex];
        275 if (!(goog.structs.Map.hasKey_(seen, key))) {
        276 this.keys_[destIndex++] = key;
        277 seen[key] = 1;
        278 }
        279 srcIndex++;
        280 }
        281 this.keys_.length = destIndex;
        282 }
        283};
        284
        285
        286/**
        287 * Returns the value for the given key. If the key is not found and the default
        288 * value is not given this will return {@code undefined}.
        289 * @param {*} key The key to get the value for.
        290 * @param {*=} opt_val The value to return if no item is found for the given
        291 * key, defaults to undefined.
        292 * @return {*} The value for the given key.
        293 */
        294goog.structs.Map.prototype.get = function(key, opt_val) {
        295 if (goog.structs.Map.hasKey_(this.map_, key)) {
        296 return this.map_[key];
        297 }
        298 return opt_val;
        299};
        300
        301
        302/**
        303 * Adds a key-value pair to the map.
        304 * @param {*} key The key.
        305 * @param {*} value The value to add.
        306 * @return {*} Some subclasses return a value.
        307 */
        308goog.structs.Map.prototype.set = function(key, value) {
        309 if (!(goog.structs.Map.hasKey_(this.map_, key))) {
        310 this.count_++;
        311 this.keys_.push(key);
        312 // Only change the version if we add a new key.
        313 this.version_++;
        314 }
        315 this.map_[key] = value;
        316};
        317
        318
        319/**
        320 * Adds multiple key-value pairs from another goog.structs.Map or Object.
        321 * @param {Object} map Object containing the data to add.
        322 */
        323goog.structs.Map.prototype.addAll = function(map) {
        324 var keys, values;
        325 if (map instanceof goog.structs.Map) {
        326 keys = map.getKeys();
        327 values = map.getValues();
        328 } else {
        329 keys = goog.object.getKeys(map);
        330 values = goog.object.getValues(map);
        331 }
        332 // we could use goog.array.forEach here but I don't want to introduce that
        333 // dependency just for this.
        334 for (var i = 0; i < keys.length; i++) {
        335 this.set(keys[i], values[i]);
        336 }
        337};
        338
        339
        340/**
        341 * Clones a map and returns a new map.
        342 * @return {!goog.structs.Map} A new map with the same key-value pairs.
        343 */
        344goog.structs.Map.prototype.clone = function() {
        345 return new goog.structs.Map(this);
        346};
        347
        348
        349/**
        350 * Returns a new map in which all the keys and values are interchanged
        351 * (keys become values and values become keys). If multiple keys map to the
        352 * same value, the chosen transposed value is implementation-dependent.
        353 *
        354 * It acts very similarly to {goog.object.transpose(Object)}.
        355 *
        356 * @return {!goog.structs.Map} The transposed map.
        357 */
        358goog.structs.Map.prototype.transpose = function() {
        359 var transposed = new goog.structs.Map();
        360 for (var i = 0; i < this.keys_.length; i++) {
        361 var key = this.keys_[i];
        362 var value = this.map_[key];
        363 transposed.set(value, key);
        364 }
        365
        366 return transposed;
        367};
        368
        369
        370/**
        371 * @return {!Object} Object representation of the map.
        372 */
        373goog.structs.Map.prototype.toObject = function() {
        374 this.cleanupKeysArray_();
        375 var obj = {};
        376 for (var i = 0; i < this.keys_.length; i++) {
        377 var key = this.keys_[i];
        378 obj[key] = this.map_[key];
        379 }
        380 return obj;
        381};
        382
        383
        384/**
        385 * Returns an iterator that iterates over the keys in the map. Removal of keys
        386 * while iterating might have undesired side effects.
        387 * @return {!goog.iter.Iterator} An iterator over the keys in the map.
        388 */
        389goog.structs.Map.prototype.getKeyIterator = function() {
        390 return this.__iterator__(true);
        391};
        392
        393
        394/**
        395 * Returns an iterator that iterates over the values in the map. Removal of
        396 * keys while iterating might have undesired side effects.
        397 * @return {!goog.iter.Iterator} An iterator over the values in the map.
        398 */
        399goog.structs.Map.prototype.getValueIterator = function() {
        400 return this.__iterator__(false);
        401};
        402
        403
        404/**
        405 * Returns an iterator that iterates over the values or the keys in the map.
        406 * This throws an exception if the map was mutated since the iterator was
        407 * created.
        408 * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
        409 * over the values. The default value is false.
        410 * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
        411 */
        412goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
        413 // Clean up keys to minimize the risk of iterating over dead keys.
        414 this.cleanupKeysArray_();
        415
        416 var i = 0;
        417 var keys = this.keys_;
        418 var map = this.map_;
        419 var version = this.version_;
        420 var selfObj = this;
        421
        422 var newIter = new goog.iter.Iterator;
        423 newIter.next = function() {
        424 while (true) {
        425 if (version != selfObj.version_) {
        426 throw Error('The map has changed since the iterator was created');
        427 }
        428 if (i >= keys.length) {
        429 throw goog.iter.StopIteration;
        430 }
        431 var key = keys[i++];
        432 return opt_keys ? key : map[key];
        433 }
        434 };
        435 return newIter;
        436};
        437
        438
        439/**
        440 * Safe way to test for hasOwnProperty. It even allows testing for
        441 * 'hasOwnProperty'.
        442 * @param {Object} obj The object to test for presence of the given key.
        443 * @param {*} key The key to check for.
        444 * @return {boolean} Whether the object has the key.
        445 * @private
        446 */
        447goog.structs.Map.hasKey_ = function(obj, key) {
        448 return Object.prototype.hasOwnProperty.call(obj, key);
        449};
        \ No newline at end of file +map.js

        lib/goog/structs/map.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Datastructure: Hash Map.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 *
        20 * This file contains an implementation of a Map structure. It implements a lot
        21 * of the methods used in goog.structs so those functions work on hashes. This
        22 * is best suited for complex key types. For simple keys such as numbers and
        23 * strings, and where special names like __proto__ are not a concern, consider
        24 * using the lighter-weight utilities in goog.object.
        25 */
        26
        27
        28goog.provide('goog.structs.Map');
        29
        30goog.require('goog.iter.Iterator');
        31goog.require('goog.iter.StopIteration');
        32goog.require('goog.object');
        33
        34
        35
        36/**
        37 * Class for Hash Map datastructure.
        38 * @param {*=} opt_map Map or Object to initialize the map with.
        39 * @param {...*} var_args If 2 or more arguments are present then they
        40 * will be used as key-value pairs.
        41 * @constructor
        42 * @template K, V
        43 */
        44goog.structs.Map = function(opt_map, var_args) {
        45
        46 /**
        47 * Underlying JS object used to implement the map.
        48 * @private {!Object}
        49 */
        50 this.map_ = {};
        51
        52 /**
        53 * An array of keys. This is necessary for two reasons:
        54 * 1. Iterating the keys using for (var key in this.map_) allocates an
        55 * object for every key in IE which is really bad for IE6 GC perf.
        56 * 2. Without a side data structure, we would need to escape all the keys
        57 * as that would be the only way we could tell during iteration if the
        58 * key was an internal key or a property of the object.
        59 *
        60 * This array can contain deleted keys so it's necessary to check the map
        61 * as well to see if the key is still in the map (this doesn't require a
        62 * memory allocation in IE).
        63 * @private {!Array<string>}
        64 */
        65 this.keys_ = [];
        66
        67 /**
        68 * The number of key value pairs in the map.
        69 * @private {number}
        70 */
        71 this.count_ = 0;
        72
        73 /**
        74 * Version used to detect changes while iterating.
        75 * @private {number}
        76 */
        77 this.version_ = 0;
        78
        79 var argLength = arguments.length;
        80
        81 if (argLength > 1) {
        82 if (argLength % 2) {
        83 throw Error('Uneven number of arguments');
        84 }
        85 for (var i = 0; i < argLength; i += 2) {
        86 this.set(arguments[i], arguments[i + 1]);
        87 }
        88 } else if (opt_map) {
        89 this.addAll(/** @type {Object} */ (opt_map));
        90 }
        91};
        92
        93
        94/**
        95 * @return {number} The number of key-value pairs in the map.
        96 */
        97goog.structs.Map.prototype.getCount = function() {
        98 return this.count_;
        99};
        100
        101
        102/**
        103 * Returns the values of the map.
        104 * @return {!Array<V>} The values in the map.
        105 */
        106goog.structs.Map.prototype.getValues = function() {
        107 this.cleanupKeysArray_();
        108
        109 var rv = [];
        110 for (var i = 0; i < this.keys_.length; i++) {
        111 var key = this.keys_[i];
        112 rv.push(this.map_[key]);
        113 }
        114 return rv;
        115};
        116
        117
        118/**
        119 * Returns the keys of the map.
        120 * @return {!Array<string>} Array of string values.
        121 */
        122goog.structs.Map.prototype.getKeys = function() {
        123 this.cleanupKeysArray_();
        124 return /** @type {!Array<string>} */ (this.keys_.concat());
        125};
        126
        127
        128/**
        129 * Whether the map contains the given key.
        130 * @param {*} key The key to check for.
        131 * @return {boolean} Whether the map contains the key.
        132 */
        133goog.structs.Map.prototype.containsKey = function(key) {
        134 return goog.structs.Map.hasKey_(this.map_, key);
        135};
        136
        137
        138/**
        139 * Whether the map contains the given value. This is O(n).
        140 * @param {V} val The value to check for.
        141 * @return {boolean} Whether the map contains the value.
        142 */
        143goog.structs.Map.prototype.containsValue = function(val) {
        144 for (var i = 0; i < this.keys_.length; i++) {
        145 var key = this.keys_[i];
        146 if (goog.structs.Map.hasKey_(this.map_, key) && this.map_[key] == val) {
        147 return true;
        148 }
        149 }
        150 return false;
        151};
        152
        153
        154/**
        155 * Whether this map is equal to the argument map.
        156 * @param {goog.structs.Map} otherMap The map against which to test equality.
        157 * @param {function(V, V): boolean=} opt_equalityFn Optional equality function
        158 * to test equality of values. If not specified, this will test whether
        159 * the values contained in each map are identical objects.
        160 * @return {boolean} Whether the maps are equal.
        161 */
        162goog.structs.Map.prototype.equals = function(otherMap, opt_equalityFn) {
        163 if (this === otherMap) {
        164 return true;
        165 }
        166
        167 if (this.count_ != otherMap.getCount()) {
        168 return false;
        169 }
        170
        171 var equalityFn = opt_equalityFn || goog.structs.Map.defaultEquals;
        172
        173 this.cleanupKeysArray_();
        174 for (var key, i = 0; key = this.keys_[i]; i++) {
        175 if (!equalityFn(this.get(key), otherMap.get(key))) {
        176 return false;
        177 }
        178 }
        179
        180 return true;
        181};
        182
        183
        184/**
        185 * Default equality test for values.
        186 * @param {*} a The first value.
        187 * @param {*} b The second value.
        188 * @return {boolean} Whether a and b reference the same object.
        189 */
        190goog.structs.Map.defaultEquals = function(a, b) {
        191 return a === b;
        192};
        193
        194
        195/**
        196 * @return {boolean} Whether the map is empty.
        197 */
        198goog.structs.Map.prototype.isEmpty = function() {
        199 return this.count_ == 0;
        200};
        201
        202
        203/**
        204 * Removes all key-value pairs from the map.
        205 */
        206goog.structs.Map.prototype.clear = function() {
        207 this.map_ = {};
        208 this.keys_.length = 0;
        209 this.count_ = 0;
        210 this.version_ = 0;
        211};
        212
        213
        214/**
        215 * Removes a key-value pair based on the key. This is O(logN) amortized due to
        216 * updating the keys array whenever the count becomes half the size of the keys
        217 * in the keys array.
        218 * @param {*} key The key to remove.
        219 * @return {boolean} Whether object was removed.
        220 */
        221goog.structs.Map.prototype.remove = function(key) {
        222 if (goog.structs.Map.hasKey_(this.map_, key)) {
        223 delete this.map_[key];
        224 this.count_--;
        225 this.version_++;
        226
        227 // clean up the keys array if the threshhold is hit
        228 if (this.keys_.length > 2 * this.count_) {
        229 this.cleanupKeysArray_();
        230 }
        231
        232 return true;
        233 }
        234 return false;
        235};
        236
        237
        238/**
        239 * Cleans up the temp keys array by removing entries that are no longer in the
        240 * map.
        241 * @private
        242 */
        243goog.structs.Map.prototype.cleanupKeysArray_ = function() {
        244 if (this.count_ != this.keys_.length) {
        245 // First remove keys that are no longer in the map.
        246 var srcIndex = 0;
        247 var destIndex = 0;
        248 while (srcIndex < this.keys_.length) {
        249 var key = this.keys_[srcIndex];
        250 if (goog.structs.Map.hasKey_(this.map_, key)) {
        251 this.keys_[destIndex++] = key;
        252 }
        253 srcIndex++;
        254 }
        255 this.keys_.length = destIndex;
        256 }
        257
        258 if (this.count_ != this.keys_.length) {
        259 // If the count still isn't correct, that means we have duplicates. This can
        260 // happen when the same key is added and removed multiple times. Now we have
        261 // to allocate one extra Object to remove the duplicates. This could have
        262 // been done in the first pass, but in the common case, we can avoid
        263 // allocating an extra object by only doing this when necessary.
        264 var seen = {};
        265 var srcIndex = 0;
        266 var destIndex = 0;
        267 while (srcIndex < this.keys_.length) {
        268 var key = this.keys_[srcIndex];
        269 if (!(goog.structs.Map.hasKey_(seen, key))) {
        270 this.keys_[destIndex++] = key;
        271 seen[key] = 1;
        272 }
        273 srcIndex++;
        274 }
        275 this.keys_.length = destIndex;
        276 }
        277};
        278
        279
        280/**
        281 * Returns the value for the given key. If the key is not found and the default
        282 * value is not given this will return {@code undefined}.
        283 * @param {*} key The key to get the value for.
        284 * @param {DEFAULT=} opt_val The value to return if no item is found for the
        285 * given key, defaults to undefined.
        286 * @return {V|DEFAULT} The value for the given key.
        287 * @template DEFAULT
        288 */
        289goog.structs.Map.prototype.get = function(key, opt_val) {
        290 if (goog.structs.Map.hasKey_(this.map_, key)) {
        291 return this.map_[key];
        292 }
        293 return opt_val;
        294};
        295
        296
        297/**
        298 * Adds a key-value pair to the map.
        299 * @param {*} key The key.
        300 * @param {V} value The value to add.
        301 * @return {*} Some subclasses return a value.
        302 */
        303goog.structs.Map.prototype.set = function(key, value) {
        304 if (!(goog.structs.Map.hasKey_(this.map_, key))) {
        305 this.count_++;
        306 this.keys_.push(key);
        307 // Only change the version if we add a new key.
        308 this.version_++;
        309 }
        310 this.map_[key] = value;
        311};
        312
        313
        314/**
        315 * Adds multiple key-value pairs from another goog.structs.Map or Object.
        316 * @param {Object} map Object containing the data to add.
        317 */
        318goog.structs.Map.prototype.addAll = function(map) {
        319 var keys, values;
        320 if (map instanceof goog.structs.Map) {
        321 keys = map.getKeys();
        322 values = map.getValues();
        323 } else {
        324 keys = goog.object.getKeys(map);
        325 values = goog.object.getValues(map);
        326 }
        327 // we could use goog.array.forEach here but I don't want to introduce that
        328 // dependency just for this.
        329 for (var i = 0; i < keys.length; i++) {
        330 this.set(keys[i], values[i]);
        331 }
        332};
        333
        334
        335/**
        336 * Calls the given function on each entry in the map.
        337 * @param {function(this:T, V, K, goog.structs.Map<K,V>)} f
        338 * @param {T=} opt_obj The value of "this" inside f.
        339 * @template T
        340 */
        341goog.structs.Map.prototype.forEach = function(f, opt_obj) {
        342 var keys = this.getKeys();
        343 for (var i = 0; i < keys.length; i++) {
        344 var key = keys[i];
        345 var value = this.get(key);
        346 f.call(opt_obj, value, key, this);
        347 }
        348};
        349
        350
        351/**
        352 * Clones a map and returns a new map.
        353 * @return {!goog.structs.Map} A new map with the same key-value pairs.
        354 */
        355goog.structs.Map.prototype.clone = function() {
        356 return new goog.structs.Map(this);
        357};
        358
        359
        360/**
        361 * Returns a new map in which all the keys and values are interchanged
        362 * (keys become values and values become keys). If multiple keys map to the
        363 * same value, the chosen transposed value is implementation-dependent.
        364 *
        365 * It acts very similarly to {goog.object.transpose(Object)}.
        366 *
        367 * @return {!goog.structs.Map} The transposed map.
        368 */
        369goog.structs.Map.prototype.transpose = function() {
        370 var transposed = new goog.structs.Map();
        371 for (var i = 0; i < this.keys_.length; i++) {
        372 var key = this.keys_[i];
        373 var value = this.map_[key];
        374 transposed.set(value, key);
        375 }
        376
        377 return transposed;
        378};
        379
        380
        381/**
        382 * @return {!Object} Object representation of the map.
        383 */
        384goog.structs.Map.prototype.toObject = function() {
        385 this.cleanupKeysArray_();
        386 var obj = {};
        387 for (var i = 0; i < this.keys_.length; i++) {
        388 var key = this.keys_[i];
        389 obj[key] = this.map_[key];
        390 }
        391 return obj;
        392};
        393
        394
        395/**
        396 * Returns an iterator that iterates over the keys in the map. Removal of keys
        397 * while iterating might have undesired side effects.
        398 * @return {!goog.iter.Iterator} An iterator over the keys in the map.
        399 */
        400goog.structs.Map.prototype.getKeyIterator = function() {
        401 return this.__iterator__(true);
        402};
        403
        404
        405/**
        406 * Returns an iterator that iterates over the values in the map. Removal of
        407 * keys while iterating might have undesired side effects.
        408 * @return {!goog.iter.Iterator} An iterator over the values in the map.
        409 */
        410goog.structs.Map.prototype.getValueIterator = function() {
        411 return this.__iterator__(false);
        412};
        413
        414
        415/**
        416 * Returns an iterator that iterates over the values or the keys in the map.
        417 * This throws an exception if the map was mutated since the iterator was
        418 * created.
        419 * @param {boolean=} opt_keys True to iterate over the keys. False to iterate
        420 * over the values. The default value is false.
        421 * @return {!goog.iter.Iterator} An iterator over the values or keys in the map.
        422 */
        423goog.structs.Map.prototype.__iterator__ = function(opt_keys) {
        424 // Clean up keys to minimize the risk of iterating over dead keys.
        425 this.cleanupKeysArray_();
        426
        427 var i = 0;
        428 var version = this.version_;
        429 var selfObj = this;
        430
        431 var newIter = new goog.iter.Iterator;
        432 newIter.next = function() {
        433 if (version != selfObj.version_) {
        434 throw Error('The map has changed since the iterator was created');
        435 }
        436 if (i >= selfObj.keys_.length) {
        437 throw goog.iter.StopIteration;
        438 }
        439 var key = selfObj.keys_[i++];
        440 return opt_keys ? key : selfObj.map_[key];
        441 };
        442 return newIter;
        443};
        444
        445
        446/**
        447 * Safe way to test for hasOwnProperty. It even allows testing for
        448 * 'hasOwnProperty'.
        449 * @param {Object} obj The object to test for presence of the given key.
        450 * @param {*} key The key to check for.
        451 * @return {boolean} Whether the object has the key.
        452 * @private
        453 */
        454goog.structs.Map.hasKey_ = function(obj, key) {
        455 return Object.prototype.hasOwnProperty.call(obj, key);
        456};
        \ No newline at end of file diff --git a/docs/source/lib/goog/structs/set.js.src.html b/docs/source/lib/goog/structs/set.js.src.html new file mode 100644 index 0000000..5c0c4e3 --- /dev/null +++ b/docs/source/lib/goog/structs/set.js.src.html @@ -0,0 +1 @@ +set.js

        lib/goog/structs/set.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Datastructure: Set.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 *
        20 * This class implements a set data structure. Adding and removing is O(1). It
        21 * supports both object and primitive values. Be careful because you can add
        22 * both 1 and new Number(1), because these are not the same. You can even add
        23 * multiple new Number(1) because these are not equal.
        24 */
        25
        26
        27goog.provide('goog.structs.Set');
        28
        29goog.require('goog.structs');
        30goog.require('goog.structs.Collection');
        31goog.require('goog.structs.Map');
        32
        33
        34
        35/**
        36 * A set that can contain both primitives and objects. Adding and removing
        37 * elements is O(1). Primitives are treated as identical if they have the same
        38 * type and convert to the same string. Objects are treated as identical only
        39 * if they are references to the same object. WARNING: A goog.structs.Set can
        40 * contain both 1 and (new Number(1)), because they are not the same. WARNING:
        41 * Adding (new Number(1)) twice will yield two distinct elements, because they
        42 * are two different objects. WARNING: Any object that is added to a
        43 * goog.structs.Set will be modified! Because goog.getUid() is used to
        44 * identify objects, every object in the set will be mutated.
        45 * @param {Array<T>|Object<?,T>=} opt_values Initial values to start with.
        46 * @constructor
        47 * @implements {goog.structs.Collection<T>}
        48 * @final
        49 * @template T
        50 */
        51goog.structs.Set = function(opt_values) {
        52 this.map_ = new goog.structs.Map;
        53 if (opt_values) {
        54 this.addAll(opt_values);
        55 }
        56};
        57
        58
        59/**
        60 * Obtains a unique key for an element of the set. Primitives will yield the
        61 * same key if they have the same type and convert to the same string. Object
        62 * references will yield the same key only if they refer to the same object.
        63 * @param {*} val Object or primitive value to get a key for.
        64 * @return {string} A unique key for this value/object.
        65 * @private
        66 */
        67goog.structs.Set.getKey_ = function(val) {
        68 var type = typeof val;
        69 if (type == 'object' && val || type == 'function') {
        70 return 'o' + goog.getUid(/** @type {Object} */ (val));
        71 } else {
        72 return type.substr(0, 1) + val;
        73 }
        74};
        75
        76
        77/**
        78 * @return {number} The number of elements in the set.
        79 * @override
        80 */
        81goog.structs.Set.prototype.getCount = function() {
        82 return this.map_.getCount();
        83};
        84
        85
        86/**
        87 * Add a primitive or an object to the set.
        88 * @param {T} element The primitive or object to add.
        89 * @override
        90 */
        91goog.structs.Set.prototype.add = function(element) {
        92 this.map_.set(goog.structs.Set.getKey_(element), element);
        93};
        94
        95
        96/**
        97 * Adds all the values in the given collection to this set.
        98 * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
        99 * containing the elements to add.
        100 */
        101goog.structs.Set.prototype.addAll = function(col) {
        102 var values = goog.structs.getValues(col);
        103 var l = values.length;
        104 for (var i = 0; i < l; i++) {
        105 this.add(values[i]);
        106 }
        107};
        108
        109
        110/**
        111 * Removes all values in the given collection from this set.
        112 * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection
        113 * containing the elements to remove.
        114 */
        115goog.structs.Set.prototype.removeAll = function(col) {
        116 var values = goog.structs.getValues(col);
        117 var l = values.length;
        118 for (var i = 0; i < l; i++) {
        119 this.remove(values[i]);
        120 }
        121};
        122
        123
        124/**
        125 * Removes the given element from this set.
        126 * @param {T} element The primitive or object to remove.
        127 * @return {boolean} Whether the element was found and removed.
        128 * @override
        129 */
        130goog.structs.Set.prototype.remove = function(element) {
        131 return this.map_.remove(goog.structs.Set.getKey_(element));
        132};
        133
        134
        135/**
        136 * Removes all elements from this set.
        137 */
        138goog.structs.Set.prototype.clear = function() {
        139 this.map_.clear();
        140};
        141
        142
        143/**
        144 * Tests whether this set is empty.
        145 * @return {boolean} True if there are no elements in this set.
        146 */
        147goog.structs.Set.prototype.isEmpty = function() {
        148 return this.map_.isEmpty();
        149};
        150
        151
        152/**
        153 * Tests whether this set contains the given element.
        154 * @param {T} element The primitive or object to test for.
        155 * @return {boolean} True if this set contains the given element.
        156 * @override
        157 */
        158goog.structs.Set.prototype.contains = function(element) {
        159 return this.map_.containsKey(goog.structs.Set.getKey_(element));
        160};
        161
        162
        163/**
        164 * Tests whether this set contains all the values in a given collection.
        165 * Repeated elements in the collection are ignored, e.g. (new
        166 * goog.structs.Set([1, 2])).containsAll([1, 1]) is True.
        167 * @param {goog.structs.Collection<T>|Object} col A collection-like object.
        168 * @return {boolean} True if the set contains all elements.
        169 */
        170goog.structs.Set.prototype.containsAll = function(col) {
        171 return goog.structs.every(col, this.contains, this);
        172};
        173
        174
        175/**
        176 * Finds all values that are present in both this set and the given collection.
        177 * @param {Array<S>|Object<?,S>} col A collection.
        178 * @return {!goog.structs.Set<T|S>} A new set containing all the values
        179 * (primitives or objects) present in both this set and the given
        180 * collection.
        181 * @template S
        182 */
        183goog.structs.Set.prototype.intersection = function(col) {
        184 var result = new goog.structs.Set();
        185
        186 var values = goog.structs.getValues(col);
        187 for (var i = 0; i < values.length; i++) {
        188 var value = values[i];
        189 if (this.contains(value)) {
        190 result.add(value);
        191 }
        192 }
        193
        194 return result;
        195};
        196
        197
        198/**
        199 * Finds all values that are present in this set and not in the given
        200 * collection.
        201 * @param {Array<T>|goog.structs.Collection<T>|Object<?,T>} col A collection.
        202 * @return {!goog.structs.Set} A new set containing all the values
        203 * (primitives or objects) present in this set but not in the given
        204 * collection.
        205 */
        206goog.structs.Set.prototype.difference = function(col) {
        207 var result = this.clone();
        208 result.removeAll(col);
        209 return result;
        210};
        211
        212
        213/**
        214 * Returns an array containing all the elements in this set.
        215 * @return {!Array<T>} An array containing all the elements in this set.
        216 */
        217goog.structs.Set.prototype.getValues = function() {
        218 return this.map_.getValues();
        219};
        220
        221
        222/**
        223 * Creates a shallow clone of this set.
        224 * @return {!goog.structs.Set<T>} A new set containing all the same elements as
        225 * this set.
        226 */
        227goog.structs.Set.prototype.clone = function() {
        228 return new goog.structs.Set(this);
        229};
        230
        231
        232/**
        233 * Tests whether the given collection consists of the same elements as this set,
        234 * regardless of order, without repetition. Primitives are treated as equal if
        235 * they have the same type and convert to the same string; objects are treated
        236 * as equal if they are references to the same object. This operation is O(n).
        237 * @param {goog.structs.Collection<T>|Object} col A collection.
        238 * @return {boolean} True if the given collection consists of the same elements
        239 * as this set, regardless of order, without repetition.
        240 */
        241goog.structs.Set.prototype.equals = function(col) {
        242 return this.getCount() == goog.structs.getCount(col) && this.isSubsetOf(col);
        243};
        244
        245
        246/**
        247 * Tests whether the given collection contains all the elements in this set.
        248 * Primitives are treated as equal if they have the same type and convert to the
        249 * same string; objects are treated as equal if they are references to the same
        250 * object. This operation is O(n).
        251 * @param {goog.structs.Collection<T>|Object} col A collection.
        252 * @return {boolean} True if this set is a subset of the given collection.
        253 */
        254goog.structs.Set.prototype.isSubsetOf = function(col) {
        255 var colCount = goog.structs.getCount(col);
        256 if (this.getCount() > colCount) {
        257 return false;
        258 }
        259 // TODO(user) Find the minimal collection size where the conversion makes
        260 // the contains() method faster.
        261 if (!(col instanceof goog.structs.Set) && colCount > 5) {
        262 // Convert to a goog.structs.Set so that goog.structs.contains runs in
        263 // O(1) time instead of O(n) time.
        264 col = new goog.structs.Set(col);
        265 }
        266 return goog.structs.every(this, function(value) {
        267 return goog.structs.contains(col, value);
        268 });
        269};
        270
        271
        272/**
        273 * Returns an iterator that iterates over the elements in this set.
        274 * @param {boolean=} opt_keys This argument is ignored.
        275 * @return {!goog.iter.Iterator} An iterator over the elements in this set.
        276 */
        277goog.structs.Set.prototype.__iterator__ = function(opt_keys) {
        278 return this.map_.__iterator__(false);
        279};
        \ No newline at end of file diff --git a/docs/source/lib/goog/structs/structs.js.src.html b/docs/source/lib/goog/structs/structs.js.src.html index bdc37d2..23c3d6e 100644 --- a/docs/source/lib/goog/structs/structs.js.src.html +++ b/docs/source/lib/goog/structs/structs.js.src.html @@ -1 +1 @@ -structs.js

        lib/goog/structs/structs.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Generics method for collection-like classes and objects.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 *
        20 * This file contains functions to work with collections. It supports using
        21 * Map, Set, Array and Object and other classes that implement collection-like
        22 * methods.
        23 */
        24
        25
        26goog.provide('goog.structs');
        27
        28goog.require('goog.array');
        29goog.require('goog.object');
        30
        31
        32// We treat an object as a dictionary if it has getKeys or it is an object that
        33// isn't arrayLike.
        34
        35
        36/**
        37 * Returns the number of values in the collection-like object.
        38 * @param {Object} col The collection-like object.
        39 * @return {number} The number of values in the collection-like object.
        40 */
        41goog.structs.getCount = function(col) {
        42 if (typeof col.getCount == 'function') {
        43 return col.getCount();
        44 }
        45 if (goog.isArrayLike(col) || goog.isString(col)) {
        46 return col.length;
        47 }
        48 return goog.object.getCount(col);
        49};
        50
        51
        52/**
        53 * Returns the values of the collection-like object.
        54 * @param {Object} col The collection-like object.
        55 * @return {!Array} The values in the collection-like object.
        56 */
        57goog.structs.getValues = function(col) {
        58 if (typeof col.getValues == 'function') {
        59 return col.getValues();
        60 }
        61 if (goog.isString(col)) {
        62 return col.split('');
        63 }
        64 if (goog.isArrayLike(col)) {
        65 var rv = [];
        66 var l = col.length;
        67 for (var i = 0; i < l; i++) {
        68 rv.push(col[i]);
        69 }
        70 return rv;
        71 }
        72 return goog.object.getValues(col);
        73};
        74
        75
        76/**
        77 * Returns the keys of the collection. Some collections have no notion of
        78 * keys/indexes and this function will return undefined in those cases.
        79 * @param {Object} col The collection-like object.
        80 * @return {!Array|undefined} The keys in the collection.
        81 */
        82goog.structs.getKeys = function(col) {
        83 if (typeof col.getKeys == 'function') {
        84 return col.getKeys();
        85 }
        86 // if we have getValues but no getKeys we know this is a key-less collection
        87 if (typeof col.getValues == 'function') {
        88 return undefined;
        89 }
        90 if (goog.isArrayLike(col) || goog.isString(col)) {
        91 var rv = [];
        92 var l = col.length;
        93 for (var i = 0; i < l; i++) {
        94 rv.push(i);
        95 }
        96 return rv;
        97 }
        98
        99 return goog.object.getKeys(col);
        100};
        101
        102
        103/**
        104 * Whether the collection contains the given value. This is O(n) and uses
        105 * equals (==) to test the existence.
        106 * @param {Object} col The collection-like object.
        107 * @param {*} val The value to check for.
        108 * @return {boolean} True if the map contains the value.
        109 */
        110goog.structs.contains = function(col, val) {
        111 if (typeof col.contains == 'function') {
        112 return col.contains(val);
        113 }
        114 if (typeof col.containsValue == 'function') {
        115 return col.containsValue(val);
        116 }
        117 if (goog.isArrayLike(col) || goog.isString(col)) {
        118 return goog.array.contains(/** @type {Array} */ (col), val);
        119 }
        120 return goog.object.containsValue(col, val);
        121};
        122
        123
        124/**
        125 * Whether the collection is empty.
        126 * @param {Object} col The collection-like object.
        127 * @return {boolean} True if empty.
        128 */
        129goog.structs.isEmpty = function(col) {
        130 if (typeof col.isEmpty == 'function') {
        131 return col.isEmpty();
        132 }
        133
        134 // We do not use goog.string.isEmpty because here we treat the string as
        135 // collection and as such even whitespace matters
        136
        137 if (goog.isArrayLike(col) || goog.isString(col)) {
        138 return goog.array.isEmpty(/** @type {Array} */ (col));
        139 }
        140 return goog.object.isEmpty(col);
        141};
        142
        143
        144/**
        145 * Removes all the elements from the collection.
        146 * @param {Object} col The collection-like object.
        147 */
        148goog.structs.clear = function(col) {
        149 // NOTE(arv): This should not contain strings because strings are immutable
        150 if (typeof col.clear == 'function') {
        151 col.clear();
        152 } else if (goog.isArrayLike(col)) {
        153 goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
        154 } else {
        155 goog.object.clear(col);
        156 }
        157};
        158
        159
        160/**
        161 * Calls a function for each value in a collection. The function takes
        162 * three arguments; the value, the key and the collection.
        163 *
        164 * @param {S} col The collection-like object.
        165 * @param {function(this:T,?,?,S):?} f The function to call for every value.
        166 * This function takes
        167 * 3 arguments (the value, the key or undefined if the collection has no
        168 * notion of keys, and the collection) and the return value is irrelevant.
        169 * @param {T=} opt_obj The object to be used as the value of 'this'
        170 * within {@code f}.
        171 * @template T,S
        172 */
        173goog.structs.forEach = function(col, f, opt_obj) {
        174 if (typeof col.forEach == 'function') {
        175 col.forEach(f, opt_obj);
        176 } else if (goog.isArrayLike(col) || goog.isString(col)) {
        177 goog.array.forEach(/** @type {Array} */ (col), f, opt_obj);
        178 } else {
        179 var keys = goog.structs.getKeys(col);
        180 var values = goog.structs.getValues(col);
        181 var l = values.length;
        182 for (var i = 0; i < l; i++) {
        183 f.call(opt_obj, values[i], keys && keys[i], col);
        184 }
        185 }
        186};
        187
        188
        189/**
        190 * Calls a function for every value in the collection. When a call returns true,
        191 * adds the value to a new collection (Array is returned by default).
        192 *
        193 * @param {S} col The collection-like object.
        194 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        195 * value. This function takes
        196 * 3 arguments (the value, the key or undefined if the collection has no
        197 * notion of keys, and the collection) and should return a Boolean. If the
        198 * return value is true the value is added to the result collection. If it
        199 * is false the value is not included.
        200 * @param {T=} opt_obj The object to be used as the value of 'this'
        201 * within {@code f}.
        202 * @return {!Object|!Array} A new collection where the passed values are
        203 * present. If col is a key-less collection an array is returned. If col
        204 * has keys and values a plain old JS object is returned.
        205 * @template T,S
        206 */
        207goog.structs.filter = function(col, f, opt_obj) {
        208 if (typeof col.filter == 'function') {
        209 return col.filter(f, opt_obj);
        210 }
        211 if (goog.isArrayLike(col) || goog.isString(col)) {
        212 return goog.array.filter(/** @type {!Array} */ (col), f, opt_obj);
        213 }
        214
        215 var rv;
        216 var keys = goog.structs.getKeys(col);
        217 var values = goog.structs.getValues(col);
        218 var l = values.length;
        219 if (keys) {
        220 rv = {};
        221 for (var i = 0; i < l; i++) {
        222 if (f.call(opt_obj, values[i], keys[i], col)) {
        223 rv[keys[i]] = values[i];
        224 }
        225 }
        226 } else {
        227 // We should not use goog.array.filter here since we want to make sure that
        228 // the index is undefined as well as make sure that col is passed to the
        229 // function.
        230 rv = [];
        231 for (var i = 0; i < l; i++) {
        232 if (f.call(opt_obj, values[i], undefined, col)) {
        233 rv.push(values[i]);
        234 }
        235 }
        236 }
        237 return rv;
        238};
        239
        240
        241/**
        242 * Calls a function for every value in the collection and adds the result into a
        243 * new collection (defaults to creating a new Array).
        244 *
        245 * @param {S} col The collection-like object.
        246 * @param {function(this:T,?,?,S):V} f The function to call for every value.
        247 * This function takes 3 arguments (the value, the key or undefined if the
        248 * collection has no notion of keys, and the collection) and should return
        249 * something. The result will be used as the value in the new collection.
        250 * @param {T=} opt_obj The object to be used as the value of 'this'
        251 * within {@code f}.
        252 * @return {!Object.<V>|!Array.<V>} A new collection with the new values. If
        253 * col is a key-less collection an array is returned. If col has keys and
        254 * values a plain old JS object is returned.
        255 * @template T,S,V
        256 */
        257goog.structs.map = function(col, f, opt_obj) {
        258 if (typeof col.map == 'function') {
        259 return col.map(f, opt_obj);
        260 }
        261 if (goog.isArrayLike(col) || goog.isString(col)) {
        262 return goog.array.map(/** @type {!Array} */ (col), f, opt_obj);
        263 }
        264
        265 var rv;
        266 var keys = goog.structs.getKeys(col);
        267 var values = goog.structs.getValues(col);
        268 var l = values.length;
        269 if (keys) {
        270 rv = {};
        271 for (var i = 0; i < l; i++) {
        272 rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
        273 }
        274 } else {
        275 // We should not use goog.array.map here since we want to make sure that
        276 // the index is undefined as well as make sure that col is passed to the
        277 // function.
        278 rv = [];
        279 for (var i = 0; i < l; i++) {
        280 rv[i] = f.call(opt_obj, values[i], undefined, col);
        281 }
        282 }
        283 return rv;
        284};
        285
        286
        287/**
        288 * Calls f for each value in a collection. If any call returns true this returns
        289 * true (without checking the rest). If all returns false this returns false.
        290 *
        291 * @param {S} col The collection-like object.
        292 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        293 * value. This function takes 3 arguments (the value, the key or undefined
        294 * if the collection has no notion of keys, and the collection) and should
        295 * return a boolean.
        296 * @param {T=} opt_obj The object to be used as the value of 'this'
        297 * within {@code f}.
        298 * @return {boolean} True if any value passes the test.
        299 * @template T,S
        300 */
        301goog.structs.some = function(col, f, opt_obj) {
        302 if (typeof col.some == 'function') {
        303 return col.some(f, opt_obj);
        304 }
        305 if (goog.isArrayLike(col) || goog.isString(col)) {
        306 return goog.array.some(/** @type {!Array} */ (col), f, opt_obj);
        307 }
        308 var keys = goog.structs.getKeys(col);
        309 var values = goog.structs.getValues(col);
        310 var l = values.length;
        311 for (var i = 0; i < l; i++) {
        312 if (f.call(opt_obj, values[i], keys && keys[i], col)) {
        313 return true;
        314 }
        315 }
        316 return false;
        317};
        318
        319
        320/**
        321 * Calls f for each value in a collection. If all calls return true this return
        322 * true this returns true. If any returns false this returns false at this point
        323 * and does not continue to check the remaining values.
        324 *
        325 * @param {S} col The collection-like object.
        326 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        327 * value. This function takes 3 arguments (the value, the key or
        328 * undefined if the collection has no notion of keys, and the collection)
        329 * and should return a boolean.
        330 * @param {T=} opt_obj The object to be used as the value of 'this'
        331 * within {@code f}.
        332 * @return {boolean} True if all key-value pairs pass the test.
        333 * @template T,S
        334 */
        335goog.structs.every = function(col, f, opt_obj) {
        336 if (typeof col.every == 'function') {
        337 return col.every(f, opt_obj);
        338 }
        339 if (goog.isArrayLike(col) || goog.isString(col)) {
        340 return goog.array.every(/** @type {!Array} */ (col), f, opt_obj);
        341 }
        342 var keys = goog.structs.getKeys(col);
        343 var values = goog.structs.getValues(col);
        344 var l = values.length;
        345 for (var i = 0; i < l; i++) {
        346 if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
        347 return false;
        348 }
        349 }
        350 return true;
        351};
        \ No newline at end of file +structs.js

        lib/goog/structs/structs.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Generics method for collection-like classes and objects.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 *
        20 * This file contains functions to work with collections. It supports using
        21 * Map, Set, Array and Object and other classes that implement collection-like
        22 * methods.
        23 */
        24
        25
        26goog.provide('goog.structs');
        27
        28goog.require('goog.array');
        29goog.require('goog.object');
        30
        31
        32// We treat an object as a dictionary if it has getKeys or it is an object that
        33// isn't arrayLike.
        34
        35
        36/**
        37 * Returns the number of values in the collection-like object.
        38 * @param {Object} col The collection-like object.
        39 * @return {number} The number of values in the collection-like object.
        40 */
        41goog.structs.getCount = function(col) {
        42 if (typeof col.getCount == 'function') {
        43 return col.getCount();
        44 }
        45 if (goog.isArrayLike(col) || goog.isString(col)) {
        46 return col.length;
        47 }
        48 return goog.object.getCount(col);
        49};
        50
        51
        52/**
        53 * Returns the values of the collection-like object.
        54 * @param {Object} col The collection-like object.
        55 * @return {!Array<?>} The values in the collection-like object.
        56 */
        57goog.structs.getValues = function(col) {
        58 if (typeof col.getValues == 'function') {
        59 return col.getValues();
        60 }
        61 if (goog.isString(col)) {
        62 return col.split('');
        63 }
        64 if (goog.isArrayLike(col)) {
        65 var rv = [];
        66 var l = col.length;
        67 for (var i = 0; i < l; i++) {
        68 rv.push(col[i]);
        69 }
        70 return rv;
        71 }
        72 return goog.object.getValues(col);
        73};
        74
        75
        76/**
        77 * Returns the keys of the collection. Some collections have no notion of
        78 * keys/indexes and this function will return undefined in those cases.
        79 * @param {Object} col The collection-like object.
        80 * @return {!Array|undefined} The keys in the collection.
        81 */
        82goog.structs.getKeys = function(col) {
        83 if (typeof col.getKeys == 'function') {
        84 return col.getKeys();
        85 }
        86 // if we have getValues but no getKeys we know this is a key-less collection
        87 if (typeof col.getValues == 'function') {
        88 return undefined;
        89 }
        90 if (goog.isArrayLike(col) || goog.isString(col)) {
        91 var rv = [];
        92 var l = col.length;
        93 for (var i = 0; i < l; i++) {
        94 rv.push(i);
        95 }
        96 return rv;
        97 }
        98
        99 return goog.object.getKeys(col);
        100};
        101
        102
        103/**
        104 * Whether the collection contains the given value. This is O(n) and uses
        105 * equals (==) to test the existence.
        106 * @param {Object} col The collection-like object.
        107 * @param {*} val The value to check for.
        108 * @return {boolean} True if the map contains the value.
        109 */
        110goog.structs.contains = function(col, val) {
        111 if (typeof col.contains == 'function') {
        112 return col.contains(val);
        113 }
        114 if (typeof col.containsValue == 'function') {
        115 return col.containsValue(val);
        116 }
        117 if (goog.isArrayLike(col) || goog.isString(col)) {
        118 return goog.array.contains(/** @type {!Array<?>} */ (col), val);
        119 }
        120 return goog.object.containsValue(col, val);
        121};
        122
        123
        124/**
        125 * Whether the collection is empty.
        126 * @param {Object} col The collection-like object.
        127 * @return {boolean} True if empty.
        128 */
        129goog.structs.isEmpty = function(col) {
        130 if (typeof col.isEmpty == 'function') {
        131 return col.isEmpty();
        132 }
        133
        134 // We do not use goog.string.isEmptyOrWhitespace because here we treat the string as
        135 // collection and as such even whitespace matters
        136
        137 if (goog.isArrayLike(col) || goog.isString(col)) {
        138 return goog.array.isEmpty(/** @type {!Array<?>} */ (col));
        139 }
        140 return goog.object.isEmpty(col);
        141};
        142
        143
        144/**
        145 * Removes all the elements from the collection.
        146 * @param {Object} col The collection-like object.
        147 */
        148goog.structs.clear = function(col) {
        149 // NOTE(arv): This should not contain strings because strings are immutable
        150 if (typeof col.clear == 'function') {
        151 col.clear();
        152 } else if (goog.isArrayLike(col)) {
        153 goog.array.clear(/** @type {goog.array.ArrayLike} */ (col));
        154 } else {
        155 goog.object.clear(col);
        156 }
        157};
        158
        159
        160/**
        161 * Calls a function for each value in a collection. The function takes
        162 * three arguments; the value, the key and the collection.
        163 *
        164 * NOTE: This will be deprecated soon! Please use a more specific method if
        165 * possible, e.g. goog.array.forEach, goog.object.forEach, etc.
        166 *
        167 * @param {S} col The collection-like object.
        168 * @param {function(this:T,?,?,S):?} f The function to call for every value.
        169 * This function takes
        170 * 3 arguments (the value, the key or undefined if the collection has no
        171 * notion of keys, and the collection) and the return value is irrelevant.
        172 * @param {T=} opt_obj The object to be used as the value of 'this'
        173 * within {@code f}.
        174 * @template T,S
        175 */
        176goog.structs.forEach = function(col, f, opt_obj) {
        177 if (typeof col.forEach == 'function') {
        178 col.forEach(f, opt_obj);
        179 } else if (goog.isArrayLike(col) || goog.isString(col)) {
        180 goog.array.forEach(/** @type {!Array<?>} */ (col), f, opt_obj);
        181 } else {
        182 var keys = goog.structs.getKeys(col);
        183 var values = goog.structs.getValues(col);
        184 var l = values.length;
        185 for (var i = 0; i < l; i++) {
        186 f.call(opt_obj, values[i], keys && keys[i], col);
        187 }
        188 }
        189};
        190
        191
        192/**
        193 * Calls a function for every value in the collection. When a call returns true,
        194 * adds the value to a new collection (Array is returned by default).
        195 *
        196 * @param {S} col The collection-like object.
        197 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        198 * value. This function takes
        199 * 3 arguments (the value, the key or undefined if the collection has no
        200 * notion of keys, and the collection) and should return a Boolean. If the
        201 * return value is true the value is added to the result collection. If it
        202 * is false the value is not included.
        203 * @param {T=} opt_obj The object to be used as the value of 'this'
        204 * within {@code f}.
        205 * @return {!Object|!Array<?>} A new collection where the passed values are
        206 * present. If col is a key-less collection an array is returned. If col
        207 * has keys and values a plain old JS object is returned.
        208 * @template T,S
        209 */
        210goog.structs.filter = function(col, f, opt_obj) {
        211 if (typeof col.filter == 'function') {
        212 return col.filter(f, opt_obj);
        213 }
        214 if (goog.isArrayLike(col) || goog.isString(col)) {
        215 return goog.array.filter(/** @type {!Array<?>} */ (col), f, opt_obj);
        216 }
        217
        218 var rv;
        219 var keys = goog.structs.getKeys(col);
        220 var values = goog.structs.getValues(col);
        221 var l = values.length;
        222 if (keys) {
        223 rv = {};
        224 for (var i = 0; i < l; i++) {
        225 if (f.call(opt_obj, values[i], keys[i], col)) {
        226 rv[keys[i]] = values[i];
        227 }
        228 }
        229 } else {
        230 // We should not use goog.array.filter here since we want to make sure that
        231 // the index is undefined as well as make sure that col is passed to the
        232 // function.
        233 rv = [];
        234 for (var i = 0; i < l; i++) {
        235 if (f.call(opt_obj, values[i], undefined, col)) {
        236 rv.push(values[i]);
        237 }
        238 }
        239 }
        240 return rv;
        241};
        242
        243
        244/**
        245 * Calls a function for every value in the collection and adds the result into a
        246 * new collection (defaults to creating a new Array).
        247 *
        248 * @param {S} col The collection-like object.
        249 * @param {function(this:T,?,?,S):V} f The function to call for every value.
        250 * This function takes 3 arguments (the value, the key or undefined if the
        251 * collection has no notion of keys, and the collection) and should return
        252 * something. The result will be used as the value in the new collection.
        253 * @param {T=} opt_obj The object to be used as the value of 'this'
        254 * within {@code f}.
        255 * @return {!Object<V>|!Array<V>} A new collection with the new values. If
        256 * col is a key-less collection an array is returned. If col has keys and
        257 * values a plain old JS object is returned.
        258 * @template T,S,V
        259 */
        260goog.structs.map = function(col, f, opt_obj) {
        261 if (typeof col.map == 'function') {
        262 return col.map(f, opt_obj);
        263 }
        264 if (goog.isArrayLike(col) || goog.isString(col)) {
        265 return goog.array.map(/** @type {!Array<?>} */ (col), f, opt_obj);
        266 }
        267
        268 var rv;
        269 var keys = goog.structs.getKeys(col);
        270 var values = goog.structs.getValues(col);
        271 var l = values.length;
        272 if (keys) {
        273 rv = {};
        274 for (var i = 0; i < l; i++) {
        275 rv[keys[i]] = f.call(opt_obj, values[i], keys[i], col);
        276 }
        277 } else {
        278 // We should not use goog.array.map here since we want to make sure that
        279 // the index is undefined as well as make sure that col is passed to the
        280 // function.
        281 rv = [];
        282 for (var i = 0; i < l; i++) {
        283 rv[i] = f.call(opt_obj, values[i], undefined, col);
        284 }
        285 }
        286 return rv;
        287};
        288
        289
        290/**
        291 * Calls f for each value in a collection. If any call returns true this returns
        292 * true (without checking the rest). If all returns false this returns false.
        293 *
        294 * @param {S} col The collection-like object.
        295 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        296 * value. This function takes 3 arguments (the value, the key or undefined
        297 * if the collection has no notion of keys, and the collection) and should
        298 * return a boolean.
        299 * @param {T=} opt_obj The object to be used as the value of 'this'
        300 * within {@code f}.
        301 * @return {boolean} True if any value passes the test.
        302 * @template T,S
        303 */
        304goog.structs.some = function(col, f, opt_obj) {
        305 if (typeof col.some == 'function') {
        306 return col.some(f, opt_obj);
        307 }
        308 if (goog.isArrayLike(col) || goog.isString(col)) {
        309 return goog.array.some(/** @type {!Array<?>} */ (col), f, opt_obj);
        310 }
        311 var keys = goog.structs.getKeys(col);
        312 var values = goog.structs.getValues(col);
        313 var l = values.length;
        314 for (var i = 0; i < l; i++) {
        315 if (f.call(opt_obj, values[i], keys && keys[i], col)) {
        316 return true;
        317 }
        318 }
        319 return false;
        320};
        321
        322
        323/**
        324 * Calls f for each value in a collection. If all calls return true this return
        325 * true this returns true. If any returns false this returns false at this point
        326 * and does not continue to check the remaining values.
        327 *
        328 * @param {S} col The collection-like object.
        329 * @param {function(this:T,?,?,S):boolean} f The function to call for every
        330 * value. This function takes 3 arguments (the value, the key or
        331 * undefined if the collection has no notion of keys, and the collection)
        332 * and should return a boolean.
        333 * @param {T=} opt_obj The object to be used as the value of 'this'
        334 * within {@code f}.
        335 * @return {boolean} True if all key-value pairs pass the test.
        336 * @template T,S
        337 */
        338goog.structs.every = function(col, f, opt_obj) {
        339 if (typeof col.every == 'function') {
        340 return col.every(f, opt_obj);
        341 }
        342 if (goog.isArrayLike(col) || goog.isString(col)) {
        343 return goog.array.every(/** @type {!Array<?>} */ (col), f, opt_obj);
        344 }
        345 var keys = goog.structs.getKeys(col);
        346 var values = goog.structs.getValues(col);
        347 var l = values.length;
        348 for (var i = 0; i < l; i++) {
        349 if (!f.call(opt_obj, values[i], keys && keys[i], col)) {
        350 return false;
        351 }
        352 }
        353 return true;
        354};
        \ No newline at end of file diff --git a/docs/source/lib/goog/style/style.js.src.html b/docs/source/lib/goog/style/style.js.src.html new file mode 100644 index 0000000..c55016d --- /dev/null +++ b/docs/source/lib/goog/style/style.js.src.html @@ -0,0 +1 @@ +style.js

        lib/goog/style/style.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for element styles.
        17 *
        18 * @author arv@google.com (Erik Arvidsson)
        19 * @author eae@google.com (Emil A Eklund)
        20 * @see ../demos/inline_block_quirks.html
        21 * @see ../demos/inline_block_standards.html
        22 * @see ../demos/style_viewport.html
        23 */
        24
        25goog.provide('goog.style');
        26
        27
        28goog.require('goog.array');
        29goog.require('goog.asserts');
        30goog.require('goog.dom');
        31goog.require('goog.dom.NodeType');
        32goog.require('goog.dom.TagName');
        33goog.require('goog.dom.vendor');
        34goog.require('goog.math.Box');
        35goog.require('goog.math.Coordinate');
        36goog.require('goog.math.Rect');
        37goog.require('goog.math.Size');
        38goog.require('goog.object');
        39goog.require('goog.string');
        40goog.require('goog.userAgent');
        41
        42/*
        43With the next two lines Selenium's //javascript/firefox-driver:command-processor
        44fails to compile with "ERROR - Malformed goog.forwardDeclaration".
        45TODO: Make it compile and restore next two lines to so we don't have to maintain the diff.
        46goog.forwardDeclare('goog.events.BrowserEvent');
        47goog.forwardDeclare('goog.events.Event');
        48*/
        49
        50/**
        51 * Sets a style value on an element.
        52 *
        53 * This function is not indended to patch issues in the browser's style
        54 * handling, but to allow easy programmatic access to setting dash-separated
        55 * style properties. An example is setting a batch of properties from a data
        56 * object without overwriting old styles. When possible, use native APIs:
        57 * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
        58 * elem.style.cssText = 'property1: value1; property2: value2'.
        59 *
        60 * @param {Element} element The element to change.
        61 * @param {string|Object} style If a string, a style name. If an object, a hash
        62 * of style names to style values.
        63 * @param {string|number|boolean=} opt_value If style was a string, then this
        64 * should be the value.
        65 */
        66goog.style.setStyle = function(element, style, opt_value) {
        67 if (goog.isString(style)) {
        68 goog.style.setStyle_(element, opt_value, style);
        69 } else {
        70 for (var key in style) {
        71 goog.style.setStyle_(element, style[key], key);
        72 }
        73 }
        74};
        75
        76
        77/**
        78 * Sets a style value on an element, with parameters swapped to work with
        79 * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
        80 * necessary.
        81 * @param {Element} element The element to change.
        82 * @param {string|number|boolean|undefined} value Style value.
        83 * @param {string} style Style name.
        84 * @private
        85 */
        86goog.style.setStyle_ = function(element, value, style) {
        87 var propertyName = goog.style.getVendorJsStyleName_(element, style);
        88
        89 if (propertyName) {
        90 element.style[propertyName] = value;
        91 }
        92};
        93
        94
        95/**
        96 * Style name cache that stores previous property name lookups.
        97 *
        98 * This is used by setStyle to speed up property lookups, entries look like:
        99 * { StyleName: ActualPropertyName }
        100 *
        101 * @private {!Object<string, string>}
        102 */
        103goog.style.styleNameCache_ = {};
        104
        105
        106/**
        107 * Returns the style property name in camel-case. If it does not exist and a
        108 * vendor-specific version of the property does exist, then return the vendor-
        109 * specific property name instead.
        110 * @param {Element} element The element to change.
        111 * @param {string} style Style name.
        112 * @return {string} Vendor-specific style.
        113 * @private
        114 */
        115goog.style.getVendorJsStyleName_ = function(element, style) {
        116 var propertyName = goog.style.styleNameCache_[style];
        117 if (!propertyName) {
        118 var camelStyle = goog.string.toCamelCase(style);
        119 propertyName = camelStyle;
        120
        121 if (element.style[camelStyle] === undefined) {
        122 var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
        123 goog.string.toTitleCase(camelStyle);
        124
        125 if (element.style[prefixedStyle] !== undefined) {
        126 propertyName = prefixedStyle;
        127 }
        128 }
        129 goog.style.styleNameCache_[style] = propertyName;
        130 }
        131
        132 return propertyName;
        133};
        134
        135
        136/**
        137 * Returns the style property name in CSS notation. If it does not exist and a
        138 * vendor-specific version of the property does exist, then return the vendor-
        139 * specific property name instead.
        140 * @param {Element} element The element to change.
        141 * @param {string} style Style name.
        142 * @return {string} Vendor-specific style.
        143 * @private
        144 */
        145goog.style.getVendorStyleName_ = function(element, style) {
        146 var camelStyle = goog.string.toCamelCase(style);
        147
        148 if (element.style[camelStyle] === undefined) {
        149 var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
        150 goog.string.toTitleCase(camelStyle);
        151
        152 if (element.style[prefixedStyle] !== undefined) {
        153 return goog.dom.vendor.getVendorPrefix() + '-' + style;
        154 }
        155 }
        156
        157 return style;
        158};
        159
        160
        161/**
        162 * Retrieves an explicitly-set style value of a node. This returns '' if there
        163 * isn't a style attribute on the element or if this style property has not been
        164 * explicitly set in script.
        165 *
        166 * @param {Element} element Element to get style of.
        167 * @param {string} property Property to get, css-style (if you have a camel-case
        168 * property, use element.style[style]).
        169 * @return {string} Style value.
        170 */
        171goog.style.getStyle = function(element, property) {
        172 // element.style is '' for well-known properties which are unset.
        173 // For for browser specific styles as 'filter' is undefined
        174 // so we need to return '' explicitly to make it consistent across
        175 // browsers.
        176 var styleValue = element.style[goog.string.toCamelCase(property)];
        177
        178 // Using typeof here because of a bug in Safari 5.1, where this value
        179 // was undefined, but === undefined returned false.
        180 if (typeof(styleValue) !== 'undefined') {
        181 return styleValue;
        182 }
        183
        184 return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
        185 '';
        186};
        187
        188
        189/**
        190 * Retrieves a computed style value of a node. It returns empty string if the
        191 * value cannot be computed (which will be the case in Internet Explorer) or
        192 * "none" if the property requested is an SVG one and it has not been
        193 * explicitly set (firefox and webkit).
        194 *
        195 * @param {Element} element Element to get style of.
        196 * @param {string} property Property to get (camel-case).
        197 * @return {string} Style value.
        198 */
        199goog.style.getComputedStyle = function(element, property) {
        200 var doc = goog.dom.getOwnerDocument(element);
        201 if (doc.defaultView && doc.defaultView.getComputedStyle) {
        202 var styles = doc.defaultView.getComputedStyle(element, null);
        203 if (styles) {
        204 // element.style[..] is undefined for browser specific styles
        205 // as 'filter'.
        206 return styles[property] || styles.getPropertyValue(property) || '';
        207 }
        208 }
        209
        210 return '';
        211};
        212
        213
        214/**
        215 * Gets the cascaded style value of a node, or null if the value cannot be
        216 * computed (only Internet Explorer can do this).
        217 *
        218 * @param {Element} element Element to get style of.
        219 * @param {string} style Property to get (camel-case).
        220 * @return {string} Style value.
        221 */
        222goog.style.getCascadedStyle = function(element, style) {
        223 // TODO(nicksantos): This should be documented to return null. #fixTypes
        224 return element.currentStyle ? element.currentStyle[style] : null;
        225};
        226
        227
        228/**
        229 * Cross-browser pseudo get computed style. It returns the computed style where
        230 * available. If not available it tries the cascaded style value (IE
        231 * currentStyle) and in worst case the inline style value. It shouldn't be
        232 * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
        233 * discussion.
        234 *
        235 * @param {Element} element Element to get style of.
        236 * @param {string} style Property to get (must be camelCase, not css-style.).
        237 * @return {string} Style value.
        238 * @private
        239 */
        240goog.style.getStyle_ = function(element, style) {
        241 return goog.style.getComputedStyle(element, style) ||
        242 goog.style.getCascadedStyle(element, style) ||
        243 (element.style && element.style[style]);
        244};
        245
        246
        247/**
        248 * Retrieves the computed value of the box-sizing CSS attribute.
        249 * Browser support: http://caniuse.com/css3-boxsizing.
        250 * @param {!Element} element The element whose box-sizing to get.
        251 * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
        252 * box-sizing is not supported (IE7 and below).
        253 */
        254goog.style.getComputedBoxSizing = function(element) {
        255 return goog.style.getStyle_(element, 'boxSizing') ||
        256 goog.style.getStyle_(element, 'MozBoxSizing') ||
        257 goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
        258};
        259
        260
        261/**
        262 * Retrieves the computed value of the position CSS attribute.
        263 * @param {Element} element The element to get the position of.
        264 * @return {string} Position value.
        265 */
        266goog.style.getComputedPosition = function(element) {
        267 return goog.style.getStyle_(element, 'position');
        268};
        269
        270
        271/**
        272 * Retrieves the computed background color string for a given element. The
        273 * string returned is suitable for assigning to another element's
        274 * background-color, but is not guaranteed to be in any particular string
        275 * format. Accessing the color in a numeric form may not be possible in all
        276 * browsers or with all input.
        277 *
        278 * If the background color for the element is defined as a hexadecimal value,
        279 * the resulting string can be parsed by goog.color.parse in all supported
        280 * browsers.
        281 *
        282 * Whether named colors like "red" or "lightblue" get translated into a
        283 * format which can be parsed is browser dependent. Calling this function on
        284 * transparent elements will return "transparent" in most browsers or
        285 * "rgba(0, 0, 0, 0)" in WebKit.
        286 * @param {Element} element The element to get the background color of.
        287 * @return {string} The computed string value of the background color.
        288 */
        289goog.style.getBackgroundColor = function(element) {
        290 return goog.style.getStyle_(element, 'backgroundColor');
        291};
        292
        293
        294/**
        295 * Retrieves the computed value of the overflow-x CSS attribute.
        296 * @param {Element} element The element to get the overflow-x of.
        297 * @return {string} The computed string value of the overflow-x attribute.
        298 */
        299goog.style.getComputedOverflowX = function(element) {
        300 return goog.style.getStyle_(element, 'overflowX');
        301};
        302
        303
        304/**
        305 * Retrieves the computed value of the overflow-y CSS attribute.
        306 * @param {Element} element The element to get the overflow-y of.
        307 * @return {string} The computed string value of the overflow-y attribute.
        308 */
        309goog.style.getComputedOverflowY = function(element) {
        310 return goog.style.getStyle_(element, 'overflowY');
        311};
        312
        313
        314/**
        315 * Retrieves the computed value of the z-index CSS attribute.
        316 * @param {Element} element The element to get the z-index of.
        317 * @return {string|number} The computed value of the z-index attribute.
        318 */
        319goog.style.getComputedZIndex = function(element) {
        320 return goog.style.getStyle_(element, 'zIndex');
        321};
        322
        323
        324/**
        325 * Retrieves the computed value of the text-align CSS attribute.
        326 * @param {Element} element The element to get the text-align of.
        327 * @return {string} The computed string value of the text-align attribute.
        328 */
        329goog.style.getComputedTextAlign = function(element) {
        330 return goog.style.getStyle_(element, 'textAlign');
        331};
        332
        333
        334/**
        335 * Retrieves the computed value of the cursor CSS attribute.
        336 * @param {Element} element The element to get the cursor of.
        337 * @return {string} The computed string value of the cursor attribute.
        338 */
        339goog.style.getComputedCursor = function(element) {
        340 return goog.style.getStyle_(element, 'cursor');
        341};
        342
        343
        344/**
        345 * Retrieves the computed value of the CSS transform attribute.
        346 * @param {Element} element The element to get the transform of.
        347 * @return {string} The computed string representation of the transform matrix.
        348 */
        349goog.style.getComputedTransform = function(element) {
        350 var property = goog.style.getVendorStyleName_(element, 'transform');
        351 return goog.style.getStyle_(element, property) ||
        352 goog.style.getStyle_(element, 'transform');
        353};
        354
        355
        356/**
        357 * Sets the top/left values of an element. If no unit is specified in the
        358 * argument then it will add px. The second argument is required if the first
        359 * argument is a string or number and is ignored if the first argument
        360 * is a coordinate.
        361 * @param {Element} el Element to move.
        362 * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
        363 * @param {string|number=} opt_arg2 Top position.
        364 */
        365goog.style.setPosition = function(el, arg1, opt_arg2) {
        366 var x, y;
        367
        368 if (arg1 instanceof goog.math.Coordinate) {
        369 x = arg1.x;
        370 y = arg1.y;
        371 } else {
        372 x = arg1;
        373 y = opt_arg2;
        374 }
        375
        376 el.style.left = goog.style.getPixelStyleValue_(
        377 /** @type {number|string} */ (x), false);
        378 el.style.top = goog.style.getPixelStyleValue_(
        379 /** @type {number|string} */ (y), false);
        380};
        381
        382
        383/**
        384 * Gets the offsetLeft and offsetTop properties of an element and returns them
        385 * in a Coordinate object
        386 * @param {Element} element Element.
        387 * @return {!goog.math.Coordinate} The position.
        388 */
        389goog.style.getPosition = function(element) {
        390 return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
        391};
        392
        393
        394/**
        395 * Returns the viewport element for a particular document
        396 * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
        397 * of.
        398 * @return {Element} document.documentElement or document.body.
        399 */
        400goog.style.getClientViewportElement = function(opt_node) {
        401 var doc;
        402 if (opt_node) {
        403 doc = goog.dom.getOwnerDocument(opt_node);
        404 } else {
        405 doc = goog.dom.getDocument();
        406 }
        407
        408 // In old IE versions the document.body represented the viewport
        409 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
        410 !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
        411 return doc.body;
        412 }
        413 return doc.documentElement;
        414};
        415
        416
        417/**
        418 * Calculates the viewport coordinates relative to the page/document
        419 * containing the node. The viewport may be the browser viewport for
        420 * non-iframe document, or the iframe container for iframe'd document.
        421 * @param {!Document} doc The document to use as the reference point.
        422 * @return {!goog.math.Coordinate} The page offset of the viewport.
        423 */
        424goog.style.getViewportPageOffset = function(doc) {
        425 var body = doc.body;
        426 var documentElement = doc.documentElement;
        427 var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
        428 var scrollTop = body.scrollTop || documentElement.scrollTop;
        429 return new goog.math.Coordinate(scrollLeft, scrollTop);
        430};
        431
        432
        433/**
        434 * Gets the client rectangle of the DOM element.
        435 *
        436 * getBoundingClientRect is part of a new CSS object model draft (with a
        437 * long-time presence in IE), replacing the error-prone parent offset
        438 * computation and the now-deprecated Gecko getBoxObjectFor.
        439 *
        440 * This utility patches common browser bugs in getBoundingClientRect. It
        441 * will fail if getBoundingClientRect is unsupported.
        442 *
        443 * If the element is not in the DOM, the result is undefined, and an error may
        444 * be thrown depending on user agent.
        445 *
        446 * @param {!Element} el The element whose bounding rectangle is being queried.
        447 * @return {Object} A native bounding rectangle with numerical left, top,
        448 * right, and bottom. Reported by Firefox to be of object type ClientRect.
        449 * @private
        450 */
        451goog.style.getBoundingClientRect_ = function(el) {
        452 var rect;
        453 try {
        454 rect = el.getBoundingClientRect();
        455 } catch (e) {
        456 // In IE < 9, calling getBoundingClientRect on an orphan element raises an
        457 // "Unspecified Error". All other browsers return zeros.
        458 return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
        459 }
        460
        461 // Patch the result in IE only, so that this function can be inlined if
        462 // compiled for non-IE.
        463 if (goog.userAgent.IE && el.ownerDocument.body) {
        464
        465 // In IE, most of the time, 2 extra pixels are added to the top and left
        466 // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
        467 // IE6 standards mode, this border can be overridden by setting the
        468 // document element's border to zero -- thus, we cannot rely on the
        469 // offset always being 2 pixels.
        470
        471 // In quirks mode, the offset can be determined by querying the body's
        472 // clientLeft/clientTop, but in standards mode, it is found by querying
        473 // the document element's clientLeft/clientTop. Since we already called
        474 // getBoundingClientRect we have already forced a reflow, so it is not
        475 // too expensive just to query them all.
        476
        477 // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
        478 var doc = el.ownerDocument;
        479 rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
        480 rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
        481 }
        482 return /** @type {Object} */ (rect);
        483};
        484
        485
        486/**
        487 * Returns the first parent that could affect the position of a given element.
        488 * @param {Element} element The element to get the offset parent for.
        489 * @return {Element} The first offset parent or null if one cannot be found.
        490 */
        491goog.style.getOffsetParent = function(element) {
        492 // element.offsetParent does the right thing in IE7 and below. In other
        493 // browsers it only includes elements with position absolute, relative or
        494 // fixed, not elements with overflow set to auto or scroll.
        495 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
        496 return element.offsetParent;
        497 }
        498
        499 var doc = goog.dom.getOwnerDocument(element);
        500 var positionStyle = goog.style.getStyle_(element, 'position');
        501 var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
        502 for (var parent = element.parentNode; parent && parent != doc;
        503 parent = parent.parentNode) {
        504 // Skip shadowDOM roots.
        505 if (parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT &&
        506 parent.host) {
        507 parent = parent.host;
        508 }
        509 positionStyle =
        510 goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
        511 skipStatic = skipStatic && positionStyle == 'static' &&
        512 parent != doc.documentElement && parent != doc.body;
        513 if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
        514 parent.scrollHeight > parent.clientHeight ||
        515 positionStyle == 'fixed' ||
        516 positionStyle == 'absolute' ||
        517 positionStyle == 'relative')) {
        518 return /** @type {!Element} */ (parent);
        519 }
        520 }
        521 return null;
        522};
        523
        524
        525/**
        526 * Calculates and returns the visible rectangle for a given element. Returns a
        527 * box describing the visible portion of the nearest scrollable offset ancestor.
        528 * Coordinates are given relative to the document.
        529 *
        530 * @param {Element} element Element to get the visible rect for.
        531 * @return {goog.math.Box} Bounding elementBox describing the visible rect or
        532 * null if scrollable ancestor isn't inside the visible viewport.
        533 */
        534goog.style.getVisibleRectForElement = function(element) {
        535 var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
        536 var dom = goog.dom.getDomHelper(element);
        537 var body = dom.getDocument().body;
        538 var documentElement = dom.getDocument().documentElement;
        539 var scrollEl = dom.getDocumentScrollElement();
        540
        541 // Determine the size of the visible rect by climbing the dom accounting for
        542 // all scrollable containers.
        543 for (var el = element; el = goog.style.getOffsetParent(el); ) {
        544 // clientWidth is zero for inline block elements in IE.
        545 // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
        546 if ((!goog.userAgent.IE || el.clientWidth != 0) &&
        547 (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
        548 // body may have overflow set on it, yet we still get the entire
        549 // viewport. In some browsers, el.offsetParent may be
        550 // document.documentElement, so check for that too.
        551 (el != body && el != documentElement &&
        552 goog.style.getStyle_(el, 'overflow') != 'visible')) {
        553 var pos = goog.style.getPageOffset(el);
        554 var client = goog.style.getClientLeftTop(el);
        555 pos.x += client.x;
        556 pos.y += client.y;
        557
        558 visibleRect.top = Math.max(visibleRect.top, pos.y);
        559 visibleRect.right = Math.min(visibleRect.right,
        560 pos.x + el.clientWidth);
        561 visibleRect.bottom = Math.min(visibleRect.bottom,
        562 pos.y + el.clientHeight);
        563 visibleRect.left = Math.max(visibleRect.left, pos.x);
        564 }
        565 }
        566
        567 // Clip by window's viewport.
        568 var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
        569 visibleRect.left = Math.max(visibleRect.left, scrollX);
        570 visibleRect.top = Math.max(visibleRect.top, scrollY);
        571 var winSize = dom.getViewportSize();
        572 visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
        573 visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
        574 return visibleRect.top >= 0 && visibleRect.left >= 0 &&
        575 visibleRect.bottom > visibleRect.top &&
        576 visibleRect.right > visibleRect.left ?
        577 visibleRect : null;
        578};
        579
        580
        581/**
        582 * Calculate the scroll position of {@code container} with the minimum amount so
        583 * that the content and the borders of the given {@code element} become visible.
        584 * If the element is bigger than the container, its top left corner will be
        585 * aligned as close to the container's top left corner as possible.
        586 *
        587 * @param {Element} element The element to make visible.
        588 * @param {Element} container The container to scroll.
        589 * @param {boolean=} opt_center Whether to center the element in the container.
        590 * Defaults to false.
        591 * @return {!goog.math.Coordinate} The new scroll position of the container,
        592 * in form of goog.math.Coordinate(scrollLeft, scrollTop).
        593 */
        594goog.style.getContainerOffsetToScrollInto =
        595 function(element, container, opt_center) {
        596 // Absolute position of the element's border's top left corner.
        597 var elementPos = goog.style.getPageOffset(element);
        598 // Absolute position of the container's border's top left corner.
        599 var containerPos = goog.style.getPageOffset(container);
        600 var containerBorder = goog.style.getBorderBox(container);
        601 // Relative pos. of the element's border box to the container's content box.
        602 var relX = elementPos.x - containerPos.x - containerBorder.left;
        603 var relY = elementPos.y - containerPos.y - containerBorder.top;
        604 // How much the element can move in the container, i.e. the difference between
        605 // the element's bottom-right-most and top-left-most position where it's
        606 // fully visible.
        607 var spaceX = container.clientWidth - element.offsetWidth;
        608 var spaceY = container.clientHeight - element.offsetHeight;
        609
        610 var scrollLeft = container.scrollLeft;
        611 var scrollTop = container.scrollTop;
        612 if (container == goog.dom.getDocument().body ||
        613 container == goog.dom.getDocument().documentElement) {
        614 // If the container is the document scroll element (usually <body>),
        615 // getPageOffset(element) is already relative to it and there is no need to
        616 // consider the current scroll.
        617 scrollLeft = containerPos.x + containerBorder.left;
        618 scrollTop = containerPos.y + containerBorder.top;
        619
        620 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) {
        621 // In older versions of IE getPageOffset(element) does not include the
        622 // continaer border so it has to be added to accomodate.
        623 scrollLeft += containerBorder.left;
        624 scrollTop += containerBorder.top;
        625 }
        626 }
        627 if (opt_center) {
        628 // All browsers round non-integer scroll positions down.
        629 scrollLeft += relX - spaceX / 2;
        630 scrollTop += relY - spaceY / 2;
        631 } else {
        632 // This formula was designed to give the correct scroll values in the
        633 // following cases:
        634 // - element is higher than container (spaceY < 0) => scroll down by relY
        635 // - element is not higher that container (spaceY >= 0):
        636 // - it is above container (relY < 0) => scroll up by abs(relY)
        637 // - it is below container (relY > spaceY) => scroll down by relY - spaceY
        638 // - it is in the container => don't scroll
        639 scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
        640 scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
        641 }
        642 return new goog.math.Coordinate(scrollLeft, scrollTop);
        643};
        644
        645
        646/**
        647 * Changes the scroll position of {@code container} with the minimum amount so
        648 * that the content and the borders of the given {@code element} become visible.
        649 * If the element is bigger than the container, its top left corner will be
        650 * aligned as close to the container's top left corner as possible.
        651 *
        652 * @param {Element} element The element to make visible.
        653 * @param {Element} container The container to scroll.
        654 * @param {boolean=} opt_center Whether to center the element in the container.
        655 * Defaults to false.
        656 */
        657goog.style.scrollIntoContainerView = function(element, container, opt_center) {
        658 var offset =
        659 goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
        660 container.scrollLeft = offset.x;
        661 container.scrollTop = offset.y;
        662};
        663
        664
        665/**
        666 * Returns clientLeft (width of the left border and, if the directionality is
        667 * right to left, the vertical scrollbar) and clientTop as a coordinate object.
        668 *
        669 * @param {Element} el Element to get clientLeft for.
        670 * @return {!goog.math.Coordinate} Client left and top.
        671 */
        672goog.style.getClientLeftTop = function(el) {
        673 return new goog.math.Coordinate(el.clientLeft, el.clientTop);
        674};
        675
        676
        677/**
        678 * Returns a Coordinate object relative to the top-left of the HTML document.
        679 * Implemented as a single function to save having to do two recursive loops in
        680 * opera and safari just to get both coordinates. If you just want one value do
        681 * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
        682 * note if you call both those methods the tree will be analysed twice.
        683 *
        684 * @param {Element} el Element to get the page offset for.
        685 * @return {!goog.math.Coordinate} The page offset.
        686 */
        687goog.style.getPageOffset = function(el) {
        688 var doc = goog.dom.getOwnerDocument(el);
        689 // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
        690 goog.asserts.assertObject(el, 'Parameter is required');
        691
        692 // NOTE(arv): If element is hidden (display none or disconnected or any the
        693 // ancestors are hidden) we get (0,0) by default but we still do the
        694 // accumulation of scroll position.
        695
        696 // TODO(arv): Should we check if the node is disconnected and in that case
        697 // return (0,0)?
        698
        699 var pos = new goog.math.Coordinate(0, 0);
        700 var viewportElement = goog.style.getClientViewportElement(doc);
        701 if (el == viewportElement) {
        702 // viewport is always at 0,0 as that defined the coordinate system for this
        703 // function - this avoids special case checks in the code below
        704 return pos;
        705 }
        706
        707 var box = goog.style.getBoundingClientRect_(el);
        708 // Must add the scroll coordinates in to get the absolute page offset
        709 // of element since getBoundingClientRect returns relative coordinates to
        710 // the viewport.
        711 var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
        712 pos.x = box.left + scrollCoord.x;
        713 pos.y = box.top + scrollCoord.y;
        714
        715 return pos;
        716};
        717
        718
        719/**
        720 * Returns the left coordinate of an element relative to the HTML document
        721 * @param {Element} el Elements.
        722 * @return {number} The left coordinate.
        723 */
        724goog.style.getPageOffsetLeft = function(el) {
        725 return goog.style.getPageOffset(el).x;
        726};
        727
        728
        729/**
        730 * Returns the top coordinate of an element relative to the HTML document
        731 * @param {Element} el Elements.
        732 * @return {number} The top coordinate.
        733 */
        734goog.style.getPageOffsetTop = function(el) {
        735 return goog.style.getPageOffset(el).y;
        736};
        737
        738
        739/**
        740 * Returns a Coordinate object relative to the top-left of an HTML document
        741 * in an ancestor frame of this element. Used for measuring the position of
        742 * an element inside a frame relative to a containing frame.
        743 *
        744 * @param {Element} el Element to get the page offset for.
        745 * @param {Window} relativeWin The window to measure relative to. If relativeWin
        746 * is not in the ancestor frame chain of the element, we measure relative to
        747 * the top-most window.
        748 * @return {!goog.math.Coordinate} The page offset.
        749 */
        750goog.style.getFramedPageOffset = function(el, relativeWin) {
        751 var position = new goog.math.Coordinate(0, 0);
        752
        753 // Iterate up the ancestor frame chain, keeping track of the current window
        754 // and the current element in that window.
        755 var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
        756 var currentEl = el;
        757 do {
        758 // if we're at the top window, we want to get the page offset.
        759 // if we're at an inner frame, we only want to get the window position
        760 // so that we can determine the actual page offset in the context of
        761 // the outer window.
        762 var offset = currentWin == relativeWin ?
        763 goog.style.getPageOffset(currentEl) :
        764 goog.style.getClientPositionForElement_(
        765 goog.asserts.assert(currentEl));
        766
        767 position.x += offset.x;
        768 position.y += offset.y;
        769 } while (currentWin && currentWin != relativeWin &&
        770 currentWin != currentWin.parent &&
        771 (currentEl = currentWin.frameElement) &&
        772 (currentWin = currentWin.parent));
        773
        774 return position;
        775};
        776
        777
        778/**
        779 * Translates the specified rect relative to origBase page, for newBase page.
        780 * If origBase and newBase are the same, this function does nothing.
        781 *
        782 * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
        783 * and it will have the translated result.
        784 * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
        785 * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
        786 * coordinate. This must be a DOM for an ancestor frame of origBase
        787 * or the same as origBase.
        788 */
        789goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
        790 if (origBase.getDocument() != newBase.getDocument()) {
        791 var body = origBase.getDocument().body;
        792 var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
        793
        794 // Adjust Body's margin.
        795 pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
        796
        797 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
        798 !origBase.isCss1CompatMode()) {
        799 pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
        800 }
        801
        802 rect.left += pos.x;
        803 rect.top += pos.y;
        804 }
        805};
        806
        807
        808/**
        809 * Returns the position of an element relative to another element in the
        810 * document. A relative to B
        811 * @param {Element|Event|goog.events.Event} a Element or mouse event whose
        812 * position we're calculating.
        813 * @param {Element|Event|goog.events.Event} b Element or mouse event position
        814 * is relative to.
        815 * @return {!goog.math.Coordinate} The relative position.
        816 */
        817goog.style.getRelativePosition = function(a, b) {
        818 var ap = goog.style.getClientPosition(a);
        819 var bp = goog.style.getClientPosition(b);
        820 return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
        821};
        822
        823
        824/**
        825 * Returns the position of the event or the element's border box relative to
        826 * the client viewport.
        827 * @param {!Element} el Element whose position to get.
        828 * @return {!goog.math.Coordinate} The position.
        829 * @private
        830 */
        831goog.style.getClientPositionForElement_ = function(el) {
        832 var box = goog.style.getBoundingClientRect_(el);
        833 return new goog.math.Coordinate(box.left, box.top);
        834};
        835
        836
        837/**
        838 * Returns the position of the event or the element's border box relative to
        839 * the client viewport.
        840 * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
        841 * @return {!goog.math.Coordinate} The position.
        842 */
        843goog.style.getClientPosition = function(el) {
        844 goog.asserts.assert(el);
        845 if (el.nodeType == goog.dom.NodeType.ELEMENT) {
        846 return goog.style.getClientPositionForElement_(
        847 /** @type {!Element} */ (el));
        848 } else {
        849 var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
        850 var be = /** @type {!goog.events.BrowserEvent} */ (el);
        851 var targetEvent = el;
        852
        853 if (el.targetTouches && el.targetTouches.length) {
        854 targetEvent = el.targetTouches[0];
        855 } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches &&
        856 be.getBrowserEvent().targetTouches.length) {
        857 targetEvent = be.getBrowserEvent().targetTouches[0];
        858 }
        859
        860 return new goog.math.Coordinate(
        861 targetEvent.clientX,
        862 targetEvent.clientY);
        863 }
        864};
        865
        866
        867/**
        868 * Moves an element to the given coordinates relative to the client viewport.
        869 * @param {Element} el Absolutely positioned element to set page offset for.
        870 * It must be in the document.
        871 * @param {number|goog.math.Coordinate} x Left position of the element's margin
        872 * box or a coordinate object.
        873 * @param {number=} opt_y Top position of the element's margin box.
        874 */
        875goog.style.setPageOffset = function(el, x, opt_y) {
        876 // Get current pageoffset
        877 var cur = goog.style.getPageOffset(el);
        878
        879 if (x instanceof goog.math.Coordinate) {
        880 opt_y = x.y;
        881 x = x.x;
        882 }
        883
        884 // NOTE(arv): We cannot allow strings for x and y. We could but that would
        885 // require us to manually transform between different units
        886
        887 // Work out deltas
        888 var dx = x - cur.x;
        889 var dy = opt_y - cur.y;
        890
        891 // Set position to current left/top + delta
        892 goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
        893};
        894
        895
        896/**
        897 * Sets the width/height values of an element. If an argument is numeric,
        898 * or a goog.math.Size is passed, it is assumed to be pixels and will add
        899 * 'px' after converting it to an integer in string form. (This just sets the
        900 * CSS width and height properties so it might set content-box or border-box
        901 * size depending on the box model the browser is using.)
        902 *
        903 * @param {Element} element Element to set the size of.
        904 * @param {string|number|goog.math.Size} w Width of the element, or a
        905 * size object.
        906 * @param {string|number=} opt_h Height of the element. Required if w is not a
        907 * size object.
        908 */
        909goog.style.setSize = function(element, w, opt_h) {
        910 var h;
        911 if (w instanceof goog.math.Size) {
        912 h = w.height;
        913 w = w.width;
        914 } else {
        915 if (opt_h == undefined) {
        916 throw Error('missing height argument');
        917 }
        918 h = opt_h;
        919 }
        920
        921 goog.style.setWidth(element, /** @type {string|number} */ (w));
        922 goog.style.setHeight(element, /** @type {string|number} */ (h));
        923};
        924
        925
        926/**
        927 * Helper function to create a string to be set into a pixel-value style
        928 * property of an element. Can round to the nearest integer value.
        929 *
        930 * @param {string|number} value The style value to be used. If a number,
        931 * 'px' will be appended, otherwise the value will be applied directly.
        932 * @param {boolean} round Whether to round the nearest integer (if property
        933 * is a number).
        934 * @return {string} The string value for the property.
        935 * @private
        936 */
        937goog.style.getPixelStyleValue_ = function(value, round) {
        938 if (typeof value == 'number') {
        939 value = (round ? Math.round(value) : value) + 'px';
        940 }
        941
        942 return value;
        943};
        944
        945
        946/**
        947 * Set the height of an element. Sets the element's style property.
        948 * @param {Element} element Element to set the height of.
        949 * @param {string|number} height The height value to set. If a number, 'px'
        950 * will be appended, otherwise the value will be applied directly.
        951 */
        952goog.style.setHeight = function(element, height) {
        953 element.style.height = goog.style.getPixelStyleValue_(height, true);
        954};
        955
        956
        957/**
        958 * Set the width of an element. Sets the element's style property.
        959 * @param {Element} element Element to set the width of.
        960 * @param {string|number} width The width value to set. If a number, 'px'
        961 * will be appended, otherwise the value will be applied directly.
        962 */
        963goog.style.setWidth = function(element, width) {
        964 element.style.width = goog.style.getPixelStyleValue_(width, true);
        965};
        966
        967
        968/**
        969 * Gets the height and width of an element, even if its display is none.
        970 *
        971 * Specifically, this returns the height and width of the border box,
        972 * irrespective of the box model in effect.
        973 *
        974 * Note that this function does not take CSS transforms into account. Please see
        975 * {@code goog.style.getTransformedSize}.
        976 * @param {Element} element Element to get size of.
        977 * @return {!goog.math.Size} Object with width/height properties.
        978 */
        979goog.style.getSize = function(element) {
        980 return goog.style.evaluateWithTemporaryDisplay_(
        981 goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
        982};
        983
        984
        985/**
        986 * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
        987 * accurate when it's passed to {@code fn}.
        988 * @param {function(!Element): T} fn Function to call with {@code element} as
        989 * an argument after temporarily changing {@code element}'s display such
        990 * that its dimensions are accurate.
        991 * @param {!Element} element Element (which may have display none) to use as
        992 * argument to {@code fn}.
        993 * @return {T} Value returned by calling {@code fn} with {@code element}.
        994 * @template T
        995 * @private
        996 */
        997goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
        998 if (goog.style.getStyle_(element, 'display') != 'none') {
        999 return fn(element);
        1000 }
        1001
        1002 var style = element.style;
        1003 var originalDisplay = style.display;
        1004 var originalVisibility = style.visibility;
        1005 var originalPosition = style.position;
        1006
        1007 style.visibility = 'hidden';
        1008 style.position = 'absolute';
        1009 style.display = 'inline';
        1010
        1011 var retVal = fn(element);
        1012
        1013 style.display = originalDisplay;
        1014 style.position = originalPosition;
        1015 style.visibility = originalVisibility;
        1016
        1017 return retVal;
        1018};
        1019
        1020
        1021/**
        1022 * Gets the height and width of an element when the display is not none.
        1023 * @param {Element} element Element to get size of.
        1024 * @return {!goog.math.Size} Object with width/height properties.
        1025 * @private
        1026 */
        1027goog.style.getSizeWithDisplay_ = function(element) {
        1028 var offsetWidth = element.offsetWidth;
        1029 var offsetHeight = element.offsetHeight;
        1030 var webkitOffsetsZero =
        1031 goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
        1032 if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
        1033 element.getBoundingClientRect) {
        1034 // Fall back to calling getBoundingClientRect when offsetWidth or
        1035 // offsetHeight are not defined, or when they are zero in WebKit browsers.
        1036 // This makes sure that we return for the correct size for SVG elements, but
        1037 // will still return 0 on Webkit prior to 534.8, see
        1038 // http://trac.webkit.org/changeset/67252.
        1039 var clientRect = goog.style.getBoundingClientRect_(element);
        1040 return new goog.math.Size(clientRect.right - clientRect.left,
        1041 clientRect.bottom - clientRect.top);
        1042 }
        1043 return new goog.math.Size(offsetWidth, offsetHeight);
        1044};
        1045
        1046
        1047/**
        1048 * Gets the height and width of an element, post transform, even if its display
        1049 * is none.
        1050 *
        1051 * This is like {@code goog.style.getSize}, except:
        1052 * <ol>
        1053 * <li>Takes webkitTransforms such as rotate and scale into account.
        1054 * <li>Will return null if {@code element} doesn't respond to
        1055 * {@code getBoundingClientRect}.
        1056 * <li>Currently doesn't make sense on non-WebKit browsers which don't support
        1057 * webkitTransforms.
        1058 * </ol>
        1059 * @param {!Element} element Element to get size of.
        1060 * @return {goog.math.Size} Object with width/height properties.
        1061 */
        1062goog.style.getTransformedSize = function(element) {
        1063 if (!element.getBoundingClientRect) {
        1064 return null;
        1065 }
        1066
        1067 var clientRect = goog.style.evaluateWithTemporaryDisplay_(
        1068 goog.style.getBoundingClientRect_, element);
        1069 return new goog.math.Size(clientRect.right - clientRect.left,
        1070 clientRect.bottom - clientRect.top);
        1071};
        1072
        1073
        1074/**
        1075 * Returns a bounding rectangle for a given element in page space.
        1076 * @param {Element} element Element to get bounds of. Must not be display none.
        1077 * @return {!goog.math.Rect} Bounding rectangle for the element.
        1078 */
        1079goog.style.getBounds = function(element) {
        1080 var o = goog.style.getPageOffset(element);
        1081 var s = goog.style.getSize(element);
        1082 return new goog.math.Rect(o.x, o.y, s.width, s.height);
        1083};
        1084
        1085
        1086/**
        1087 * Converts a CSS selector in the form style-property to styleProperty.
        1088 * @param {*} selector CSS Selector.
        1089 * @return {string} Camel case selector.
        1090 * @deprecated Use goog.string.toCamelCase instead.
        1091 */
        1092goog.style.toCamelCase = function(selector) {
        1093 return goog.string.toCamelCase(String(selector));
        1094};
        1095
        1096
        1097/**
        1098 * Converts a CSS selector in the form styleProperty to style-property.
        1099 * @param {string} selector Camel case selector.
        1100 * @return {string} Selector cased.
        1101 * @deprecated Use goog.string.toSelectorCase instead.
        1102 */
        1103goog.style.toSelectorCase = function(selector) {
        1104 return goog.string.toSelectorCase(selector);
        1105};
        1106
        1107
        1108/**
        1109 * Gets the opacity of a node (x-browser). This gets the inline style opacity
        1110 * of the node, and does not take into account the cascaded or the computed
        1111 * style for this node.
        1112 * @param {Element} el Element whose opacity has to be found.
        1113 * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
        1114 * if the opacity is not set.
        1115 */
        1116goog.style.getOpacity = function(el) {
        1117 var style = el.style;
        1118 var result = '';
        1119 if ('opacity' in style) {
        1120 result = style.opacity;
        1121 } else if ('MozOpacity' in style) {
        1122 result = style.MozOpacity;
        1123 } else if ('filter' in style) {
        1124 var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
        1125 if (match) {
        1126 result = String(match[1] / 100);
        1127 }
        1128 }
        1129 return result == '' ? result : Number(result);
        1130};
        1131
        1132
        1133/**
        1134 * Sets the opacity of a node (x-browser).
        1135 * @param {Element} el Elements whose opacity has to be set.
        1136 * @param {number|string} alpha Opacity between 0 and 1 or an empty string
        1137 * {@code ''} to clear the opacity.
        1138 */
        1139goog.style.setOpacity = function(el, alpha) {
        1140 var style = el.style;
        1141 if ('opacity' in style) {
        1142 style.opacity = alpha;
        1143 } else if ('MozOpacity' in style) {
        1144 style.MozOpacity = alpha;
        1145 } else if ('filter' in style) {
        1146 // TODO(arv): Overwriting the filter might have undesired side effects.
        1147 if (alpha === '') {
        1148 style.filter = '';
        1149 } else {
        1150 style.filter = 'alpha(opacity=' + alpha * 100 + ')';
        1151 }
        1152 }
        1153};
        1154
        1155
        1156/**
        1157 * Sets the background of an element to a transparent image in a browser-
        1158 * independent manner.
        1159 *
        1160 * This function does not support repeating backgrounds or alternate background
        1161 * positions to match the behavior of Internet Explorer. It also does not
        1162 * support sizingMethods other than crop since they cannot be replicated in
        1163 * browsers other than Internet Explorer.
        1164 *
        1165 * @param {Element} el The element to set background on.
        1166 * @param {string} src The image source URL.
        1167 */
        1168goog.style.setTransparentBackgroundImage = function(el, src) {
        1169 var style = el.style;
        1170 // It is safe to use the style.filter in IE only. In Safari 'filter' is in
        1171 // style object but access to style.filter causes it to throw an exception.
        1172 // Note: IE8 supports images with an alpha channel.
        1173 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
        1174 // See TODO in setOpacity.
        1175 style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
        1176 'src="' + src + '", sizingMethod="crop")';
        1177 } else {
        1178 // Set style properties individually instead of using background shorthand
        1179 // to prevent overwriting a pre-existing background color.
        1180 style.backgroundImage = 'url(' + src + ')';
        1181 style.backgroundPosition = 'top left';
        1182 style.backgroundRepeat = 'no-repeat';
        1183 }
        1184};
        1185
        1186
        1187/**
        1188 * Clears the background image of an element in a browser independent manner.
        1189 * @param {Element} el The element to clear background image for.
        1190 */
        1191goog.style.clearTransparentBackgroundImage = function(el) {
        1192 var style = el.style;
        1193 if ('filter' in style) {
        1194 // See TODO in setOpacity.
        1195 style.filter = '';
        1196 } else {
        1197 // Set style properties individually instead of using background shorthand
        1198 // to prevent overwriting a pre-existing background color.
        1199 style.backgroundImage = 'none';
        1200 }
        1201};
        1202
        1203
        1204/**
        1205 * Shows or hides an element from the page. Hiding the element is done by
        1206 * setting the display property to "none", removing the element from the
        1207 * rendering hierarchy so it takes up no space. To show the element, the default
        1208 * inherited display property is restored (defined either in stylesheets or by
        1209 * the browser's default style rules.)
        1210 *
        1211 * Caveat 1: if the inherited display property for the element is set to "none"
        1212 * by the stylesheets, that is the property that will be restored by a call to
        1213 * showElement(), effectively toggling the display between "none" and "none".
        1214 *
        1215 * Caveat 2: if the element display style is set inline (by setting either
        1216 * element.style.display or a style attribute in the HTML), a call to
        1217 * showElement will clear that setting and defer to the inherited style in the
        1218 * stylesheet.
        1219 * @param {Element} el Element to show or hide.
        1220 * @param {*} display True to render the element in its default style,
        1221 * false to disable rendering the element.
        1222 * @deprecated Use goog.style.setElementShown instead.
        1223 */
        1224goog.style.showElement = function(el, display) {
        1225 goog.style.setElementShown(el, display);
        1226};
        1227
        1228
        1229/**
        1230 * Shows or hides an element from the page. Hiding the element is done by
        1231 * setting the display property to "none", removing the element from the
        1232 * rendering hierarchy so it takes up no space. To show the element, the default
        1233 * inherited display property is restored (defined either in stylesheets or by
        1234 * the browser's default style rules).
        1235 *
        1236 * Caveat 1: if the inherited display property for the element is set to "none"
        1237 * by the stylesheets, that is the property that will be restored by a call to
        1238 * setElementShown(), effectively toggling the display between "none" and
        1239 * "none".
        1240 *
        1241 * Caveat 2: if the element display style is set inline (by setting either
        1242 * element.style.display or a style attribute in the HTML), a call to
        1243 * setElementShown will clear that setting and defer to the inherited style in
        1244 * the stylesheet.
        1245 * @param {Element} el Element to show or hide.
        1246 * @param {*} isShown True to render the element in its default style,
        1247 * false to disable rendering the element.
        1248 */
        1249goog.style.setElementShown = function(el, isShown) {
        1250 el.style.display = isShown ? '' : 'none';
        1251};
        1252
        1253
        1254/**
        1255 * Test whether the given element has been shown or hidden via a call to
        1256 * {@link #setElementShown}.
        1257 *
        1258 * Note this is strictly a companion method for a call
        1259 * to {@link #setElementShown} and the same caveats apply; in particular, this
        1260 * method does not guarantee that the return value will be consistent with
        1261 * whether or not the element is actually visible.
        1262 *
        1263 * @param {Element} el The element to test.
        1264 * @return {boolean} Whether the element has been shown.
        1265 * @see #setElementShown
        1266 */
        1267goog.style.isElementShown = function(el) {
        1268 return el.style.display != 'none';
        1269};
        1270
        1271
        1272/**
        1273 * Installs the styles string into the window that contains opt_element. If
        1274 * opt_element is null, the main window is used.
        1275 * @param {string} stylesString The style string to install.
        1276 * @param {Node=} opt_node Node whose parent document should have the
        1277 * styles installed.
        1278 * @return {Element|StyleSheet} The style element created.
        1279 */
        1280goog.style.installStyles = function(stylesString, opt_node) {
        1281 var dh = goog.dom.getDomHelper(opt_node);
        1282 var styleSheet = null;
        1283
        1284 // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
        1285 // undefined as of IE 11.
        1286 var doc = dh.getDocument();
        1287 if (goog.userAgent.IE && doc.createStyleSheet) {
        1288 styleSheet = doc.createStyleSheet();
        1289 goog.style.setStyles(styleSheet, stylesString);
        1290 } else {
        1291 var head = dh.getElementsByTagNameAndClass(goog.dom.TagName.HEAD)[0];
        1292
        1293 // In opera documents are not guaranteed to have a head element, thus we
        1294 // have to make sure one exists before using it.
        1295 if (!head) {
        1296 var body = dh.getElementsByTagNameAndClass(goog.dom.TagName.BODY)[0];
        1297 head = dh.createDom(goog.dom.TagName.HEAD);
        1298 body.parentNode.insertBefore(head, body);
        1299 }
        1300 styleSheet = dh.createDom(goog.dom.TagName.STYLE);
        1301 // NOTE(user): Setting styles after the style element has been appended
        1302 // to the head results in a nasty Webkit bug in certain scenarios. Please
        1303 // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
        1304 // details.
        1305 goog.style.setStyles(styleSheet, stylesString);
        1306 dh.appendChild(head, styleSheet);
        1307 }
        1308 return styleSheet;
        1309};
        1310
        1311
        1312/**
        1313 * Removes the styles added by {@link #installStyles}.
        1314 * @param {Element|StyleSheet} styleSheet The value returned by
        1315 * {@link #installStyles}.
        1316 */
        1317goog.style.uninstallStyles = function(styleSheet) {
        1318 var node = styleSheet.ownerNode || styleSheet.owningElement ||
        1319 /** @type {Element} */ (styleSheet);
        1320 goog.dom.removeNode(node);
        1321};
        1322
        1323
        1324/**
        1325 * Sets the content of a style element. The style element can be any valid
        1326 * style element. This element will have its content completely replaced by
        1327 * the new stylesString.
        1328 * @param {Element|StyleSheet} element A stylesheet element as returned by
        1329 * installStyles.
        1330 * @param {string} stylesString The new content of the stylesheet.
        1331 */
        1332goog.style.setStyles = function(element, stylesString) {
        1333 if (goog.userAgent.IE && goog.isDef(element.cssText)) {
        1334 // Adding the selectors individually caused the browser to hang if the
        1335 // selector was invalid or there were CSS comments. Setting the cssText of
        1336 // the style node works fine and ignores CSS that IE doesn't understand.
        1337 // However IE >= 11 doesn't support cssText any more, so we make sure that
        1338 // cssText is a defined property and otherwise fall back to innerHTML.
        1339 element.cssText = stylesString;
        1340 } else {
        1341 element.innerHTML = stylesString;
        1342 }
        1343};
        1344
        1345
        1346/**
        1347 * Sets 'white-space: pre-wrap' for a node (x-browser).
        1348 *
        1349 * There are as many ways of specifying pre-wrap as there are browsers.
        1350 *
        1351 * CSS3/IE8: white-space: pre-wrap;
        1352 * Mozilla: white-space: -moz-pre-wrap;
        1353 * Opera: white-space: -o-pre-wrap;
        1354 * IE6/7: white-space: pre; word-wrap: break-word;
        1355 *
        1356 * @param {Element} el Element to enable pre-wrap for.
        1357 */
        1358goog.style.setPreWrap = function(el) {
        1359 var style = el.style;
        1360 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
        1361 style.whiteSpace = 'pre';
        1362 style.wordWrap = 'break-word';
        1363 } else if (goog.userAgent.GECKO) {
        1364 style.whiteSpace = '-moz-pre-wrap';
        1365 } else {
        1366 style.whiteSpace = 'pre-wrap';
        1367 }
        1368};
        1369
        1370
        1371/**
        1372 * Sets 'display: inline-block' for an element (cross-browser).
        1373 * @param {Element} el Element to which the inline-block display style is to be
        1374 * applied.
        1375 * @see ../demos/inline_block_quirks.html
        1376 * @see ../demos/inline_block_standards.html
        1377 */
        1378goog.style.setInlineBlock = function(el) {
        1379 var style = el.style;
        1380 // Without position:relative, weirdness ensues. Just accept it and move on.
        1381 style.position = 'relative';
        1382
        1383 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
        1384 // IE8 supports inline-block so fall through to the else
        1385 // Zoom:1 forces hasLayout, display:inline gives inline behavior.
        1386 style.zoom = '1';
        1387 style.display = 'inline';
        1388 } else {
        1389 // Opera, Webkit, and Safari seem to do OK with the standard inline-block
        1390 // style.
        1391 style.display = 'inline-block';
        1392 }
        1393};
        1394
        1395
        1396/**
        1397 * Returns true if the element is using right to left (rtl) direction.
        1398 * @param {Element} el The element to test.
        1399 * @return {boolean} True for right to left, false for left to right.
        1400 */
        1401goog.style.isRightToLeft = function(el) {
        1402 return 'rtl' == goog.style.getStyle_(el, 'direction');
        1403};
        1404
        1405
        1406/**
        1407 * The CSS style property corresponding to an element being
        1408 * unselectable on the current browser platform (null if none).
        1409 * Opera and IE instead use a DOM attribute 'unselectable'.
        1410 * @type {?string}
        1411 * @private
        1412 */
        1413goog.style.unselectableStyle_ =
        1414 goog.userAgent.GECKO ? 'MozUserSelect' :
        1415 goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
        1416 null;
        1417
        1418
        1419/**
        1420 * Returns true if the element is set to be unselectable, false otherwise.
        1421 * Note that on some platforms (e.g. Mozilla), even if an element isn't set
        1422 * to be unselectable, it will behave as such if any of its ancestors is
        1423 * unselectable.
        1424 * @param {Element} el Element to check.
        1425 * @return {boolean} Whether the element is set to be unselectable.
        1426 */
        1427goog.style.isUnselectable = function(el) {
        1428 if (goog.style.unselectableStyle_) {
        1429 return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
        1430 } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
        1431 return el.getAttribute('unselectable') == 'on';
        1432 }
        1433 return false;
        1434};
        1435
        1436
        1437/**
        1438 * Makes the element and its descendants selectable or unselectable. Note
        1439 * that on some platforms (e.g. Mozilla), even if an element isn't set to
        1440 * be unselectable, it will behave as such if any of its ancestors is
        1441 * unselectable.
        1442 * @param {Element} el The element to alter.
        1443 * @param {boolean} unselectable Whether the element and its descendants
        1444 * should be made unselectable.
        1445 * @param {boolean=} opt_noRecurse Whether to only alter the element's own
        1446 * selectable state, and leave its descendants alone; defaults to false.
        1447 */
        1448goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
        1449 // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
        1450 var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
        1451 var name = goog.style.unselectableStyle_;
        1452 if (name) {
        1453 // Add/remove the appropriate CSS style to/from the element and its
        1454 // descendants.
        1455 var value = unselectable ? 'none' : '';
        1456 // MathML elements do not have a style property. Verify before setting.
        1457 if (el.style) {
        1458 el.style[name] = value;
        1459 }
        1460 if (descendants) {
        1461 for (var i = 0, descendant; descendant = descendants[i]; i++) {
        1462 if (descendant.style) {
        1463 descendant.style[name] = value;
        1464 }
        1465 }
        1466 }
        1467 } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
        1468 // Toggle the 'unselectable' attribute on the element and its descendants.
        1469 var value = unselectable ? 'on' : '';
        1470 el.setAttribute('unselectable', value);
        1471 if (descendants) {
        1472 for (var i = 0, descendant; descendant = descendants[i]; i++) {
        1473 descendant.setAttribute('unselectable', value);
        1474 }
        1475 }
        1476 }
        1477};
        1478
        1479
        1480/**
        1481 * Gets the border box size for an element.
        1482 * @param {Element} element The element to get the size for.
        1483 * @return {!goog.math.Size} The border box size.
        1484 */
        1485goog.style.getBorderBoxSize = function(element) {
        1486 return new goog.math.Size(element.offsetWidth, element.offsetHeight);
        1487};
        1488
        1489
        1490/**
        1491 * Sets the border box size of an element. This is potentially expensive in IE
        1492 * if the document is CSS1Compat mode
        1493 * @param {Element} element The element to set the size on.
        1494 * @param {goog.math.Size} size The new size.
        1495 */
        1496goog.style.setBorderBoxSize = function(element, size) {
        1497 var doc = goog.dom.getOwnerDocument(element);
        1498 var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
        1499
        1500 if (goog.userAgent.IE &&
        1501 !goog.userAgent.isVersionOrHigher('10') &&
        1502 (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
        1503 var style = element.style;
        1504 if (isCss1CompatMode) {
        1505 var paddingBox = goog.style.getPaddingBox(element);
        1506 var borderBox = goog.style.getBorderBox(element);
        1507 style.pixelWidth = size.width - borderBox.left - paddingBox.left -
        1508 paddingBox.right - borderBox.right;
        1509 style.pixelHeight = size.height - borderBox.top - paddingBox.top -
        1510 paddingBox.bottom - borderBox.bottom;
        1511 } else {
        1512 style.pixelWidth = size.width;
        1513 style.pixelHeight = size.height;
        1514 }
        1515 } else {
        1516 goog.style.setBoxSizingSize_(element, size, 'border-box');
        1517 }
        1518};
        1519
        1520
        1521/**
        1522 * Gets the content box size for an element. This is potentially expensive in
        1523 * all browsers.
        1524 * @param {Element} element The element to get the size for.
        1525 * @return {!goog.math.Size} The content box size.
        1526 */
        1527goog.style.getContentBoxSize = function(element) {
        1528 var doc = goog.dom.getOwnerDocument(element);
        1529 var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
        1530 if (ieCurrentStyle &&
        1531 goog.dom.getDomHelper(doc).isCss1CompatMode() &&
        1532 ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
        1533 !ieCurrentStyle.boxSizing) {
        1534 // If IE in CSS1Compat mode than just use the width and height.
        1535 // If we have a boxSizing then fall back on measuring the borders etc.
        1536 var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
        1537 'width', 'pixelWidth');
        1538 var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
        1539 'height', 'pixelHeight');
        1540 return new goog.math.Size(width, height);
        1541 } else {
        1542 var borderBoxSize = goog.style.getBorderBoxSize(element);
        1543 var paddingBox = goog.style.getPaddingBox(element);
        1544 var borderBox = goog.style.getBorderBox(element);
        1545 return new goog.math.Size(borderBoxSize.width -
        1546 borderBox.left - paddingBox.left -
        1547 paddingBox.right - borderBox.right,
        1548 borderBoxSize.height -
        1549 borderBox.top - paddingBox.top -
        1550 paddingBox.bottom - borderBox.bottom);
        1551 }
        1552};
        1553
        1554
        1555/**
        1556 * Sets the content box size of an element. This is potentially expensive in IE
        1557 * if the document is BackCompat mode.
        1558 * @param {Element} element The element to set the size on.
        1559 * @param {goog.math.Size} size The new size.
        1560 */
        1561goog.style.setContentBoxSize = function(element, size) {
        1562 var doc = goog.dom.getOwnerDocument(element);
        1563 var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
        1564 if (goog.userAgent.IE &&
        1565 !goog.userAgent.isVersionOrHigher('10') &&
        1566 (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
        1567 var style = element.style;
        1568 if (isCss1CompatMode) {
        1569 style.pixelWidth = size.width;
        1570 style.pixelHeight = size.height;
        1571 } else {
        1572 var paddingBox = goog.style.getPaddingBox(element);
        1573 var borderBox = goog.style.getBorderBox(element);
        1574 style.pixelWidth = size.width + borderBox.left + paddingBox.left +
        1575 paddingBox.right + borderBox.right;
        1576 style.pixelHeight = size.height + borderBox.top + paddingBox.top +
        1577 paddingBox.bottom + borderBox.bottom;
        1578 }
        1579 } else {
        1580 goog.style.setBoxSizingSize_(element, size, 'content-box');
        1581 }
        1582};
        1583
        1584
        1585/**
        1586 * Helper function that sets the box sizing as well as the width and height
        1587 * @param {Element} element The element to set the size on.
        1588 * @param {goog.math.Size} size The new size to set.
        1589 * @param {string} boxSizing The box-sizing value.
        1590 * @private
        1591 */
        1592goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
        1593 var style = element.style;
        1594 if (goog.userAgent.GECKO) {
        1595 style.MozBoxSizing = boxSizing;
        1596 } else if (goog.userAgent.WEBKIT) {
        1597 style.WebkitBoxSizing = boxSizing;
        1598 } else {
        1599 // Includes IE8 and Opera 9.50+
        1600 style.boxSizing = boxSizing;
        1601 }
        1602
        1603 // Setting this to a negative value will throw an exception on IE
        1604 // (and doesn't do anything different than setting it to 0).
        1605 style.width = Math.max(size.width, 0) + 'px';
        1606 style.height = Math.max(size.height, 0) + 'px';
        1607};
        1608
        1609
        1610/**
        1611 * IE specific function that converts a non pixel unit to pixels.
        1612 * @param {Element} element The element to convert the value for.
        1613 * @param {string} value The current value as a string. The value must not be
        1614 * ''.
        1615 * @param {string} name The CSS property name to use for the converstion. This
        1616 * should be 'left', 'top', 'width' or 'height'.
        1617 * @param {string} pixelName The CSS pixel property name to use to get the
        1618 * value in pixels.
        1619 * @return {number} The value in pixels.
        1620 * @private
        1621 */
        1622goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
        1623 // Try if we already have a pixel value. IE does not do half pixels so we
        1624 // only check if it matches a number followed by 'px'.
        1625 if (/^\d+px?$/.test(value)) {
        1626 return parseInt(value, 10);
        1627 } else {
        1628 var oldStyleValue = element.style[name];
        1629 var oldRuntimeValue = element.runtimeStyle[name];
        1630 // set runtime style to prevent changes
        1631 element.runtimeStyle[name] = element.currentStyle[name];
        1632 element.style[name] = value;
        1633 var pixelValue = element.style[pixelName];
        1634 // restore
        1635 element.style[name] = oldStyleValue;
        1636 element.runtimeStyle[name] = oldRuntimeValue;
        1637 return pixelValue;
        1638 }
        1639};
        1640
        1641
        1642/**
        1643 * Helper function for getting the pixel padding or margin for IE.
        1644 * @param {Element} element The element to get the padding for.
        1645 * @param {string} propName The property name.
        1646 * @return {number} The pixel padding.
        1647 * @private
        1648 */
        1649goog.style.getIePixelDistance_ = function(element, propName) {
        1650 var value = goog.style.getCascadedStyle(element, propName);
        1651 return value ?
        1652 goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
        1653};
        1654
        1655
        1656/**
        1657 * Gets the computed paddings or margins (on all sides) in pixels.
        1658 * @param {Element} element The element to get the padding for.
        1659 * @param {string} stylePrefix Pass 'padding' to retrieve the padding box,
        1660 * or 'margin' to retrieve the margin box.
        1661 * @return {!goog.math.Box} The computed paddings or margins.
        1662 * @private
        1663 */
        1664goog.style.getBox_ = function(element, stylePrefix) {
        1665 if (goog.userAgent.IE) {
        1666 var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
        1667 var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
        1668 var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
        1669 var bottom = goog.style.getIePixelDistance_(
        1670 element, stylePrefix + 'Bottom');
        1671 return new goog.math.Box(top, right, bottom, left);
        1672 } else {
        1673 // On non-IE browsers, getComputedStyle is always non-null.
        1674 var left = /** @type {string} */ (
        1675 goog.style.getComputedStyle(element, stylePrefix + 'Left'));
        1676 var right = /** @type {string} */ (
        1677 goog.style.getComputedStyle(element, stylePrefix + 'Right'));
        1678 var top = /** @type {string} */ (
        1679 goog.style.getComputedStyle(element, stylePrefix + 'Top'));
        1680 var bottom = /** @type {string} */ (
        1681 goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
        1682
        1683 // NOTE(arv): Gecko can return floating point numbers for the computed
        1684 // style values.
        1685 return new goog.math.Box(parseFloat(top),
        1686 parseFloat(right),
        1687 parseFloat(bottom),
        1688 parseFloat(left));
        1689 }
        1690};
        1691
        1692
        1693/**
        1694 * Gets the computed paddings (on all sides) in pixels.
        1695 * @param {Element} element The element to get the padding for.
        1696 * @return {!goog.math.Box} The computed paddings.
        1697 */
        1698goog.style.getPaddingBox = function(element) {
        1699 return goog.style.getBox_(element, 'padding');
        1700};
        1701
        1702
        1703/**
        1704 * Gets the computed margins (on all sides) in pixels.
        1705 * @param {Element} element The element to get the margins for.
        1706 * @return {!goog.math.Box} The computed margins.
        1707 */
        1708goog.style.getMarginBox = function(element) {
        1709 return goog.style.getBox_(element, 'margin');
        1710};
        1711
        1712
        1713/**
        1714 * A map used to map the border width keywords to a pixel width.
        1715 * @type {Object}
        1716 * @private
        1717 */
        1718goog.style.ieBorderWidthKeywords_ = {
        1719 'thin': 2,
        1720 'medium': 4,
        1721 'thick': 6
        1722};
        1723
        1724
        1725/**
        1726 * Helper function for IE to get the pixel border.
        1727 * @param {Element} element The element to get the pixel border for.
        1728 * @param {string} prop The part of the property name.
        1729 * @return {number} The value in pixels.
        1730 * @private
        1731 */
        1732goog.style.getIePixelBorder_ = function(element, prop) {
        1733 if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
        1734 return 0;
        1735 }
        1736 var width = goog.style.getCascadedStyle(element, prop + 'Width');
        1737 if (width in goog.style.ieBorderWidthKeywords_) {
        1738 return goog.style.ieBorderWidthKeywords_[width];
        1739 }
        1740 return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
        1741};
        1742
        1743
        1744/**
        1745 * Gets the computed border widths (on all sides) in pixels
        1746 * @param {Element} element The element to get the border widths for.
        1747 * @return {!goog.math.Box} The computed border widths.
        1748 */
        1749goog.style.getBorderBox = function(element) {
        1750 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
        1751 var left = goog.style.getIePixelBorder_(element, 'borderLeft');
        1752 var right = goog.style.getIePixelBorder_(element, 'borderRight');
        1753 var top = goog.style.getIePixelBorder_(element, 'borderTop');
        1754 var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
        1755 return new goog.math.Box(top, right, bottom, left);
        1756 } else {
        1757 // On non-IE browsers, getComputedStyle is always non-null.
        1758 var left = /** @type {string} */ (
        1759 goog.style.getComputedStyle(element, 'borderLeftWidth'));
        1760 var right = /** @type {string} */ (
        1761 goog.style.getComputedStyle(element, 'borderRightWidth'));
        1762 var top = /** @type {string} */ (
        1763 goog.style.getComputedStyle(element, 'borderTopWidth'));
        1764 var bottom = /** @type {string} */ (
        1765 goog.style.getComputedStyle(element, 'borderBottomWidth'));
        1766
        1767 return new goog.math.Box(parseFloat(top),
        1768 parseFloat(right),
        1769 parseFloat(bottom),
        1770 parseFloat(left));
        1771 }
        1772};
        1773
        1774
        1775/**
        1776 * Returns the font face applied to a given node. Opera and IE should return
        1777 * the font actually displayed. Firefox returns the author's most-preferred
        1778 * font (whether the browser is capable of displaying it or not.)
        1779 * @param {Element} el The element whose font family is returned.
        1780 * @return {string} The font family applied to el.
        1781 */
        1782goog.style.getFontFamily = function(el) {
        1783 var doc = goog.dom.getOwnerDocument(el);
        1784 var font = '';
        1785 // The moveToElementText method from the TextRange only works if the element
        1786 // is attached to the owner document.
        1787 if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
        1788 var range = doc.body.createTextRange();
        1789 range.moveToElementText(el);
        1790 /** @preserveTry */
        1791 try {
        1792 font = range.queryCommandValue('FontName');
        1793 } catch (e) {
        1794 // This is a workaround for a awkward exception.
        1795 // On some IE, there is an exception coming from it.
        1796 // The error description from this exception is:
        1797 // This window has already been registered as a drop target
        1798 // This is bogus description, likely due to a bug in ie.
        1799 font = '';
        1800 }
        1801 }
        1802 if (!font) {
        1803 // Note if for some reason IE can't derive FontName with a TextRange, we
        1804 // fallback to using currentStyle
        1805 font = goog.style.getStyle_(el, 'fontFamily');
        1806 }
        1807
        1808 // Firefox returns the applied font-family string (author's list of
        1809 // preferred fonts.) We want to return the most-preferred font, in lieu of
        1810 // the *actually* applied font.
        1811 var fontsArray = font.split(',');
        1812 if (fontsArray.length > 1) font = fontsArray[0];
        1813
        1814 // Sanitize for x-browser consistency:
        1815 // Strip quotes because browsers aren't consistent with how they're
        1816 // applied; Opera always encloses, Firefox sometimes, and IE never.
        1817 return goog.string.stripQuotes(font, '"\'');
        1818};
        1819
        1820
        1821/**
        1822 * Regular expression used for getLengthUnits.
        1823 * @type {RegExp}
        1824 * @private
        1825 */
        1826goog.style.lengthUnitRegex_ = /[^\d]+$/;
        1827
        1828
        1829/**
        1830 * Returns the units used for a CSS length measurement.
        1831 * @param {string} value A CSS length quantity.
        1832 * @return {?string} The units of measurement.
        1833 */
        1834goog.style.getLengthUnits = function(value) {
        1835 var units = value.match(goog.style.lengthUnitRegex_);
        1836 return units && units[0] || null;
        1837};
        1838
        1839
        1840/**
        1841 * Map of absolute CSS length units
        1842 * @type {Object}
        1843 * @private
        1844 */
        1845goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
        1846 'cm' : 1,
        1847 'in' : 1,
        1848 'mm' : 1,
        1849 'pc' : 1,
        1850 'pt' : 1
        1851};
        1852
        1853
        1854/**
        1855 * Map of relative CSS length units that can be accurately converted to px
        1856 * font-size values using getIePixelValue_. Only units that are defined in
        1857 * relation to a font size are convertible (%, small, etc. are not).
        1858 * @type {Object}
        1859 * @private
        1860 */
        1861goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
        1862 'em' : 1,
        1863 'ex' : 1
        1864};
        1865
        1866
        1867/**
        1868 * Returns the font size, in pixels, of text in an element.
        1869 * @param {Element} el The element whose font size is returned.
        1870 * @return {number} The font size (in pixels).
        1871 */
        1872goog.style.getFontSize = function(el) {
        1873 var fontSize = goog.style.getStyle_(el, 'fontSize');
        1874 var sizeUnits = goog.style.getLengthUnits(fontSize);
        1875 if (fontSize && 'px' == sizeUnits) {
        1876 // NOTE(user): This could be parseFloat instead, but IE doesn't return
        1877 // decimal fractions in getStyle_ and Firefox reports the fractions, but
        1878 // ignores them when rendering. Interestingly enough, when we force the
        1879 // issue and size something to e.g., 50% of 25px, the browsers round in
        1880 // opposite directions with Firefox reporting 12px and IE 13px. I punt.
        1881 return parseInt(fontSize, 10);
        1882 }
        1883
        1884 // In IE, we can convert absolute length units to a px value using
        1885 // goog.style.getIePixelValue_. Units defined in relation to a font size
        1886 // (em, ex) are applied relative to the element's parentNode and can also
        1887 // be converted.
        1888 if (goog.userAgent.IE) {
        1889 if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
        1890 return goog.style.getIePixelValue_(el,
        1891 fontSize,
        1892 'left',
        1893 'pixelLeft');
        1894 } else if (el.parentNode &&
        1895 el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
        1896 sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
        1897 // Check the parent size - if it is the same it means the relative size
        1898 // value is inherited and we therefore don't want to count it twice. If
        1899 // it is different, this element either has explicit style or has a CSS
        1900 // rule applying to it.
        1901 var parentElement = /** @type {!Element} */ (el.parentNode);
        1902 var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
        1903 return goog.style.getIePixelValue_(parentElement,
        1904 fontSize == parentSize ?
        1905 '1em' : fontSize,
        1906 'left',
        1907 'pixelLeft');
        1908 }
        1909 }
        1910
        1911 // Sometimes we can't cleanly find the font size (some units relative to a
        1912 // node's parent's font size are difficult: %, smaller et al), so we create
        1913 // an invisible, absolutely-positioned span sized to be the height of an 'M'
        1914 // rendered in its parent's (i.e., our target element's) font size. This is
        1915 // the definition of CSS's font size attribute.
        1916 var sizeElement = goog.dom.createDom(
        1917 goog.dom.TagName.SPAN,
        1918 {'style': 'visibility:hidden;position:absolute;' +
        1919 'line-height:0;padding:0;margin:0;border:0;height:1em;'});
        1920 goog.dom.appendChild(el, sizeElement);
        1921 fontSize = sizeElement.offsetHeight;
        1922 goog.dom.removeNode(sizeElement);
        1923
        1924 return fontSize;
        1925};
        1926
        1927
        1928/**
        1929 * Parses a style attribute value. Converts CSS property names to camel case.
        1930 * @param {string} value The style attribute value.
        1931 * @return {!Object} Map of CSS properties to string values.
        1932 */
        1933goog.style.parseStyleAttribute = function(value) {
        1934 var result = {};
        1935 goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
        1936 var keyValue = pair.split(/\s*:\s*/);
        1937 if (keyValue.length == 2) {
        1938 result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
        1939 }
        1940 });
        1941 return result;
        1942};
        1943
        1944
        1945/**
        1946 * Reverse of parseStyleAttribute; that is, takes a style object and returns the
        1947 * corresponding attribute value. Converts camel case property names to proper
        1948 * CSS selector names.
        1949 * @param {Object} obj Map of CSS properties to values.
        1950 * @return {string} The style attribute value.
        1951 */
        1952goog.style.toStyleAttribute = function(obj) {
        1953 var buffer = [];
        1954 goog.object.forEach(obj, function(value, key) {
        1955 buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
        1956 });
        1957 return buffer.join('');
        1958};
        1959
        1960
        1961/**
        1962 * JavaScript name of the "float" css property.
        1963 * @private {string}
        1964 * @const
        1965 */
        1966goog.style.FLOAT_CSS_PROPERTY_NAME_ =
        1967 goog.userAgent.IE && !goog.userAgent.isVersionOrHigher(12) ?
        1968 'styleFloat' : 'cssFloat';
        1969
        1970
        1971/**
        1972 * Sets CSS float property on an element.
        1973 * @param {Element} el The element to set float property on.
        1974 * @param {string} value The value of float CSS property to set on this element.
        1975 */
        1976goog.style.setFloat = function(el, value) {
        1977 el.style[goog.style.FLOAT_CSS_PROPERTY_NAME_] = value;
        1978};
        1979
        1980
        1981/**
        1982 * Gets value of explicitly-set float CSS property on an element.
        1983 * @param {Element} el The element to get float property of.
        1984 * @return {string} The value of explicitly-set float CSS property on this
        1985 * element.
        1986 */
        1987goog.style.getFloat = function(el) {
        1988 return el.style[goog.style.FLOAT_CSS_PROPERTY_NAME_] || '';
        1989};
        1990
        1991
        1992/**
        1993 * Returns the scroll bar width (represents the width of both horizontal
        1994 * and vertical scroll).
        1995 *
        1996 * @param {string=} opt_className An optional class name (or names) to apply
        1997 * to the invisible div created to measure the scrollbar. This is necessary
        1998 * if some scrollbars are styled differently than others.
        1999 * @return {number} The scroll bar width in px.
        2000 */
        2001goog.style.getScrollbarWidth = function(opt_className) {
        2002 // Add two hidden divs. The child div is larger than the parent and
        2003 // forces scrollbars to appear on it.
        2004 // Using overflow:scroll does not work consistently with scrollbars that
        2005 // are styled with ::-webkit-scrollbar.
        2006 var outerDiv = goog.dom.createElement(goog.dom.TagName.DIV);
        2007 if (opt_className) {
        2008 outerDiv.className = opt_className;
        2009 }
        2010 outerDiv.style.cssText = 'overflow:auto;' +
        2011 'position:absolute;top:0;width:100px;height:100px';
        2012 var innerDiv = goog.dom.createElement(goog.dom.TagName.DIV);
        2013 goog.style.setSize(innerDiv, '200px', '200px');
        2014 outerDiv.appendChild(innerDiv);
        2015 goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
        2016 var width = outerDiv.offsetWidth - outerDiv.clientWidth;
        2017 goog.dom.removeNode(outerDiv);
        2018 return width;
        2019};
        2020
        2021
        2022/**
        2023 * Regular expression to extract x and y translation components from a CSS
        2024 * transform Matrix representation.
        2025 *
        2026 * @type {!RegExp}
        2027 * @const
        2028 * @private
        2029 */
        2030goog.style.MATRIX_TRANSLATION_REGEX_ =
        2031 new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
        2032 '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
        2033 '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
        2034
        2035
        2036/**
        2037 * Returns the x,y translation component of any CSS transforms applied to the
        2038 * element, in pixels.
        2039 *
        2040 * @param {!Element} element The element to get the translation of.
        2041 * @return {!goog.math.Coordinate} The CSS translation of the element in px.
        2042 */
        2043goog.style.getCssTranslation = function(element) {
        2044 var transform = goog.style.getComputedTransform(element);
        2045 if (!transform) {
        2046 return new goog.math.Coordinate(0, 0);
        2047 }
        2048 var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
        2049 if (!matches) {
        2050 return new goog.math.Coordinate(0, 0);
        2051 }
        2052 return new goog.math.Coordinate(parseFloat(matches[1]),
        2053 parseFloat(matches[2]));
        2054};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/asserts.js.src.html b/docs/source/lib/goog/testing/asserts.js.src.html new file mode 100644 index 0000000..702a2de --- /dev/null +++ b/docs/source/lib/goog/testing/asserts.js.src.html @@ -0,0 +1 @@ +asserts.js

        lib/goog/testing/asserts.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14goog.provide('goog.testing.JsUnitException');
        15goog.provide('goog.testing.asserts');
        16goog.provide('goog.testing.asserts.ArrayLike');
        17
        18goog.require('goog.testing.stacktrace');
        19
        20// TODO(user): Copied from JsUnit with some small modifications, we should
        21// reimplement the asserters.
        22
        23
        24/**
        25 * @typedef {Array|NodeList|Arguments|{length: number}}
        26 */
        27goog.testing.asserts.ArrayLike;
        28
        29var DOUBLE_EQUALITY_PREDICATE = function(var1, var2) {
        30 return var1 == var2;
        31};
        32var JSUNIT_UNDEFINED_VALUE;
        33var TO_STRING_EQUALITY_PREDICATE = function(var1, var2) {
        34 return var1.toString() === var2.toString();
        35};
        36
        37var PRIMITIVE_EQUALITY_PREDICATES = {
        38 'String': DOUBLE_EQUALITY_PREDICATE,
        39 'Number': DOUBLE_EQUALITY_PREDICATE,
        40 'Boolean': DOUBLE_EQUALITY_PREDICATE,
        41 'Date': function(date1, date2) {
        42 return date1.getTime() == date2.getTime();
        43 },
        44 'RegExp': TO_STRING_EQUALITY_PREDICATE,
        45 'Function': TO_STRING_EQUALITY_PREDICATE
        46};
        47
        48
        49/**
        50 * Compares equality of two numbers, allowing them to differ up to a given
        51 * tolerance.
        52 * @param {number} var1 A number.
        53 * @param {number} var2 A number.
        54 * @param {number} tolerance the maximum allowed difference.
        55 * @return {boolean} Whether the two variables are sufficiently close.
        56 * @private
        57 */
        58goog.testing.asserts.numberRoughEqualityPredicate_ = function(
        59 var1, var2, tolerance) {
        60 return Math.abs(var1 - var2) <= tolerance;
        61};
        62
        63
        64/**
        65 * @type {Object<string, function(*, *, number): boolean>}
        66 * @private
        67 */
        68goog.testing.asserts.primitiveRoughEqualityPredicates_ = {
        69 'Number': goog.testing.asserts.numberRoughEqualityPredicate_
        70};
        71
        72
        73var _trueTypeOf = function(something) {
        74 var result = typeof something;
        75 try {
        76 switch (result) {
        77 case 'string':
        78 break;
        79 case 'boolean':
        80 break;
        81 case 'number':
        82 break;
        83 case 'object':
        84 if (something == null) {
        85 result = 'null';
        86 break;
        87 }
        88 case 'function':
        89 switch (something.constructor) {
        90 case new String('').constructor:
        91 result = 'String';
        92 break;
        93 case new Boolean(true).constructor:
        94 result = 'Boolean';
        95 break;
        96 case new Number(0).constructor:
        97 result = 'Number';
        98 break;
        99 case new Array().constructor:
        100 result = 'Array';
        101 break;
        102 case new RegExp().constructor:
        103 result = 'RegExp';
        104 break;
        105 case new Date().constructor:
        106 result = 'Date';
        107 break;
        108 case Function:
        109 result = 'Function';
        110 break;
        111 default:
        112 var m = something.constructor.toString().match(
        113 /function\s*([^( ]+)\(/);
        114 if (m) {
        115 result = m[1];
        116 } else {
        117 break;
        118 }
        119 }
        120 break;
        121 }
        122 } catch (e) {
        123
        124 } finally {
        125 result = result.substr(0, 1).toUpperCase() + result.substr(1);
        126 }
        127 return result;
        128};
        129
        130var _displayStringForValue = function(aVar) {
        131 var result;
        132 try {
        133 result = '<' + String(aVar) + '>';
        134 } catch (ex) {
        135 result = '<toString failed: ' + ex.message + '>';
        136 // toString does not work on this object :-(
        137 }
        138 if (!(aVar === null || aVar === JSUNIT_UNDEFINED_VALUE)) {
        139 result += ' (' + _trueTypeOf(aVar) + ')';
        140 }
        141 return result;
        142};
        143
        144var fail = function(failureMessage) {
        145 goog.testing.asserts.raiseException('Call to fail()', failureMessage);
        146};
        147
        148var argumentsIncludeComments = function(expectedNumberOfNonCommentArgs, args) {
        149 return args.length == expectedNumberOfNonCommentArgs + 1;
        150};
        151
        152var commentArg = function(expectedNumberOfNonCommentArgs, args) {
        153 if (argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)) {
        154 return args[0];
        155 }
        156
        157 return null;
        158};
        159
        160var nonCommentArg = function(desiredNonCommentArgIndex,
        161 expectedNumberOfNonCommentArgs, args) {
        162 return argumentsIncludeComments(expectedNumberOfNonCommentArgs, args) ?
        163 args[desiredNonCommentArgIndex] :
        164 args[desiredNonCommentArgIndex - 1];
        165};
        166
        167var _validateArguments = function(expectedNumberOfNonCommentArgs, args) {
        168 var valid = args.length == expectedNumberOfNonCommentArgs ||
        169 args.length == expectedNumberOfNonCommentArgs + 1 &&
        170 goog.isString(args[0]);
        171 _assert(null, valid, 'Incorrect arguments passed to assert function');
        172};
        173
        174var _assert = function(comment, booleanValue, failureMessage) {
        175 if (!booleanValue) {
        176 goog.testing.asserts.raiseException(comment, failureMessage);
        177 }
        178};
        179
        180
        181/**
        182 * @param {*} expected The expected value.
        183 * @param {*} actual The actual value.
        184 * @return {string} A failure message of the values don't match.
        185 * @private
        186 */
        187goog.testing.asserts.getDefaultErrorMsg_ = function(expected, actual) {
        188 var msg = 'Expected ' + _displayStringForValue(expected) + ' but was ' +
        189 _displayStringForValue(actual);
        190 if ((typeof expected == 'string') && (typeof actual == 'string')) {
        191 // Try to find a human-readable difference.
        192 var limit = Math.min(expected.length, actual.length);
        193 var commonPrefix = 0;
        194 while (commonPrefix < limit &&
        195 expected.charAt(commonPrefix) == actual.charAt(commonPrefix)) {
        196 commonPrefix++;
        197 }
        198
        199 var commonSuffix = 0;
        200 while (commonSuffix < limit &&
        201 expected.charAt(expected.length - commonSuffix - 1) ==
        202 actual.charAt(actual.length - commonSuffix - 1)) {
        203 commonSuffix++;
        204 }
        205
        206 if (commonPrefix + commonSuffix > limit) {
        207 commonSuffix = 0;
        208 }
        209
        210 if (commonPrefix > 2 || commonSuffix > 2) {
        211 var printString = function(str) {
        212 var startIndex = Math.max(0, commonPrefix - 2);
        213 var endIndex = Math.min(str.length, str.length - (commonSuffix - 2));
        214 return (startIndex > 0 ? '...' : '') +
        215 str.substring(startIndex, endIndex) +
        216 (endIndex < str.length ? '...' : '');
        217 };
        218
        219 msg += '\nDifference was at position ' + commonPrefix +
        220 '. Expected [' + printString(expected) +
        221 '] vs. actual [' + printString(actual) + ']';
        222 }
        223 }
        224 return msg;
        225};
        226
        227
        228/**
        229 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        230 * @param {*=} opt_b The value to assert (2 args only).
        231 */
        232var assert = function(a, opt_b) {
        233 _validateArguments(1, arguments);
        234 var comment = commentArg(1, arguments);
        235 var booleanValue = nonCommentArg(1, 1, arguments);
        236
        237 _assert(comment, goog.isBoolean(booleanValue),
        238 'Bad argument to assert(boolean)');
        239 _assert(comment, booleanValue, 'Call to assert(boolean) with false');
        240};
        241
        242
        243/**
        244 * Asserts that the function throws an error.
        245 *
        246 * @param {!(string|Function)} a The assertion comment or the function to call.
        247 * @param {!Function=} opt_b The function to call (if the first argument of
        248 * {@code assertThrows} was the comment).
        249 * @return {*} The error thrown by the function.
        250 * @throws {goog.testing.JsUnitException} If the assertion failed.
        251 */
        252var assertThrows = function(a, opt_b) {
        253 _validateArguments(1, arguments);
        254 var func = nonCommentArg(1, 1, arguments);
        255 var comment = commentArg(1, arguments);
        256 _assert(comment, typeof func == 'function',
        257 'Argument passed to assertThrows is not a function');
        258
        259 try {
        260 func();
        261 } catch (e) {
        262 if (e && goog.isString(e['stacktrace']) && goog.isString(e['message'])) {
        263 // Remove the stack trace appended to the error message by Opera 10.0
        264 var startIndex = e['message'].length - e['stacktrace'].length;
        265 if (e['message'].indexOf(e['stacktrace'], startIndex) == startIndex) {
        266 e['message'] = e['message'].substr(0, startIndex - 14);
        267 }
        268 }
        269 return e;
        270 }
        271 goog.testing.asserts.raiseException(comment,
        272 'No exception thrown from function passed to assertThrows');
        273};
        274
        275
        276/**
        277 * Asserts that the function does not throw an error.
        278 *
        279 * @param {!(string|Function)} a The assertion comment or the function to call.
        280 * @param {!Function=} opt_b The function to call (if the first argument of
        281 * {@code assertNotThrows} was the comment).
        282 * @return {*} The return value of the function.
        283 * @throws {goog.testing.JsUnitException} If the assertion failed.
        284 */
        285var assertNotThrows = function(a, opt_b) {
        286 _validateArguments(1, arguments);
        287 var comment = commentArg(1, arguments);
        288 var func = nonCommentArg(1, 1, arguments);
        289 _assert(comment, typeof func == 'function',
        290 'Argument passed to assertNotThrows is not a function');
        291
        292 try {
        293 return func();
        294 } catch (e) {
        295 comment = comment ? (comment + '\n') : '';
        296 comment += 'A non expected exception was thrown from function passed to ' +
        297 'assertNotThrows';
        298 // Some browsers don't have a stack trace so at least have the error
        299 // description.
        300 var stackTrace = e['stack'] || e['stacktrace'] || e.toString();
        301 goog.testing.asserts.raiseException(comment, stackTrace);
        302 }
        303};
        304
        305
        306/**
        307 * Asserts that the given callback function results in a JsUnitException when
        308 * called, and that the resulting failure message matches the given expected
        309 * message.
        310 * @param {function() : void} callback Function to be run expected to result
        311 * in a JsUnitException (usually contains a call to an assert).
        312 * @param {string=} opt_expectedMessage Failure message expected to be given
        313 * with the exception.
        314 */
        315var assertThrowsJsUnitException = function(callback, opt_expectedMessage) {
        316 var failed = false;
        317 try {
        318 goog.testing.asserts.callWithoutLogging(callback);
        319 } catch (ex) {
        320 if (!ex.isJsUnitException) {
        321 fail('Expected a JsUnitException');
        322 }
        323 if (typeof opt_expectedMessage != 'undefined' &&
        324 ex.message != opt_expectedMessage) {
        325 fail('Expected message [' + opt_expectedMessage + '] but got [' +
        326 ex.message + ']');
        327 }
        328 failed = true;
        329 }
        330 if (!failed) {
        331 fail('Expected a failure: ' + opt_expectedMessage);
        332 }
        333};
        334
        335
        336/**
        337 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        338 * @param {*=} opt_b The value to assert (2 args only).
        339 */
        340var assertTrue = function(a, opt_b) {
        341 _validateArguments(1, arguments);
        342 var comment = commentArg(1, arguments);
        343 var booleanValue = nonCommentArg(1, 1, arguments);
        344
        345 _assert(comment, goog.isBoolean(booleanValue),
        346 'Bad argument to assertTrue(boolean)');
        347 _assert(comment, booleanValue, 'Call to assertTrue(boolean) with false');
        348};
        349
        350
        351/**
        352 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        353 * @param {*=} opt_b The value to assert (2 args only).
        354 */
        355var assertFalse = function(a, opt_b) {
        356 _validateArguments(1, arguments);
        357 var comment = commentArg(1, arguments);
        358 var booleanValue = nonCommentArg(1, 1, arguments);
        359
        360 _assert(comment, goog.isBoolean(booleanValue),
        361 'Bad argument to assertFalse(boolean)');
        362 _assert(comment, !booleanValue, 'Call to assertFalse(boolean) with true');
        363};
        364
        365
        366/**
        367 * @param {*} a The expected value (2 args) or the debug message (3 args).
        368 * @param {*} b The actual value (2 args) or the expected value (3 args).
        369 * @param {*=} opt_c The actual value (3 args only).
        370 */
        371var assertEquals = function(a, b, opt_c) {
        372 _validateArguments(2, arguments);
        373 var var1 = nonCommentArg(1, 2, arguments);
        374 var var2 = nonCommentArg(2, 2, arguments);
        375 _assert(commentArg(2, arguments), var1 === var2,
        376 goog.testing.asserts.getDefaultErrorMsg_(var1, var2));
        377};
        378
        379
        380/**
        381 * @param {*} a The expected value (2 args) or the debug message (3 args).
        382 * @param {*} b The actual value (2 args) or the expected value (3 args).
        383 * @param {*=} opt_c The actual value (3 args only).
        384 */
        385var assertNotEquals = function(a, b, opt_c) {
        386 _validateArguments(2, arguments);
        387 var var1 = nonCommentArg(1, 2, arguments);
        388 var var2 = nonCommentArg(2, 2, arguments);
        389 _assert(commentArg(2, arguments), var1 !== var2,
        390 'Expected not to be ' + _displayStringForValue(var2));
        391};
        392
        393
        394/**
        395 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        396 * @param {*=} opt_b The value to assert (2 args only).
        397 */
        398var assertNull = function(a, opt_b) {
        399 _validateArguments(1, arguments);
        400 var aVar = nonCommentArg(1, 1, arguments);
        401 _assert(commentArg(1, arguments), aVar === null,
        402 goog.testing.asserts.getDefaultErrorMsg_(null, aVar));
        403};
        404
        405
        406/**
        407 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        408 * @param {*=} opt_b The value to assert (2 args only).
        409 */
        410var assertNotNull = function(a, opt_b) {
        411 _validateArguments(1, arguments);
        412 var aVar = nonCommentArg(1, 1, arguments);
        413 _assert(commentArg(1, arguments), aVar !== null,
        414 'Expected not to be ' + _displayStringForValue(null));
        415};
        416
        417
        418/**
        419 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        420 * @param {*=} opt_b The value to assert (2 args only).
        421 */
        422var assertUndefined = function(a, opt_b) {
        423 _validateArguments(1, arguments);
        424 var aVar = nonCommentArg(1, 1, arguments);
        425 _assert(commentArg(1, arguments), aVar === JSUNIT_UNDEFINED_VALUE,
        426 goog.testing.asserts.getDefaultErrorMsg_(JSUNIT_UNDEFINED_VALUE, aVar));
        427};
        428
        429
        430/**
        431 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        432 * @param {*=} opt_b The value to assert (2 args only).
        433 */
        434var assertNotUndefined = function(a, opt_b) {
        435 _validateArguments(1, arguments);
        436 var aVar = nonCommentArg(1, 1, arguments);
        437 _assert(commentArg(1, arguments), aVar !== JSUNIT_UNDEFINED_VALUE,
        438 'Expected not to be ' + _displayStringForValue(JSUNIT_UNDEFINED_VALUE));
        439};
        440
        441
        442/**
        443 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        444 * @param {*=} opt_b The value to assert (2 args only).
        445 */
        446var assertNotNullNorUndefined = function(a, opt_b) {
        447 _validateArguments(1, arguments);
        448 assertNotNull.apply(null, arguments);
        449 assertNotUndefined.apply(null, arguments);
        450};
        451
        452
        453/**
        454 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        455 * @param {*=} opt_b The value to assert (2 args only).
        456 */
        457var assertNonEmptyString = function(a, opt_b) {
        458 _validateArguments(1, arguments);
        459 var aVar = nonCommentArg(1, 1, arguments);
        460 _assert(commentArg(1, arguments),
        461 aVar !== JSUNIT_UNDEFINED_VALUE && aVar !== null &&
        462 typeof aVar == 'string' && aVar !== '',
        463 'Expected non-empty string but was ' + _displayStringForValue(aVar));
        464};
        465
        466
        467/**
        468 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        469 * @param {*=} opt_b The value to assert (2 args only).
        470 */
        471var assertNaN = function(a, opt_b) {
        472 _validateArguments(1, arguments);
        473 var aVar = nonCommentArg(1, 1, arguments);
        474 _assert(commentArg(1, arguments), isNaN(aVar), 'Expected NaN');
        475};
        476
        477
        478/**
        479 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        480 * @param {*=} opt_b The value to assert (2 args only).
        481 */
        482var assertNotNaN = function(a, opt_b) {
        483 _validateArguments(1, arguments);
        484 var aVar = nonCommentArg(1, 1, arguments);
        485 _assert(commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN');
        486};
        487
        488
        489/**
        490 * Runs a function in an environment where test failures are not logged. This is
        491 * useful for testing test code, where failures can be a normal part of a test.
        492 * @param {function() : void} fn Function to run without logging failures.
        493 */
        494goog.testing.asserts.callWithoutLogging = function(fn) {
        495 var testRunner = goog.global['G_testRunner'];
        496 var oldLogTestFailure = testRunner['logTestFailure'];
        497 try {
        498 // Any failures in the callback shouldn't be recorded.
        499 testRunner['logTestFailure'] = undefined;
        500 fn();
        501 } finally {
        502 testRunner['logTestFailure'] = oldLogTestFailure;
        503 }
        504};
        505
        506
        507/**
        508 * The return value of the equality predicate passed to findDifferences below,
        509 * in cases where the predicate can't test the input variables for equality.
        510 * @type {?string}
        511 */
        512goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS = null;
        513
        514
        515/**
        516 * The return value of the equality predicate passed to findDifferences below,
        517 * in cases where the input vriables are equal.
        518 * @type {?string}
        519 */
        520goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL = '';
        521
        522
        523/**
        524 * Determines if two items of any type match, and formulates an error message
        525 * if not.
        526 * @param {*} expected Expected argument to match.
        527 * @param {*} actual Argument as a result of performing the test.
        528 * @param {(function(string, *, *): ?string)=} opt_equalityPredicate An optional
        529 * function that can be used to check equality of variables. It accepts 3
        530 * arguments: type-of-variables, var1, var2 (in that order) and returns an
        531 * error message if the variables are not equal,
        532 * goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL if the variables
        533 * are equal, or
        534 * goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS if the predicate
        535 * couldn't check the input variables. The function will be called only if
        536 * the types of var1 and var2 are identical.
        537 * @return {?string} Null on success, error message on failure.
        538 */
        539goog.testing.asserts.findDifferences = function(expected, actual,
        540 opt_equalityPredicate) {
        541 var failures = [];
        542 var seen1 = [];
        543 var seen2 = [];
        544
        545 // To avoid infinite recursion when the two parameters are self-referential
        546 // along the same path of properties, keep track of the object pairs already
        547 // seen in this call subtree, and abort when a cycle is detected.
        548 function innerAssertWithCycleCheck(var1, var2, path) {
        549 // This is used for testing, so we can afford to be slow (but more
        550 // accurate). So we just check whether var1 is in seen1. If we
        551 // found var1 in index i, we simply need to check whether var2 is
        552 // in seen2[i]. If it is, we do not recurse to check var1/var2. If
        553 // it isn't, we know that the structures of the two objects must be
        554 // different.
        555 //
        556 // This is based on the fact that values at index i in seen1 and
        557 // seen2 will be checked for equality eventually (when
        558 // innerAssertImplementation(seen1[i], seen2[i], path) finishes).
        559 for (var i = 0; i < seen1.length; ++i) {
        560 var match1 = seen1[i] === var1;
        561 var match2 = seen2[i] === var2;
        562 if (match1 || match2) {
        563 if (!match1 || !match2) {
        564 // Asymmetric cycles, so the objects have different structure.
        565 failures.push('Asymmetric cycle detected at ' + path);
        566 }
        567 return;
        568 }
        569 }
        570
        571 seen1.push(var1);
        572 seen2.push(var2);
        573 innerAssertImplementation(var1, var2, path);
        574 seen1.pop();
        575 seen2.pop();
        576 }
        577
        578 var equalityPredicate = opt_equalityPredicate || function(type, var1, var2) {
        579 var typedPredicate = PRIMITIVE_EQUALITY_PREDICATES[type];
        580 if (!typedPredicate) {
        581 return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS;
        582 }
        583 var equal = typedPredicate(var1, var2);
        584 return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL :
        585 goog.testing.asserts.getDefaultErrorMsg_(var1, var2);
        586 };
        587
        588 /**
        589 * @param {*} var1 An item in the expected object.
        590 * @param {*} var2 The corresponding item in the actual object.
        591 * @param {string} path Their path in the objects.
        592 * @suppress {missingProperties} The map_ property is unknown to the compiler
        593 * unless goog.structs.Map is loaded.
        594 */
        595 function innerAssertImplementation(var1, var2, path) {
        596 if (var1 === var2) {
        597 return;
        598 }
        599
        600 var typeOfVar1 = _trueTypeOf(var1);
        601 var typeOfVar2 = _trueTypeOf(var2);
        602
        603 if (typeOfVar1 == typeOfVar2) {
        604 var isArray = typeOfVar1 == 'Array';
        605 var errorMessage = equalityPredicate(typeOfVar1, var1, var2);
        606 if (errorMessage !=
        607 goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS) {
        608 if (errorMessage !=
        609 goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL) {
        610 failures.push(path + ': ' + errorMessage);
        611 }
        612 } else if (isArray && var1.length != var2.length) {
        613 failures.push(path + ': Expected ' + var1.length + '-element array ' +
        614 'but got a ' + var2.length + '-element array');
        615 } else {
        616 var childPath = path + (isArray ? '[%s]' : (path ? '.%s' : '%s'));
        617
        618 // if an object has an __iterator__ property, we have no way of
        619 // actually inspecting its raw properties, and JS 1.7 doesn't
        620 // overload [] to make it possible for someone to generically
        621 // use what the iterator returns to compare the object-managed
        622 // properties. This gets us into deep poo with things like
        623 // goog.structs.Map, at least on systems that support iteration.
        624 if (!var1['__iterator__']) {
        625 for (var prop in var1) {
        626 if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) {
        627 // Skip array indices for now. We'll handle them later.
        628 continue;
        629 }
        630
        631 if (prop in var2) {
        632 innerAssertWithCycleCheck(var1[prop], var2[prop],
        633 childPath.replace('%s', prop));
        634 } else {
        635 failures.push('property ' + prop +
        636 ' not present in actual ' + (path || typeOfVar2));
        637 }
        638 }
        639 // make sure there aren't properties in var2 that are missing
        640 // from var1. if there are, then by definition they don't
        641 // match.
        642 for (var prop in var2) {
        643 if (isArray && goog.testing.asserts.isArrayIndexProp_(prop)) {
        644 // Skip array indices for now. We'll handle them later.
        645 continue;
        646 }
        647
        648 if (!(prop in var1)) {
        649 failures.push('property ' + prop +
        650 ' not present in expected ' +
        651 (path || typeOfVar1));
        652 }
        653 }
        654
        655 // Handle array indices by iterating from 0 to arr.length.
        656 //
        657 // Although all browsers allow holes in arrays, browsers
        658 // are inconsistent in what they consider a hole. For example,
        659 // "[0,undefined,2]" has a hole on IE but not on Firefox.
        660 //
        661 // Because our style guide bans for...in iteration over arrays,
        662 // we assume that most users don't care about holes in arrays,
        663 // and that it is ok to say that a hole is equivalent to a slot
        664 // populated with 'undefined'.
        665 if (isArray) {
        666 for (prop = 0; prop < var1.length; prop++) {
        667 innerAssertWithCycleCheck(var1[prop], var2[prop],
        668 childPath.replace('%s', String(prop)));
        669 }
        670 }
        671 } else {
        672 // special-case for closure objects that have iterators
        673 if (goog.isFunction(var1.equals)) {
        674 // use the object's own equals function, assuming it accepts an
        675 // object and returns a boolean
        676 if (!var1.equals(var2)) {
        677 failures.push('equals() returned false for ' +
        678 (path || typeOfVar1));
        679 }
        680 } else if (var1.map_) {
        681 // assume goog.structs.Map or goog.structs.Set, where comparing
        682 // their private map_ field is sufficient
        683 innerAssertWithCycleCheck(var1.map_, var2.map_,
        684 childPath.replace('%s', 'map_'));
        685 } else {
        686 // else die, so user knows we can't do anything
        687 failures.push('unable to check ' + (path || typeOfVar1) +
        688 ' for equality: it has an iterator we do not ' +
        689 'know how to handle. please add an equals method');
        690 }
        691 }
        692 }
        693 } else {
        694 failures.push(path + ' ' +
        695 goog.testing.asserts.getDefaultErrorMsg_(var1, var2));
        696 }
        697 }
        698
        699 innerAssertWithCycleCheck(expected, actual, '');
        700 return failures.length == 0 ? null :
        701 goog.testing.asserts.getDefaultErrorMsg_(expected, actual) +
        702 '\n ' + failures.join('\n ');
        703};
        704
        705
        706/**
        707 * Notes:
        708 * Object equality has some nasty browser quirks, and this implementation is
        709 * not 100% correct. For example,
        710 *
        711 * <code>
        712 * var a = [0, 1, 2];
        713 * var b = [0, 1, 2];
        714 * delete a[1];
        715 * b[1] = undefined;
        716 * assertObjectEquals(a, b); // should fail, but currently passes
        717 * </code>
        718 *
        719 * See asserts_test.html for more interesting edge cases.
        720 *
        721 * The first comparison object provided is the expected value, the second is
        722 * the actual.
        723 *
        724 * @param {*} a Assertion message or comparison object.
        725 * @param {*} b Comparison object.
        726 * @param {*=} opt_c Comparison object, if an assertion message was provided.
        727 */
        728var assertObjectEquals = function(a, b, opt_c) {
        729 _validateArguments(2, arguments);
        730 var v1 = nonCommentArg(1, 2, arguments);
        731 var v2 = nonCommentArg(2, 2, arguments);
        732 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
        733 var differences = goog.testing.asserts.findDifferences(v1, v2);
        734
        735 _assert(failureMessage, !differences, differences);
        736};
        737
        738
        739/**
        740 * Similar to assertObjectEquals above, but accepts a tolerance margin.
        741 *
        742 * @param {*} a Assertion message or comparison object.
        743 * @param {*} b Comparison object.
        744 * @param {*} c Comparison object or tolerance.
        745 * @param {*=} opt_d Tolerance, if an assertion message was provided.
        746 */
        747var assertObjectRoughlyEquals = function(a, b, c, opt_d) {
        748 _validateArguments(3, arguments);
        749 var v1 = nonCommentArg(1, 3, arguments);
        750 var v2 = nonCommentArg(2, 3, arguments);
        751 var tolerance = nonCommentArg(3, 3, arguments);
        752 var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : '';
        753 var equalityPredicate = function(type, var1, var2) {
        754 var typedPredicate =
        755 goog.testing.asserts.primitiveRoughEqualityPredicates_[type];
        756 if (!typedPredicate) {
        757 return goog.testing.asserts.EQUALITY_PREDICATE_CANT_PROCESS;
        758 }
        759 var equal = typedPredicate(var1, var2, tolerance);
        760 return equal ? goog.testing.asserts.EQUALITY_PREDICATE_VARS_ARE_EQUAL :
        761 goog.testing.asserts.getDefaultErrorMsg_(var1, var2) +
        762 ' which was more than ' + tolerance + ' away';
        763 };
        764 var differences = goog.testing.asserts.findDifferences(
        765 v1, v2, equalityPredicate);
        766
        767 _assert(failureMessage, !differences, differences);
        768};
        769
        770
        771/**
        772 * Compares two arbitrary objects for non-equalness.
        773 *
        774 * All the same caveats as for assertObjectEquals apply here:
        775 * Undefined values may be confused for missing values, or vice versa.
        776 *
        777 * @param {*} a Assertion message or comparison object.
        778 * @param {*} b Comparison object.
        779 * @param {*=} opt_c Comparison object, if an assertion message was provided.
        780 */
        781var assertObjectNotEquals = function(a, b, opt_c) {
        782 _validateArguments(2, arguments);
        783 var v1 = nonCommentArg(1, 2, arguments);
        784 var v2 = nonCommentArg(2, 2, arguments);
        785 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
        786 var differences = goog.testing.asserts.findDifferences(v1, v2);
        787
        788 _assert(failureMessage, differences, 'Objects should not be equal');
        789};
        790
        791
        792/**
        793 * Compares two arrays ignoring negative indexes and extra properties on the
        794 * array objects. Use case: Internet Explorer adds the index, lastIndex and
        795 * input enumerable fields to the result of string.match(/regexp/g), which makes
        796 * assertObjectEquals fail.
        797 * @param {*} a The expected array (2 args) or the debug message (3 args).
        798 * @param {*} b The actual array (2 args) or the expected array (3 args).
        799 * @param {*=} opt_c The actual array (3 args only).
        800 */
        801var assertArrayEquals = function(a, b, opt_c) {
        802 _validateArguments(2, arguments);
        803 var v1 = nonCommentArg(1, 2, arguments);
        804 var v2 = nonCommentArg(2, 2, arguments);
        805 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
        806
        807 var typeOfVar1 = _trueTypeOf(v1);
        808 _assert(failureMessage,
        809 typeOfVar1 == 'Array',
        810 'Expected an array for assertArrayEquals but found a ' + typeOfVar1);
        811
        812 var typeOfVar2 = _trueTypeOf(v2);
        813 _assert(failureMessage,
        814 typeOfVar2 == 'Array',
        815 'Expected an array for assertArrayEquals but found a ' + typeOfVar2);
        816
        817 assertObjectEquals(failureMessage,
        818 Array.prototype.concat.call(v1), Array.prototype.concat.call(v2));
        819};
        820
        821
        822/**
        823 * Compares two objects that can be accessed like an array and assert that
        824 * each element is equal.
        825 * @param {string|Object} a Failure message (3 arguments)
        826 * or object #1 (2 arguments).
        827 * @param {Object} b Object #1 (2 arguments) or object #2 (3 arguments).
        828 * @param {Object=} opt_c Object #2 (3 arguments).
        829 */
        830var assertElementsEquals = function(a, b, opt_c) {
        831 _validateArguments(2, arguments);
        832
        833 var v1 = nonCommentArg(1, 2, arguments);
        834 var v2 = nonCommentArg(2, 2, arguments);
        835 var failureMessage = commentArg(2, arguments) ? commentArg(2, arguments) : '';
        836
        837 if (!v1) {
        838 assert(failureMessage, !v2);
        839 } else {
        840 assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length);
        841 for (var i = 0; i < v1.length; ++i) {
        842 assertEquals(
        843 'mismatch at index ' + i + ': ' + failureMessage, v1[i], v2[i]);
        844 }
        845 }
        846};
        847
        848
        849/**
        850 * Compares two objects that can be accessed like an array and assert that
        851 * each element is roughly equal.
        852 * @param {string|Object} a Failure message (4 arguments)
        853 * or object #1 (3 arguments).
        854 * @param {Object} b Object #1 (4 arguments) or object #2 (3 arguments).
        855 * @param {Object|number} c Object #2 (4 arguments) or tolerance (3 arguments).
        856 * @param {number=} opt_d tolerance (4 arguments).
        857 */
        858var assertElementsRoughlyEqual = function(a, b, c, opt_d) {
        859 _validateArguments(3, arguments);
        860
        861 var v1 = nonCommentArg(1, 3, arguments);
        862 var v2 = nonCommentArg(2, 3, arguments);
        863 var tolerance = nonCommentArg(3, 3, arguments);
        864 var failureMessage = commentArg(3, arguments) ? commentArg(3, arguments) : '';
        865
        866 if (!v1) {
        867 assert(failureMessage, !v2);
        868 } else {
        869 assertEquals('length mismatch: ' + failureMessage, v1.length, v2.length);
        870 for (var i = 0; i < v1.length; ++i) {
        871 assertRoughlyEquals(failureMessage, v1[i], v2[i], tolerance);
        872 }
        873 }
        874};
        875
        876
        877/**
        878 * Compares two array-like objects without taking their order into account.
        879 * @param {string|goog.testing.asserts.ArrayLike} a Assertion message or the
        880 * expected elements.
        881 * @param {goog.testing.asserts.ArrayLike} b Expected elements or the actual
        882 * elements.
        883 * @param {goog.testing.asserts.ArrayLike=} opt_c Actual elements.
        884 */
        885var assertSameElements = function(a, b, opt_c) {
        886 _validateArguments(2, arguments);
        887 var expected = nonCommentArg(1, 2, arguments);
        888 var actual = nonCommentArg(2, 2, arguments);
        889 var message = commentArg(2, arguments);
        890
        891 assertTrue('Bad arguments to assertSameElements(opt_message, expected: ' +
        892 'ArrayLike, actual: ArrayLike)',
        893 goog.isArrayLike(expected) && goog.isArrayLike(actual));
        894
        895 // Clones expected and actual and converts them to real arrays.
        896 expected = goog.testing.asserts.toArray_(expected);
        897 actual = goog.testing.asserts.toArray_(actual);
        898 // TODO(user): It would be great to show only the difference
        899 // between the expected and actual elements.
        900 _assert(message, expected.length == actual.length,
        901 'Expected ' + expected.length + ' elements: [' + expected + '], ' +
        902 'got ' + actual.length + ' elements: [' + actual + ']');
        903
        904 var toFind = goog.testing.asserts.toArray_(expected);
        905 for (var i = 0; i < actual.length; i++) {
        906 var index = goog.testing.asserts.indexOf_(toFind, actual[i]);
        907 _assert(message, index != -1, 'Expected [' + expected + '], got [' +
        908 actual + ']');
        909 toFind.splice(index, 1);
        910 }
        911};
        912
        913
        914/**
        915 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        916 * @param {*=} opt_b The value to assert (2 args only).
        917 */
        918var assertEvaluatesToTrue = function(a, opt_b) {
        919 _validateArguments(1, arguments);
        920 var value = nonCommentArg(1, 1, arguments);
        921 if (!value) {
        922 _assert(commentArg(1, arguments), false, 'Expected to evaluate to true');
        923 }
        924};
        925
        926
        927/**
        928 * @param {*} a The value to assert (1 arg) or debug message (2 args).
        929 * @param {*=} opt_b The value to assert (2 args only).
        930 */
        931var assertEvaluatesToFalse = function(a, opt_b) {
        932 _validateArguments(1, arguments);
        933 var value = nonCommentArg(1, 1, arguments);
        934 if (value) {
        935 _assert(commentArg(1, arguments), false, 'Expected to evaluate to false');
        936 }
        937};
        938
        939
        940/**
        941 * Compares two HTML snippets.
        942 *
        943 * Take extra care if attributes are involved. {@code assertHTMLEquals}'s
        944 * implementation isn't prepared for complex cases. For example, the following
        945 * comparisons erroneously fail:
        946 * <pre>
        947 * assertHTMLEquals('<a href="x" target="y">', '<a target="y" href="x">');
        948 * assertHTMLEquals('<div classname="a b">', '<div classname="b a">');
        949 * assertHTMLEquals('<input disabled>', '<input disabled="disabled">');
        950 * </pre>
        951 *
        952 * When in doubt, use {@code goog.testing.dom.assertHtmlMatches}.
        953 *
        954 * @param {*} a The expected value (2 args) or the debug message (3 args).
        955 * @param {*} b The actual value (2 args) or the expected value (3 args).
        956 * @param {*=} opt_c The actual value (3 args only).
        957 */
        958var assertHTMLEquals = function(a, b, opt_c) {
        959 _validateArguments(2, arguments);
        960 var var1 = nonCommentArg(1, 2, arguments);
        961 var var2 = nonCommentArg(2, 2, arguments);
        962 var var1Standardized = standardizeHTML(var1);
        963 var var2Standardized = standardizeHTML(var2);
        964
        965 _assert(commentArg(2, arguments), var1Standardized === var2Standardized,
        966 goog.testing.asserts.getDefaultErrorMsg_(
        967 var1Standardized, var2Standardized));
        968};
        969
        970
        971/**
        972 * Compares two CSS property values to make sure that they represent the same
        973 * things. This will normalize values in the browser. For example, in Firefox,
        974 * this assertion will consider "rgb(0, 0, 255)" and "#0000ff" to be identical
        975 * values for the "color" property. This function won't normalize everything --
        976 * for example, in most browsers, "blue" will not match "#0000ff". It is
        977 * intended only to compensate for unexpected normalizations performed by
        978 * the browser that should also affect your expected value.
        979 * @param {string} a Assertion message, or the CSS property name.
        980 * @param {string} b CSS property name, or the expected value.
        981 * @param {string} c The expected value, or the actual value.
        982 * @param {string=} opt_d The actual value.
        983 */
        984var assertCSSValueEquals = function(a, b, c, opt_d) {
        985 _validateArguments(3, arguments);
        986 var propertyName = nonCommentArg(1, 3, arguments);
        987 var expectedValue = nonCommentArg(2, 3, arguments);
        988 var actualValue = nonCommentArg(3, 3, arguments);
        989 var expectedValueStandardized =
        990 standardizeCSSValue(propertyName, expectedValue);
        991 var actualValueStandardized =
        992 standardizeCSSValue(propertyName, actualValue);
        993
        994 _assert(commentArg(3, arguments),
        995 expectedValueStandardized == actualValueStandardized,
        996 goog.testing.asserts.getDefaultErrorMsg_(
        997 expectedValueStandardized, actualValueStandardized));
        998};
        999
        1000
        1001/**
        1002 * @param {*} a The expected value (2 args) or the debug message (3 args).
        1003 * @param {*} b The actual value (2 args) or the expected value (3 args).
        1004 * @param {*=} opt_c The actual value (3 args only).
        1005 */
        1006var assertHashEquals = function(a, b, opt_c) {
        1007 _validateArguments(2, arguments);
        1008 var var1 = nonCommentArg(1, 2, arguments);
        1009 var var2 = nonCommentArg(2, 2, arguments);
        1010 var message = commentArg(2, arguments);
        1011 for (var key in var1) {
        1012 _assert(message,
        1013 key in var2, 'Expected hash had key ' + key + ' that was not found');
        1014 _assert(message, var1[key] == var2[key], 'Value for key ' + key +
        1015 ' mismatch - expected = ' + var1[key] + ', actual = ' + var2[key]);
        1016 }
        1017
        1018 for (var key in var2) {
        1019 _assert(message, key in var1, 'Actual hash had key ' + key +
        1020 ' that was not expected');
        1021 }
        1022};
        1023
        1024
        1025/**
        1026 * @param {*} a The expected value (3 args) or the debug message (4 args).
        1027 * @param {*} b The actual value (3 args) or the expected value (4 args).
        1028 * @param {*} c The tolerance (3 args) or the actual value (4 args).
        1029 * @param {*=} opt_d The tolerance (4 args only).
        1030 */
        1031var assertRoughlyEquals = function(a, b, c, opt_d) {
        1032 _validateArguments(3, arguments);
        1033 var expected = nonCommentArg(1, 3, arguments);
        1034 var actual = nonCommentArg(2, 3, arguments);
        1035 var tolerance = nonCommentArg(3, 3, arguments);
        1036 _assert(commentArg(3, arguments),
        1037 goog.testing.asserts.numberRoughEqualityPredicate_(
        1038 expected, actual, tolerance),
        1039 'Expected ' + expected + ', but got ' + actual +
        1040 ' which was more than ' + tolerance + ' away');
        1041};
        1042
        1043
        1044/**
        1045 * Checks if the test value is a member of the given container. Uses
        1046 * container.indexOf as the underlying function, so this works for strings
        1047 * and arrays.
        1048 * @param {*} a Failure message (3 arguments) or the test value
        1049 * (2 arguments).
        1050 * @param {*} b The test value (3 arguments) or the container
        1051 * (2 arguments).
        1052 * @param {*=} opt_c The container.
        1053 */
        1054var assertContains = function(a, b, opt_c) {
        1055 _validateArguments(2, arguments);
        1056 var contained = nonCommentArg(1, 2, arguments);
        1057 var container = nonCommentArg(2, 2, arguments);
        1058 _assert(commentArg(2, arguments),
        1059 goog.testing.asserts.contains_(container, contained),
        1060 'Expected \'' + container + '\' to contain \'' + contained + '\'');
        1061};
        1062
        1063
        1064/**
        1065 * Checks if the given element is not the member of the given container.
        1066 * @param {*} a Failure message (3 arguments) or the contained element
        1067 * (2 arguments).
        1068 * @param {*} b The contained element (3 arguments) or the container
        1069 * (2 arguments).
        1070 * @param {*=} opt_c The container.
        1071 */
        1072var assertNotContains = function(a, b, opt_c) {
        1073 _validateArguments(2, arguments);
        1074 var contained = nonCommentArg(1, 2, arguments);
        1075 var container = nonCommentArg(2, 2, arguments);
        1076 _assert(commentArg(2, arguments),
        1077 !goog.testing.asserts.contains_(container, contained),
        1078 'Expected \'' + container + '\' not to contain \'' + contained + '\'');
        1079};
        1080
        1081
        1082/**
        1083 * Checks if the given string matches the given regular expression.
        1084 * @param {*} a Failure message (3 arguments) or the expected regular
        1085 * expression as a string or RegExp (2 arguments).
        1086 * @param {*} b The regular expression (3 arguments) or the string to test
        1087 * (2 arguments).
        1088 * @param {*=} opt_c The string to test.
        1089 */
        1090var assertRegExp = function(a, b, opt_c) {
        1091 _validateArguments(2, arguments);
        1092 var regexp = nonCommentArg(1, 2, arguments);
        1093 var string = nonCommentArg(2, 2, arguments);
        1094 if (typeof(regexp) == 'string') {
        1095 regexp = new RegExp(regexp);
        1096 }
        1097 _assert(commentArg(2, arguments),
        1098 regexp.test(string),
        1099 'Expected \'' + string + '\' to match RegExp ' + regexp.toString());
        1100};
        1101
        1102
        1103/**
        1104 * Converts an array like object to array or clones it if it's already array.
        1105 * @param {goog.testing.asserts.ArrayLike} arrayLike The collection.
        1106 * @return {!Array<?>} Copy of the collection as array.
        1107 * @private
        1108 */
        1109goog.testing.asserts.toArray_ = function(arrayLike) {
        1110 var ret = [];
        1111 for (var i = 0; i < arrayLike.length; i++) {
        1112 ret[i] = arrayLike[i];
        1113 }
        1114 return ret;
        1115};
        1116
        1117
        1118/**
        1119 * Finds the position of the first occurrence of an element in a container.
        1120 * @param {goog.testing.asserts.ArrayLike} container
        1121 * The array to find the element in.
        1122 * @param {*} contained Element to find.
        1123 * @return {number} Index of the first occurrence or -1 if not found.
        1124 * @private
        1125 */
        1126goog.testing.asserts.indexOf_ = function(container, contained) {
        1127 if (container.indexOf) {
        1128 return container.indexOf(contained);
        1129 } else {
        1130 // IE6/7 do not have indexOf so do a search.
        1131 for (var i = 0; i < container.length; i++) {
        1132 if (container[i] === contained) {
        1133 return i;
        1134 }
        1135 }
        1136 return -1;
        1137 }
        1138};
        1139
        1140
        1141/**
        1142 * Tells whether the array contains the given element.
        1143 * @param {goog.testing.asserts.ArrayLike} container The array to
        1144 * find the element in.
        1145 * @param {*} contained Element to find.
        1146 * @return {boolean} Whether the element is in the array.
        1147 * @private
        1148 */
        1149goog.testing.asserts.contains_ = function(container, contained) {
        1150 // TODO(user): Can we check for container.contains as well?
        1151 // That would give us support for most goog.structs (though weird results
        1152 // with anything else with a contains method, like goog.math.Range). Falling
        1153 // back with container.some would catch all iterables, too.
        1154 return goog.testing.asserts.indexOf_(container, contained) != -1;
        1155};
        1156
        1157var standardizeHTML = function(html) {
        1158 var translator = document.createElement('DIV');
        1159 translator.innerHTML = html;
        1160
        1161 // Trim whitespace from result (without relying on goog.string)
        1162 return translator.innerHTML.replace(/^\s+|\s+$/g, '');
        1163};
        1164
        1165
        1166/**
        1167 * Standardizes a CSS value for a given property by applying it to an element
        1168 * and then reading it back.
        1169 * @param {string} propertyName CSS property name.
        1170 * @param {string} value CSS value.
        1171 * @return {string} Normalized CSS value.
        1172 */
        1173var standardizeCSSValue = function(propertyName, value) {
        1174 var styleDeclaration = document.createElement('DIV').style;
        1175 styleDeclaration[propertyName] = value;
        1176 return styleDeclaration[propertyName];
        1177};
        1178
        1179
        1180/**
        1181 * Raises a JsUnit exception with the given comment.
        1182 * @param {string} comment A summary for the exception.
        1183 * @param {string=} opt_message A description of the exception.
        1184 */
        1185goog.testing.asserts.raiseException = function(comment, opt_message) {
        1186 throw new goog.testing.JsUnitException(comment, opt_message);
        1187};
        1188
        1189
        1190/**
        1191 * Helper function for assertObjectEquals.
        1192 * @param {string} prop A property name.
        1193 * @return {boolean} If the property name is an array index.
        1194 * @private
        1195 */
        1196goog.testing.asserts.isArrayIndexProp_ = function(prop) {
        1197 return (prop | 0) == prop;
        1198};
        1199
        1200
        1201
        1202/**
        1203 * @param {string} comment A summary for the exception.
        1204 * @param {?string=} opt_message A description of the exception.
        1205 * @constructor
        1206 * @extends {Error}
        1207 * @final
        1208 */
        1209goog.testing.JsUnitException = function(comment, opt_message) {
        1210 this.isJsUnitException = true;
        1211 this.message = (comment ? comment : '') +
        1212 (comment && opt_message ? '\n' : '') +
        1213 (opt_message ? opt_message : '');
        1214 this.stackTrace = goog.testing.stacktrace.get();
        1215 // These fields are for compatibility with jsUnitTestManager.
        1216 this.comment = comment || null;
        1217 this.jsUnitMessage = opt_message || '';
        1218
        1219 // Ensure there is a stack trace.
        1220 if (Error.captureStackTrace) {
        1221 Error.captureStackTrace(this, goog.testing.JsUnitException);
        1222 } else {
        1223 this.stack = new Error().stack || '';
        1224 }
        1225};
        1226goog.inherits(goog.testing.JsUnitException, Error);
        1227
        1228
        1229/** @override */
        1230goog.testing.JsUnitException.prototype.toString = function() {
        1231 return this.message;
        1232};
        1233
        1234
        1235goog.exportSymbol('fail', fail);
        1236goog.exportSymbol('assert', assert);
        1237goog.exportSymbol('assertThrows', assertThrows);
        1238goog.exportSymbol('assertNotThrows', assertNotThrows);
        1239goog.exportSymbol('assertTrue', assertTrue);
        1240goog.exportSymbol('assertFalse', assertFalse);
        1241goog.exportSymbol('assertEquals', assertEquals);
        1242goog.exportSymbol('assertNotEquals', assertNotEquals);
        1243goog.exportSymbol('assertNull', assertNull);
        1244goog.exportSymbol('assertNotNull', assertNotNull);
        1245goog.exportSymbol('assertUndefined', assertUndefined);
        1246goog.exportSymbol('assertNotUndefined', assertNotUndefined);
        1247goog.exportSymbol('assertNotNullNorUndefined', assertNotNullNorUndefined);
        1248goog.exportSymbol('assertNonEmptyString', assertNonEmptyString);
        1249goog.exportSymbol('assertNaN', assertNaN);
        1250goog.exportSymbol('assertNotNaN', assertNotNaN);
        1251goog.exportSymbol('assertObjectEquals', assertObjectEquals);
        1252goog.exportSymbol('assertObjectRoughlyEquals', assertObjectRoughlyEquals);
        1253goog.exportSymbol('assertObjectNotEquals', assertObjectNotEquals);
        1254goog.exportSymbol('assertArrayEquals', assertArrayEquals);
        1255goog.exportSymbol('assertElementsEquals', assertElementsEquals);
        1256goog.exportSymbol('assertElementsRoughlyEqual', assertElementsRoughlyEqual);
        1257goog.exportSymbol('assertSameElements', assertSameElements);
        1258goog.exportSymbol('assertEvaluatesToTrue', assertEvaluatesToTrue);
        1259goog.exportSymbol('assertEvaluatesToFalse', assertEvaluatesToFalse);
        1260goog.exportSymbol('assertHTMLEquals', assertHTMLEquals);
        1261goog.exportSymbol('assertHashEquals', assertHashEquals);
        1262goog.exportSymbol('assertRoughlyEquals', assertRoughlyEquals);
        1263goog.exportSymbol('assertContains', assertContains);
        1264goog.exportSymbol('assertNotContains', assertNotContains);
        1265goog.exportSymbol('assertRegExp', assertRegExp);
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/asynctestcase.js.src.html b/docs/source/lib/goog/testing/asynctestcase.js.src.html new file mode 100644 index 0000000..0c164e4 --- /dev/null +++ b/docs/source/lib/goog/testing/asynctestcase.js.src.html @@ -0,0 +1 @@ +asynctestcase.js

        lib/goog/testing/asynctestcase.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14// All Rights Reserved.
        15
        16/**
        17 * @fileoverview A class representing a set of test functions that use
        18 * asynchronous functions that cannot be meaningfully mocked.
        19 *
        20 * To create a Google-compatable JsUnit test using this test case, put the
        21 * following snippet in your test:
        22 *
        23 * var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
        24 *
        25 * To make the test runner wait for your asynchronous behaviour, use:
        26 *
        27 * asyncTestCase.waitForAsync('Waiting for xhr to respond');
        28 *
        29 * The next test will not start until the following call is made, or a
        30 * timeout occurs:
        31 *
        32 * asyncTestCase.continueTesting();
        33 *
        34 * There does NOT need to be a 1:1 mapping of waitForAsync calls and
        35 * continueTesting calls. The next test will be run after a single call to
        36 * continueTesting is made, as long as there is no subsequent call to
        37 * waitForAsync in the same thread.
        38 *
        39 * Example:
        40 * // Returning here would cause the next test to be run.
        41 * asyncTestCase.waitForAsync('description 1');
        42 * // Returning here would *not* cause the next test to be run.
        43 * // Only effect of additional waitForAsync() calls is an updated
        44 * // description in the case of a timeout.
        45 * asyncTestCase.waitForAsync('updated description');
        46 * asyncTestCase.continueTesting();
        47 * // Returning here would cause the next test to be run.
        48 * asyncTestCase.waitForAsync('just kidding, still running.');
        49 * // Returning here would *not* cause the next test to be run.
        50 *
        51 * The test runner can also be made to wait for more than one asynchronous
        52 * event with:
        53 *
        54 * asyncTestCase.waitForSignals(n);
        55 *
        56 * The next test will not start until asyncTestCase.signal() is called n times,
        57 * or the test step timeout is exceeded.
        58 *
        59 * This class supports asynchronous behaviour in all test functions except for
        60 * tearDownPage. If such support is needed, it can be added.
        61 *
        62 * Example Usage:
        63 *
        64 * var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
        65 * // Optionally, set a longer-than-normal step timeout.
        66 * asyncTestCase.stepTimeout = 30 * 1000;
        67 *
        68 * function testSetTimeout() {
        69 * var step = 0;
        70 * function stepCallback() {
        71 * step++;
        72 * switch (step) {
        73 * case 1:
        74 * var startTime = goog.now();
        75 * asyncTestCase.waitForAsync('step 1');
        76 * window.setTimeout(stepCallback, 100);
        77 * break;
        78 * case 2:
        79 * assertTrue('Timeout fired too soon',
        80 * goog.now() - startTime >= 100);
        81 * asyncTestCase.waitForAsync('step 2');
        82 * window.setTimeout(stepCallback, 100);
        83 * break;
        84 * case 3:
        85 * assertTrue('Timeout fired too soon',
        86 * goog.now() - startTime >= 200);
        87 * asyncTestCase.continueTesting();
        88 * break;
        89 * default:
        90 * fail('Unexpected call to stepCallback');
        91 * }
        92 * }
        93 * stepCallback();
        94 * }
        95 *
        96 * Known Issues:
        97 * IE7 Exceptions:
        98 * As the failingtest.html will show, it appears as though ie7 does not
        99 * propagate an exception past a function called using the func.call()
        100 * syntax. This causes case 3 of the failing tests (exceptions) to show up
        101 * as timeouts in IE.
        102 * window.onerror:
        103 * This seems to catch errors only in ff2/ff3. It does not work in Safari or
        104 * IE7. The consequence of this is that exceptions that would have been
        105 * caught by window.onerror show up as timeouts.
        106 *
        107 * @author agrieve@google.com (Andrew Grieve)
        108 */
        109
        110goog.provide('goog.testing.AsyncTestCase');
        111goog.provide('goog.testing.AsyncTestCase.ControlBreakingException');
        112
        113goog.require('goog.testing.TestCase');
        114goog.require('goog.testing.TestCase.Test');
        115goog.require('goog.testing.asserts');
        116
        117
        118
        119/**
        120 * A test case that is capable of running tests the contain asynchronous logic.
        121 * @param {string=} opt_name A descriptive name for the test case.
        122 * @extends {goog.testing.TestCase}
        123 * @constructor
        124 */
        125goog.testing.AsyncTestCase = function(opt_name) {
        126 goog.testing.TestCase.call(this, opt_name);
        127};
        128goog.inherits(goog.testing.AsyncTestCase, goog.testing.TestCase);
        129
        130
        131/**
        132 * Represents result of top stack function call.
        133 * @typedef {{controlBreakingExceptionThrown: boolean, message: string}}
        134 * @private
        135 */
        136goog.testing.AsyncTestCase.TopStackFuncResult_;
        137
        138
        139
        140/**
        141 * An exception class used solely for control flow.
        142 * @param {string=} opt_message Error message.
        143 * @constructor
        144 * @final
        145 */
        146goog.testing.AsyncTestCase.ControlBreakingException = function(opt_message) {
        147 /**
        148 * The exception message.
        149 * @type {string}
        150 */
        151 this.message = opt_message || '';
        152};
        153
        154
        155/**
        156 * Return value for .toString().
        157 * @type {string}
        158 */
        159goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING =
        160 '[AsyncTestCase.ControlBreakingException]';
        161
        162
        163/**
        164 * Marks this object as a ControlBreakingException
        165 * @type {boolean}
        166 */
        167goog.testing.AsyncTestCase.ControlBreakingException.prototype.
        168 isControlBreakingException = true;
        169
        170
        171/** @override */
        172goog.testing.AsyncTestCase.ControlBreakingException.prototype.toString =
        173 function() {
        174 // This shows up in the console when the exception is not caught.
        175 return goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
        176};
        177
        178
        179/**
        180 * How long to wait for a single step of a test to complete in milliseconds.
        181 * A step starts when a call to waitForAsync() is made.
        182 * @type {number}
        183 */
        184goog.testing.AsyncTestCase.prototype.stepTimeout = 1000;
        185
        186
        187/**
        188 * How long to wait after a failed test before moving onto the next one.
        189 * The purpose of this is to allow any pending async callbacks from the failing
        190 * test to finish up and not cause the next test to fail.
        191 * @type {number}
        192 */
        193goog.testing.AsyncTestCase.prototype.timeToSleepAfterFailure = 500;
        194
        195
        196/**
        197 * Turn on extra logging to help debug failing async. tests.
        198 * @type {boolean}
        199 * @private
        200 */
        201goog.testing.AsyncTestCase.prototype.enableDebugLogs_ = false;
        202
        203
        204/**
        205 * A reference to the original asserts.js assert_() function.
        206 * @private
        207 */
        208goog.testing.AsyncTestCase.prototype.origAssert_;
        209
        210
        211/**
        212 * A reference to the original asserts.js fail() function.
        213 * @private
        214 */
        215goog.testing.AsyncTestCase.prototype.origFail_;
        216
        217
        218/**
        219 * A reference to the original window.onerror function.
        220 * @type {Function|undefined}
        221 * @private
        222 */
        223goog.testing.AsyncTestCase.prototype.origOnError_;
        224
        225
        226/**
        227 * The stage of the test we are currently on.
        228 * @type {Function|undefined}}
        229 * @private
        230 */
        231goog.testing.AsyncTestCase.prototype.curStepFunc_;
        232
        233
        234/**
        235 * The name of the stage of the test we are currently on.
        236 * @type {string}
        237 * @private
        238 */
        239goog.testing.AsyncTestCase.prototype.curStepName_ = '';
        240
        241
        242/**
        243 * The stage of the test we should run next.
        244 * @type {Function|undefined}
        245 * @private
        246 */
        247goog.testing.AsyncTestCase.prototype.nextStepFunc;
        248
        249
        250/**
        251 * The name of the stage of the test we should run next.
        252 * @type {string}
        253 * @private
        254 */
        255goog.testing.AsyncTestCase.prototype.nextStepName_ = '';
        256
        257
        258/**
        259 * The handle to the current setTimeout timer.
        260 * @type {number}
        261 * @private
        262 */
        263goog.testing.AsyncTestCase.prototype.timeoutHandle_ = 0;
        264
        265
        266/**
        267 * Marks if the cleanUp() function has been called for the currently running
        268 * test.
        269 * @type {boolean}
        270 * @private
        271 */
        272goog.testing.AsyncTestCase.prototype.cleanedUp_ = false;
        273
        274
        275/**
        276 * The currently active test.
        277 * @type {goog.testing.TestCase.Test|undefined}
        278 * @protected
        279 */
        280goog.testing.AsyncTestCase.prototype.activeTest;
        281
        282
        283/**
        284 * A flag to prevent recursive exception handling.
        285 * @type {boolean}
        286 * @private
        287 */
        288goog.testing.AsyncTestCase.prototype.inException_ = false;
        289
        290
        291/**
        292 * Flag used to determine if we can move to the next step in the testing loop.
        293 * @type {boolean}
        294 * @private
        295 */
        296goog.testing.AsyncTestCase.prototype.isReady_ = true;
        297
        298
        299/**
        300 * Number of signals to wait for before continuing testing when waitForSignals
        301 * is used.
        302 * @type {number}
        303 * @private
        304 */
        305goog.testing.AsyncTestCase.prototype.expectedSignalCount_ = 0;
        306
        307
        308/**
        309 * Number of signals received.
        310 * @type {number}
        311 * @private
        312 */
        313goog.testing.AsyncTestCase.prototype.receivedSignalCount_ = 0;
        314
        315
        316/**
        317 * Flag that tells us if there is a function in the call stack that will make
        318 * a call to pump_().
        319 * @type {boolean}
        320 * @private
        321 */
        322goog.testing.AsyncTestCase.prototype.returnWillPump_ = false;
        323
        324
        325/**
        326 * The number of times we have thrown a ControlBreakingException so that we
        327 * know not to complain in our window.onerror handler. In Webkit, window.onerror
        328 * is not supported, and so this counter will keep going up but we won't care
        329 * about it.
        330 * @type {number}
        331 * @private
        332 */
        333goog.testing.AsyncTestCase.prototype.numControlExceptionsExpected_ = 0;
        334
        335
        336/**
        337 * The current step name.
        338 * @return {!string} Step name.
        339 * @protected
        340 */
        341goog.testing.AsyncTestCase.prototype.getCurrentStepName = function() {
        342 return this.curStepName_;
        343};
        344
        345
        346/**
        347 * Preferred way of creating an AsyncTestCase. Creates one and initializes it
        348 * with the G_testRunner.
        349 * @param {string=} opt_name A descriptive name for the test case.
        350 * @return {!goog.testing.AsyncTestCase} The created AsyncTestCase.
        351 */
        352goog.testing.AsyncTestCase.createAndInstall = function(opt_name) {
        353 var asyncTestCase = new goog.testing.AsyncTestCase(opt_name);
        354 goog.testing.TestCase.initializeTestRunner(asyncTestCase);
        355 return asyncTestCase;
        356};
        357
        358
        359/**
        360 * Informs the testcase not to continue to the next step in the test cycle
        361 * until continueTesting is called.
        362 * @param {string=} opt_name A description of what we are waiting for.
        363 */
        364goog.testing.AsyncTestCase.prototype.waitForAsync = function(opt_name) {
        365 this.isReady_ = false;
        366 this.curStepName_ = opt_name || this.curStepName_;
        367
        368 // Reset the timer that tracks if the async test takes too long.
        369 this.stopTimeoutTimer_();
        370 this.startTimeoutTimer_();
        371};
        372
        373
        374/**
        375 * Continue with the next step in the test cycle.
        376 */
        377goog.testing.AsyncTestCase.prototype.continueTesting = function() {
        378 if (this.receivedSignalCount_ < this.expectedSignalCount_) {
        379 var remaining = this.expectedSignalCount_ - this.receivedSignalCount_;
        380 throw Error('Still waiting for ' + remaining + ' signals.');
        381 }
        382 this.endCurrentStep_();
        383};
        384
        385
        386/**
        387 * Ends the current test step and queues the next test step to run.
        388 * @private
        389 */
        390goog.testing.AsyncTestCase.prototype.endCurrentStep_ = function() {
        391 if (!this.isReady_) {
        392 // We are a potential entry point, so we pump.
        393 this.isReady_ = true;
        394 this.stopTimeoutTimer_();
        395 // Run this in a setTimeout so that the caller has a chance to call
        396 // waitForAsync() again before we continue.
        397 this.timeout(goog.bind(this.pump_, this, null), 0);
        398 }
        399};
        400
        401
        402/**
        403 * Informs the testcase not to continue to the next step in the test cycle
        404 * until signal is called the specified number of times. Within a test, this
        405 * function behaves additively if called multiple times; the number of signals
        406 * to wait for will be the sum of all expected number of signals this function
        407 * was called with.
        408 * @param {number} times The number of signals to receive before
        409 * continuing testing.
        410 * @param {string=} opt_name A description of what we are waiting for.
        411 */
        412goog.testing.AsyncTestCase.prototype.waitForSignals =
        413 function(times, opt_name) {
        414 this.expectedSignalCount_ += times;
        415 if (this.receivedSignalCount_ < this.expectedSignalCount_) {
        416 this.waitForAsync(opt_name);
        417 }
        418};
        419
        420
        421/**
        422 * Signals once to continue with the test. If this is the last signal that the
        423 * test was waiting on, call continueTesting.
        424 */
        425goog.testing.AsyncTestCase.prototype.signal = function() {
        426 if (++this.receivedSignalCount_ === this.expectedSignalCount_ &&
        427 this.expectedSignalCount_ > 0) {
        428 this.endCurrentStep_();
        429 }
        430};
        431
        432
        433/**
        434 * Handles an exception thrown by a test.
        435 * @param {*=} opt_e The exception object associated with the failure
        436 * or a string.
        437 * @throws Always throws a ControlBreakingException.
        438 */
        439goog.testing.AsyncTestCase.prototype.doAsyncError = function(opt_e) {
        440 // If we've caught an exception that we threw, then just pass it along. This
        441 // can happen if doAsyncError() was called from a call to assert and then
        442 // again by pump_().
        443 if (opt_e && opt_e.isControlBreakingException) {
        444 throw opt_e;
        445 }
        446
        447 // Prevent another timeout error from triggering for this test step.
        448 this.stopTimeoutTimer_();
        449
        450 // doError() uses test.name. Here, we create a dummy test and give it a more
        451 // helpful name based on the step we're currently on.
        452 var fakeTestObj = new goog.testing.TestCase.Test(this.curStepName_,
        453 goog.nullFunction);
        454 if (this.activeTest) {
        455 fakeTestObj.name = this.activeTest.name + ' [' + fakeTestObj.name + ']';
        456 }
        457
        458 if (this.activeTest) {
        459 // Note: if the test has an error, and then tearDown has an error, they will
        460 // both be reported.
        461 this.doError(fakeTestObj, opt_e);
        462 } else {
        463 this.exceptionBeforeTest = opt_e;
        464 }
        465
        466 // This is a potential entry point, so we pump. We also add in a bit of a
        467 // delay to try and prevent any async behavior from the failed test from
        468 // causing the next test to fail.
        469 this.timeout(goog.bind(this.pump_, this, this.doAsyncErrorTearDown_),
        470 this.timeToSleepAfterFailure);
        471
        472 // We just caught an exception, so we do not want the code above us on the
        473 // stack to continue executing. If pump_ is in our call-stack, then it will
        474 // batch together multiple errors, so we only increment the count if pump_ is
        475 // not in the stack and let pump_ increment the count when it batches them.
        476 if (!this.returnWillPump_) {
        477 this.numControlExceptionsExpected_ += 1;
        478 this.dbgLog_('doAsynError: numControlExceptionsExpected_ = ' +
        479 this.numControlExceptionsExpected_ + ' and throwing exception.');
        480 }
        481
        482 // Copy the error message to ControlBreakingException.
        483 var message = '';
        484 if (typeof opt_e == 'string') {
        485 message = opt_e;
        486 } else if (opt_e && opt_e.message) {
        487 message = opt_e.message;
        488 }
        489 throw new goog.testing.AsyncTestCase.ControlBreakingException(message);
        490};
        491
        492
        493/**
        494 * Sets up the test page and then waits until the test case has been marked
        495 * as ready before executing the tests.
        496 * @override
        497 */
        498goog.testing.AsyncTestCase.prototype.runTests = function() {
        499 this.hookAssert_();
        500 this.hookOnError_();
        501
        502 this.setNextStep_(this.doSetUpPage_, 'setUpPage');
        503 // We are an entry point, so we pump.
        504 this.pump_();
        505};
        506
        507
        508/**
        509 * Starts the tests.
        510 * @override
        511 */
        512goog.testing.AsyncTestCase.prototype.cycleTests = function() {
        513 // We are an entry point, so we pump.
        514 this.saveMessage('Start');
        515 this.setNextStep_(this.doIteration_, 'doIteration');
        516 this.pump_();
        517};
        518
        519
        520/**
        521 * Finalizes the test case, called when the tests have finished executing.
        522 * @override
        523 */
        524goog.testing.AsyncTestCase.prototype.finalize = function() {
        525 this.unhookAll_();
        526 this.setNextStep_(null, 'finalized');
        527 goog.testing.AsyncTestCase.superClass_.finalize.call(this);
        528};
        529
        530
        531/**
        532 * Enables verbose logging of what is happening inside of the AsyncTestCase.
        533 */
        534goog.testing.AsyncTestCase.prototype.enableDebugLogging = function() {
        535 this.enableDebugLogs_ = true;
        536};
        537
        538
        539/**
        540 * Logs the given debug message to the console (when enabled).
        541 * @param {string} message The message to log.
        542 * @private
        543 */
        544goog.testing.AsyncTestCase.prototype.dbgLog_ = function(message) {
        545 if (this.enableDebugLogs_) {
        546 this.log('AsyncTestCase - ' + message);
        547 }
        548};
        549
        550
        551/**
        552 * Wraps doAsyncError() for when we are sure that the test runner has no user
        553 * code above it in the stack.
        554 * @param {string|Error=} opt_e The exception object associated with the
        555 * failure or a string.
        556 * @private
        557 */
        558goog.testing.AsyncTestCase.prototype.doTopOfStackAsyncError_ =
        559 function(opt_e) {
        560 /** @preserveTry */
        561 try {
        562 this.doAsyncError(opt_e);
        563 } catch (e) {
        564 // We know that we are on the top of the stack, so there is no need to
        565 // throw this exception in this case.
        566 if (e.isControlBreakingException) {
        567 this.numControlExceptionsExpected_ -= 1;
        568 this.dbgLog_('doTopOfStackAsyncError_: numControlExceptionsExpected_ = ' +
        569 this.numControlExceptionsExpected_ + ' and catching exception.');
        570 } else {
        571 throw e;
        572 }
        573 }
        574};
        575
        576
        577/**
        578 * Calls the tearDown function, catching any errors, and then moves on to
        579 * the next step in the testing cycle.
        580 * @private
        581 */
        582goog.testing.AsyncTestCase.prototype.doAsyncErrorTearDown_ = function() {
        583 if (this.inException_) {
        584 // We get here if tearDown is throwing the error.
        585 // Upon calling continueTesting, the inline function 'doAsyncError' (set
        586 // below) is run.
        587 this.endCurrentStep_();
        588 } else {
        589 this.inException_ = true;
        590 this.isReady_ = true;
        591
        592 // The continue point is different depending on if the error happened in
        593 // setUpPage() or in setUp()/test*()/tearDown().
        594 var stepFuncAfterError = this.nextStepFunc_;
        595 var stepNameAfterError = 'TestCase.execute (after error)';
        596 if (this.activeTest) {
        597 stepFuncAfterError = this.doIteration_;
        598 stepNameAfterError = 'doIteration (after error)';
        599 }
        600
        601 // We must set the next step before calling tearDown.
        602 this.setNextStep_(function() {
        603 this.inException_ = false;
        604 // This is null when an error happens in setUpPage.
        605 this.setNextStep_(stepFuncAfterError, stepNameAfterError);
        606 }, 'doAsyncError');
        607
        608 // Call the test's tearDown().
        609 if (!this.cleanedUp_) {
        610 this.cleanedUp_ = true;
        611 this.tearDown();
        612 }
        613 }
        614};
        615
        616
        617/**
        618 * Replaces the asserts.js assert_() and fail() functions with a wrappers to
        619 * catch the exceptions.
        620 * @private
        621 */
        622goog.testing.AsyncTestCase.prototype.hookAssert_ = function() {
        623 if (!this.origAssert_) {
        624 this.origAssert_ = _assert;
        625 this.origFail_ = fail;
        626 var self = this;
        627 _assert = function() {
        628 /** @preserveTry */
        629 try {
        630 self.origAssert_.apply(this, arguments);
        631 } catch (e) {
        632 self.dbgLog_('Wrapping failed assert()');
        633 self.doAsyncError(e);
        634 }
        635 };
        636 fail = function() {
        637 /** @preserveTry */
        638 try {
        639 self.origFail_.apply(this, arguments);
        640 } catch (e) {
        641 self.dbgLog_('Wrapping fail()');
        642 self.doAsyncError(e);
        643 }
        644 };
        645 }
        646};
        647
        648
        649/**
        650 * Sets a window.onerror handler for catching exceptions that happen in async
        651 * callbacks. Note that as of Safari 3.1, Safari does not support this.
        652 * @private
        653 */
        654goog.testing.AsyncTestCase.prototype.hookOnError_ = function() {
        655 if (!this.origOnError_) {
        656 this.origOnError_ = window.onerror;
        657 var self = this;
        658 window.onerror = function(error, url, line) {
        659 // Ignore exceptions that we threw on purpose.
        660 var cbe =
        661 goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
        662 if (String(error).indexOf(cbe) != -1 &&
        663 self.numControlExceptionsExpected_) {
        664 self.numControlExceptionsExpected_ -= 1;
        665 self.dbgLog_('window.onerror: numControlExceptionsExpected_ = ' +
        666 self.numControlExceptionsExpected_ + ' and ignoring exception. ' +
        667 error);
        668 // Tell the browser not to compain about the error.
        669 return true;
        670 } else {
        671 self.dbgLog_('window.onerror caught exception.');
        672 var message = error + '\nURL: ' + url + '\nLine: ' + line;
        673 self.doTopOfStackAsyncError_(message);
        674 // Tell the browser to complain about the error.
        675 return false;
        676 }
        677 };
        678 }
        679};
        680
        681
        682/**
        683 * Unhooks window.onerror and _assert.
        684 * @private
        685 */
        686goog.testing.AsyncTestCase.prototype.unhookAll_ = function() {
        687 if (this.origOnError_) {
        688 window.onerror = this.origOnError_;
        689 this.origOnError_ = null;
        690 _assert = this.origAssert_;
        691 this.origAssert_ = null;
        692 fail = this.origFail_;
        693 this.origFail_ = null;
        694 }
        695};
        696
        697
        698/**
        699 * Enables the timeout timer. This timer fires unless continueTesting is
        700 * called.
        701 * @private
        702 */
        703goog.testing.AsyncTestCase.prototype.startTimeoutTimer_ = function() {
        704 if (!this.timeoutHandle_ && this.stepTimeout > 0) {
        705 this.timeoutHandle_ = this.timeout(goog.bind(function() {
        706 this.dbgLog_('Timeout timer fired with id ' + this.timeoutHandle_);
        707 this.timeoutHandle_ = 0;
        708
        709 this.doTopOfStackAsyncError_('Timed out while waiting for ' +
        710 'continueTesting() to be called.');
        711 }, this, null), this.stepTimeout);
        712 this.dbgLog_('Started timeout timer with id ' + this.timeoutHandle_);
        713 }
        714};
        715
        716
        717/**
        718 * Disables the timeout timer.
        719 * @private
        720 */
        721goog.testing.AsyncTestCase.prototype.stopTimeoutTimer_ = function() {
        722 if (this.timeoutHandle_) {
        723 this.dbgLog_('Clearing timeout timer with id ' + this.timeoutHandle_);
        724 this.clearTimeout(this.timeoutHandle_);
        725 this.timeoutHandle_ = 0;
        726 }
        727};
        728
        729
        730/**
        731 * Sets the next function to call in our sequence of async callbacks.
        732 * @param {Function} func The function that executes the next step.
        733 * @param {string} name A description of the next step.
        734 * @private
        735 */
        736goog.testing.AsyncTestCase.prototype.setNextStep_ = function(func, name) {
        737 this.nextStepFunc_ = func && goog.bind(func, this);
        738 this.nextStepName_ = name;
        739};
        740
        741
        742/**
        743 * Calls the given function, redirecting any exceptions to doAsyncError.
        744 * @param {Function} func The function to call.
        745 * @return {!goog.testing.AsyncTestCase.TopStackFuncResult_} Returns a
        746 * TopStackFuncResult_.
        747 * @private
        748 */
        749goog.testing.AsyncTestCase.prototype.callTopOfStackFunc_ = function(func) {
        750 /** @preserveTry */
        751 try {
        752 func.call(this);
        753 return {controlBreakingExceptionThrown: false, message: ''};
        754 } catch (e) {
        755 this.dbgLog_('Caught exception in callTopOfStackFunc_');
        756 /** @preserveTry */
        757 try {
        758 this.doAsyncError(e);
        759 return {controlBreakingExceptionThrown: false, message: ''};
        760 } catch (e2) {
        761 if (!e2.isControlBreakingException) {
        762 throw e2;
        763 }
        764 return {controlBreakingExceptionThrown: true, message: e2.message};
        765 }
        766 }
        767};
        768
        769
        770/**
        771 * Calls the next callback when the isReady_ flag is true.
        772 * @param {Function=} opt_doFirst A function to call before pumping.
        773 * @private
        774 * @throws Throws a ControlBreakingException if there were any failing steps.
        775 */
        776goog.testing.AsyncTestCase.prototype.pump_ = function(opt_doFirst) {
        777 // If this function is already above us in the call-stack, then we should
        778 // return rather than pumping in order to minimize call-stack depth.
        779 if (!this.returnWillPump_) {
        780 this.setBatchTime(this.now());
        781 this.returnWillPump_ = true;
        782 var topFuncResult = {};
        783
        784 if (opt_doFirst) {
        785 topFuncResult = this.callTopOfStackFunc_(opt_doFirst);
        786 }
        787 // Note: we don't check for this.running here because it is not set to true
        788 // while executing setUpPage and tearDownPage.
        789 // Also, if isReady_ is false, then one of two things will happen:
        790 // 1. Our timeout callback will be called.
        791 // 2. The tests will call continueTesting(), which will call pump_() again.
        792 while (this.isReady_ && this.nextStepFunc_ &&
        793 !topFuncResult.controlBreakingExceptionThrown) {
        794 this.curStepFunc_ = this.nextStepFunc_;
        795 this.curStepName_ = this.nextStepName_;
        796 this.nextStepFunc_ = null;
        797 this.nextStepName_ = '';
        798
        799 this.dbgLog_('Performing step: ' + this.curStepName_);
        800 topFuncResult =
        801 this.callTopOfStackFunc_(/** @type {Function} */(this.curStepFunc_));
        802
        803 // If the max run time is exceeded call this function again async so as
        804 // not to block the browser.
        805 var delta = this.now() - this.getBatchTime();
        806 if (delta > goog.testing.TestCase.maxRunTime &&
        807 !topFuncResult.controlBreakingExceptionThrown) {
        808 this.saveMessage('Breaking async');
        809 var self = this;
        810 this.timeout(function() { self.pump_(); }, 100);
        811 break;
        812 }
        813 }
        814 this.returnWillPump_ = false;
        815 } else if (opt_doFirst) {
        816 opt_doFirst.call(this);
        817 }
        818};
        819
        820
        821/**
        822 * Sets up the test page and then waits untill the test case has been marked
        823 * as ready before executing the tests.
        824 * @private
        825 */
        826goog.testing.AsyncTestCase.prototype.doSetUpPage_ = function() {
        827 this.setNextStep_(this.execute, 'TestCase.execute');
        828 this.setUpPage();
        829};
        830
        831
        832/**
        833 * Step 1: Move to the next test.
        834 * @private
        835 */
        836goog.testing.AsyncTestCase.prototype.doIteration_ = function() {
        837 this.expectedSignalCount_ = 0;
        838 this.receivedSignalCount_ = 0;
        839 this.activeTest = this.next();
        840 if (this.activeTest && this.running) {
        841 this.result_.runCount++;
        842 // If this test should be marked as having failed, doIteration will go
        843 // straight to the next test.
        844 if (this.maybeFailTestEarly(this.activeTest)) {
        845 this.setNextStep_(this.doIteration_, 'doIteration');
        846 } else {
        847 this.setNextStep_(this.doSetUp_, 'setUp');
        848 }
        849 } else {
        850 // All tests done.
        851 this.finalize();
        852 }
        853};
        854
        855
        856/**
        857 * Step 2: Call setUp().
        858 * @private
        859 */
        860goog.testing.AsyncTestCase.prototype.doSetUp_ = function() {
        861 this.log('Running test: ' + this.activeTest.name);
        862 this.cleanedUp_ = false;
        863 this.setNextStep_(this.doExecute_, this.activeTest.name);
        864 this.setUp();
        865};
        866
        867
        868/**
        869 * Step 3: Call test.execute().
        870 * @private
        871 */
        872goog.testing.AsyncTestCase.prototype.doExecute_ = function() {
        873 this.setNextStep_(this.doTearDown_, 'tearDown');
        874 this.activeTest.execute();
        875};
        876
        877
        878/**
        879 * Step 4: Call tearDown().
        880 * @private
        881 */
        882goog.testing.AsyncTestCase.prototype.doTearDown_ = function() {
        883 this.cleanedUp_ = true;
        884 this.setNextStep_(this.doNext_, 'doNext');
        885 this.tearDown();
        886};
        887
        888
        889/**
        890 * Step 5: Call doSuccess()
        891 * @private
        892 */
        893goog.testing.AsyncTestCase.prototype.doNext_ = function() {
        894 this.setNextStep_(this.doIteration_, 'doIteration');
        895 this.doSuccess(/** @type {goog.testing.TestCase.Test} */(this.activeTest));
        896};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/events/events.js.src.html b/docs/source/lib/goog/testing/events/events.js.src.html new file mode 100644 index 0000000..2b4c4ab --- /dev/null +++ b/docs/source/lib/goog/testing/events/events.js.src.html @@ -0,0 +1 @@ +events.js

        lib/goog/testing/events/events.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Event Simulation.
        17 *
        18 * Utility functions for simulating events at the Closure level. All functions
        19 * in this package generate events by calling goog.events.fireListeners,
        20 * rather than interfacing with the browser directly. This is intended for
        21 * testing purposes, and should not be used in production code.
        22 *
        23 * The decision to use Closure events and dispatchers instead of the browser's
        24 * native events and dispatchers was conscious and deliberate. Native event
        25 * dispatchers have their own set of quirks and edge cases. Pure JS dispatchers
        26 * are more robust and transparent.
        27 *
        28 * If you think you need a testing mechanism that uses native Event objects,
        29 * please, please email closure-tech first to explain your use case before you
        30 * sink time into this.
        31 *
        32 * @author nicksantos@google.com (Nick Santos)
        33 */
        34
        35goog.provide('goog.testing.events');
        36goog.provide('goog.testing.events.Event');
        37
        38goog.require('goog.Disposable');
        39goog.require('goog.asserts');
        40goog.require('goog.dom.NodeType');
        41goog.require('goog.events');
        42goog.require('goog.events.BrowserEvent');
        43goog.require('goog.events.BrowserFeature');
        44goog.require('goog.events.EventTarget');
        45goog.require('goog.events.EventType');
        46goog.require('goog.events.KeyCodes');
        47goog.require('goog.object');
        48goog.require('goog.style');
        49goog.require('goog.userAgent');
        50
        51
        52
        53/**
        54 * goog.events.BrowserEvent expects an Event so we provide one for JSCompiler.
        55 *
        56 * This clones a lot of the functionality of goog.events.Event. This used to
        57 * use a mixin, but the mixin results in confusing the two types when compiled.
        58 *
        59 * @param {string} type Event Type.
        60 * @param {Object=} opt_target Reference to the object that is the target of
        61 * this event.
        62 * @constructor
        63 * @extends {Event}
        64 */
        65goog.testing.events.Event = function(type, opt_target) {
        66 this.type = type;
        67
        68 this.target = /** @type {EventTarget} */ (opt_target || null);
        69
        70 this.currentTarget = this.target;
        71};
        72
        73
        74/**
        75 * Whether to cancel the event in internal capture/bubble processing for IE.
        76 * @type {boolean}
        77 * @public
        78 * @suppress {underscore|visibility} Technically public, but referencing this
        79 * outside this package is strongly discouraged.
        80 */
        81goog.testing.events.Event.prototype.propagationStopped_ = false;
        82
        83
        84/** @override */
        85goog.testing.events.Event.prototype.defaultPrevented = false;
        86
        87
        88/**
        89 * Return value for in internal capture/bubble processing for IE.
        90 * @type {boolean}
        91 * @public
        92 * @suppress {underscore|visibility} Technically public, but referencing this
        93 * outside this package is strongly discouraged.
        94 */
        95goog.testing.events.Event.prototype.returnValue_ = true;
        96
        97
        98/** @override */
        99goog.testing.events.Event.prototype.stopPropagation = function() {
        100 this.propagationStopped_ = true;
        101};
        102
        103
        104/** @override */
        105goog.testing.events.Event.prototype.preventDefault = function() {
        106 this.defaultPrevented = true;
        107 this.returnValue_ = false;
        108};
        109
        110
        111/**
        112 * Asserts an event target exists. This will fail if target is not defined.
        113 *
        114 * TODO(nnaze): Gradually add this to the methods in this file, and eventually
        115 * update the method signatures to not take nullables. See http://b/8961907
        116 *
        117 * @param {EventTarget} target A target to assert.
        118 * @return {!EventTarget} The target, guaranteed to exist.
        119 * @private
        120 */
        121goog.testing.events.assertEventTarget_ = function(target) {
        122 return goog.asserts.assert(target, 'EventTarget should be defined.');
        123};
        124
        125
        126/**
        127 * A static helper function that sets the mouse position to the event.
        128 * @param {Event} event A simulated native event.
        129 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        130 * target's position (if available), otherwise (0, 0).
        131 * @private
        132 */
        133goog.testing.events.setEventClientXY_ = function(event, opt_coords) {
        134 if (!opt_coords && event.target &&
        135 event.target.nodeType == goog.dom.NodeType.ELEMENT) {
        136 try {
        137 opt_coords =
        138 goog.style.getClientPosition(/** @type {!Element} **/ (event.target));
        139 } catch (ex) {
        140 // IE sometimes throws if it can't get the position.
        141 }
        142 }
        143 event.clientX = opt_coords ? opt_coords.x : 0;
        144 event.clientY = opt_coords ? opt_coords.y : 0;
        145
        146 // Pretend the browser window is at (0, 0).
        147 event.screenX = event.clientX;
        148 event.screenY = event.clientY;
        149};
        150
        151
        152/**
        153 * Simulates a mousedown, mouseup, and then click on the given event target,
        154 * with the left mouse button.
        155 * @param {EventTarget} target The target for the event.
        156 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
        157 * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
        158 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        159 * target's position (if available), otherwise (0, 0).
        160 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        161 * BrowserEvent.
        162 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        163 * was called on any of the events, true otherwise.
        164 */
        165goog.testing.events.fireClickSequence =
        166 function(target, opt_button, opt_coords, opt_eventProperties) {
        167 // Fire mousedown, mouseup, and click. Then return the bitwise AND of the 3.
        168 return !!(goog.testing.events.fireMouseDownEvent(
        169 target, opt_button, opt_coords, opt_eventProperties) &
        170 goog.testing.events.fireMouseUpEvent(
        171 target, opt_button, opt_coords, opt_eventProperties) &
        172 goog.testing.events.fireClickEvent(
        173 target, opt_button, opt_coords, opt_eventProperties));
        174};
        175
        176
        177/**
        178 * Simulates the sequence of events fired by the browser when the user double-
        179 * clicks the given target.
        180 * @param {EventTarget} target The target for the event.
        181 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        182 * target's position (if available), otherwise (0, 0).
        183 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        184 * BrowserEvent.
        185 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        186 * was called on any of the events, true otherwise.
        187 */
        188goog.testing.events.fireDoubleClickSequence = function(
        189 target, opt_coords, opt_eventProperties) {
        190 // Fire mousedown, mouseup, click, mousedown, mouseup, click, dblclick.
        191 // Then return the bitwise AND of the 7.
        192 var btn = goog.events.BrowserEvent.MouseButton.LEFT;
        193 return !!(goog.testing.events.fireMouseDownEvent(
        194 target, btn, opt_coords, opt_eventProperties) &
        195 goog.testing.events.fireMouseUpEvent(
        196 target, btn, opt_coords, opt_eventProperties) &
        197 goog.testing.events.fireClickEvent(
        198 target, btn, opt_coords, opt_eventProperties) &
        199 // IE fires a selectstart instead of the second mousedown in a
        200 // dblclick, but we don't care about selectstart.
        201 (goog.userAgent.IE ||
        202 goog.testing.events.fireMouseDownEvent(
        203 target, btn, opt_coords, opt_eventProperties)) &
        204 goog.testing.events.fireMouseUpEvent(
        205 target, btn, opt_coords, opt_eventProperties) &
        206 // IE doesn't fire the second click in a dblclick.
        207 (goog.userAgent.IE ||
        208 goog.testing.events.fireClickEvent(
        209 target, btn, opt_coords, opt_eventProperties)) &
        210 goog.testing.events.fireDoubleClickEvent(
        211 target, opt_coords, opt_eventProperties));
        212};
        213
        214
        215/**
        216 * Simulates a complete keystroke (keydown, keypress, and keyup). Note that
        217 * if preventDefault is called on the keydown, the keypress will not fire.
        218 *
        219 * @param {EventTarget} target The target for the event.
        220 * @param {number} keyCode The keycode of the key pressed.
        221 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        222 * BrowserEvent.
        223 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        224 * was called on any of the events, true otherwise.
        225 */
        226goog.testing.events.fireKeySequence = function(
        227 target, keyCode, opt_eventProperties) {
        228 return goog.testing.events.fireNonAsciiKeySequence(target, keyCode, keyCode,
        229 opt_eventProperties);
        230};
        231
        232
        233/**
        234 * Simulates a complete keystroke (keydown, keypress, and keyup) when typing
        235 * a non-ASCII character. Same as fireKeySequence, the keypress will not fire
        236 * if preventDefault is called on the keydown.
        237 *
        238 * @param {EventTarget} target The target for the event.
        239 * @param {number} keyCode The keycode of the keydown and keyup events.
        240 * @param {number} keyPressKeyCode The keycode of the keypress event.
        241 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        242 * BrowserEvent.
        243 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        244 * was called on any of the events, true otherwise.
        245 */
        246goog.testing.events.fireNonAsciiKeySequence = function(
        247 target, keyCode, keyPressKeyCode, opt_eventProperties) {
        248 var keydown =
        249 new goog.testing.events.Event(goog.events.EventType.KEYDOWN, target);
        250 var keyup =
        251 new goog.testing.events.Event(goog.events.EventType.KEYUP, target);
        252 var keypress =
        253 new goog.testing.events.Event(goog.events.EventType.KEYPRESS, target);
        254 keydown.keyCode = keyup.keyCode = keyCode;
        255 keypress.keyCode = keyPressKeyCode;
        256
        257 if (opt_eventProperties) {
        258 goog.object.extend(keydown, opt_eventProperties);
        259 goog.object.extend(keyup, opt_eventProperties);
        260 goog.object.extend(keypress, opt_eventProperties);
        261 }
        262
        263 // Fire keydown, keypress, and keyup. Note that if the keydown is
        264 // prevent-defaulted, then the keypress will not fire.
        265 var result = true;
        266 if (!goog.testing.events.isBrokenGeckoMacActionKey_(keydown)) {
        267 result = goog.testing.events.fireBrowserEvent(keydown);
        268 }
        269 if (goog.events.KeyCodes.firesKeyPressEvent(
        270 keyCode, undefined, keydown.shiftKey, keydown.ctrlKey,
        271 keydown.altKey) && result) {
        272 result &= goog.testing.events.fireBrowserEvent(keypress);
        273 }
        274 return !!(result & goog.testing.events.fireBrowserEvent(keyup));
        275};
        276
        277
        278/**
        279 * @param {goog.testing.events.Event} e The event.
        280 * @return {boolean} Whether this is the Gecko/Mac's Meta-C/V/X, which
        281 * is broken and requires special handling.
        282 * @private
        283 */
        284goog.testing.events.isBrokenGeckoMacActionKey_ = function(e) {
        285 return goog.userAgent.MAC && goog.userAgent.GECKO &&
        286 (e.keyCode == goog.events.KeyCodes.C ||
        287 e.keyCode == goog.events.KeyCodes.X ||
        288 e.keyCode == goog.events.KeyCodes.V) && e.metaKey;
        289};
        290
        291
        292/**
        293 * Simulates a mouseover event on the given target.
        294 * @param {EventTarget} target The target for the event.
        295 * @param {EventTarget} relatedTarget The related target for the event (e.g.,
        296 * the node that the mouse is being moved out of).
        297 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        298 * target's position (if available), otherwise (0, 0).
        299 * @return {boolean} The returnValue of the event: false if preventDefault() was
        300 * called on it, true otherwise.
        301 */
        302goog.testing.events.fireMouseOverEvent = function(target, relatedTarget,
        303 opt_coords) {
        304 var mouseover =
        305 new goog.testing.events.Event(goog.events.EventType.MOUSEOVER, target);
        306 mouseover.relatedTarget = relatedTarget;
        307 goog.testing.events.setEventClientXY_(mouseover, opt_coords);
        308 return goog.testing.events.fireBrowserEvent(mouseover);
        309};
        310
        311
        312/**
        313 * Simulates a mousemove event on the given target.
        314 * @param {EventTarget} target The target for the event.
        315 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        316 * target's position (if available), otherwise (0, 0).
        317 * @return {boolean} The returnValue of the event: false if preventDefault() was
        318 * called on it, true otherwise.
        319 */
        320goog.testing.events.fireMouseMoveEvent = function(target, opt_coords) {
        321 var mousemove =
        322 new goog.testing.events.Event(goog.events.EventType.MOUSEMOVE, target);
        323
        324 goog.testing.events.setEventClientXY_(mousemove, opt_coords);
        325 return goog.testing.events.fireBrowserEvent(mousemove);
        326};
        327
        328
        329/**
        330 * Simulates a mouseout event on the given target.
        331 * @param {EventTarget} target The target for the event.
        332 * @param {EventTarget} relatedTarget The related target for the event (e.g.,
        333 * the node that the mouse is being moved into).
        334 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        335 * target's position (if available), otherwise (0, 0).
        336 * @return {boolean} The returnValue of the event: false if preventDefault() was
        337 * called on it, true otherwise.
        338 */
        339goog.testing.events.fireMouseOutEvent = function(target, relatedTarget,
        340 opt_coords) {
        341 var mouseout =
        342 new goog.testing.events.Event(goog.events.EventType.MOUSEOUT, target);
        343 mouseout.relatedTarget = relatedTarget;
        344 goog.testing.events.setEventClientXY_(mouseout, opt_coords);
        345 return goog.testing.events.fireBrowserEvent(mouseout);
        346};
        347
        348
        349/**
        350 * Simulates a mousedown event on the given target.
        351 * @param {EventTarget} target The target for the event.
        352 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
        353 * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
        354 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        355 * target's position (if available), otherwise (0, 0).
        356 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        357 * BrowserEvent.
        358 * @return {boolean} The returnValue of the event: false if preventDefault() was
        359 * called on it, true otherwise.
        360 */
        361goog.testing.events.fireMouseDownEvent =
        362 function(target, opt_button, opt_coords, opt_eventProperties) {
        363
        364 var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
        365 button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
        366 goog.events.BrowserEvent.IEButtonMap[button] : button;
        367 return goog.testing.events.fireMouseButtonEvent_(
        368 goog.events.EventType.MOUSEDOWN, target, button, opt_coords,
        369 opt_eventProperties);
        370};
        371
        372
        373/**
        374 * Simulates a mouseup event on the given target.
        375 * @param {EventTarget} target The target for the event.
        376 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
        377 * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
        378 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        379 * target's position (if available), otherwise (0, 0).
        380 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        381 * BrowserEvent.
        382 * @return {boolean} The returnValue of the event: false if preventDefault() was
        383 * called on it, true otherwise.
        384 */
        385goog.testing.events.fireMouseUpEvent =
        386 function(target, opt_button, opt_coords, opt_eventProperties) {
        387 var button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
        388 button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
        389 goog.events.BrowserEvent.IEButtonMap[button] : button;
        390 return goog.testing.events.fireMouseButtonEvent_(
        391 goog.events.EventType.MOUSEUP, target, button, opt_coords,
        392 opt_eventProperties);
        393};
        394
        395
        396/**
        397 * Simulates a click event on the given target. IE only supports click with
        398 * the left mouse button.
        399 * @param {EventTarget} target The target for the event.
        400 * @param {goog.events.BrowserEvent.MouseButton=} opt_button Mouse button;
        401 * defaults to {@code goog.events.BrowserEvent.MouseButton.LEFT}.
        402 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        403 * target's position (if available), otherwise (0, 0).
        404 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        405 * BrowserEvent.
        406 * @return {boolean} The returnValue of the event: false if preventDefault() was
        407 * called on it, true otherwise.
        408 */
        409goog.testing.events.fireClickEvent =
        410 function(target, opt_button, opt_coords, opt_eventProperties) {
        411 return goog.testing.events.fireMouseButtonEvent_(goog.events.EventType.CLICK,
        412 target, opt_button, opt_coords, opt_eventProperties);
        413};
        414
        415
        416/**
        417 * Simulates a double-click event on the given target. Always double-clicks
        418 * with the left mouse button since no browser supports double-clicking with
        419 * any other buttons.
        420 * @param {EventTarget} target The target for the event.
        421 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        422 * target's position (if available), otherwise (0, 0).
        423 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        424 * BrowserEvent.
        425 * @return {boolean} The returnValue of the event: false if preventDefault() was
        426 * called on it, true otherwise.
        427 */
        428goog.testing.events.fireDoubleClickEvent =
        429 function(target, opt_coords, opt_eventProperties) {
        430 return goog.testing.events.fireMouseButtonEvent_(
        431 goog.events.EventType.DBLCLICK, target,
        432 goog.events.BrowserEvent.MouseButton.LEFT, opt_coords,
        433 opt_eventProperties);
        434};
        435
        436
        437/**
        438 * Helper function to fire a mouse event.
        439 * with the left mouse button since no browser supports double-clicking with
        440 * any other buttons.
        441 * @param {string} type The event type.
        442 * @param {EventTarget} target The target for the event.
        443 * @param {number=} opt_button Mouse button; defaults to
        444 * {@code goog.events.BrowserEvent.MouseButton.LEFT}.
        445 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        446 * target's position (if available), otherwise (0, 0).
        447 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        448 * BrowserEvent.
        449 * @return {boolean} The returnValue of the event: false if preventDefault() was
        450 * called on it, true otherwise.
        451 * @private
        452 */
        453goog.testing.events.fireMouseButtonEvent_ =
        454 function(type, target, opt_button, opt_coords, opt_eventProperties) {
        455 var e =
        456 new goog.testing.events.Event(type, target);
        457 e.button = opt_button || goog.events.BrowserEvent.MouseButton.LEFT;
        458 goog.testing.events.setEventClientXY_(e, opt_coords);
        459 if (opt_eventProperties) {
        460 goog.object.extend(e, opt_eventProperties);
        461 }
        462 return goog.testing.events.fireBrowserEvent(e);
        463};
        464
        465
        466/**
        467 * Simulates a contextmenu event on the given target.
        468 * @param {EventTarget} target The target for the event.
        469 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        470 * target's position (if available), otherwise (0, 0).
        471 * @return {boolean} The returnValue of the event: false if preventDefault() was
        472 * called on it, true otherwise.
        473 */
        474goog.testing.events.fireContextMenuEvent = function(target, opt_coords) {
        475 var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
        476 goog.events.BrowserEvent.MouseButton.LEFT :
        477 goog.events.BrowserEvent.MouseButton.RIGHT;
        478 var contextmenu =
        479 new goog.testing.events.Event(goog.events.EventType.CONTEXTMENU, target);
        480 contextmenu.button = !goog.events.BrowserFeature.HAS_W3C_BUTTON ?
        481 goog.events.BrowserEvent.IEButtonMap[button] : button;
        482 contextmenu.ctrlKey = goog.userAgent.MAC;
        483 goog.testing.events.setEventClientXY_(contextmenu, opt_coords);
        484 return goog.testing.events.fireBrowserEvent(contextmenu);
        485};
        486
        487
        488/**
        489 * Simulates a mousedown, contextmenu, and the mouseup on the given event
        490 * target, with the right mouse button.
        491 * @param {EventTarget} target The target for the event.
        492 * @param {goog.math.Coordinate=} opt_coords Mouse position. Defaults to event's
        493 * target's position (if available), otherwise (0, 0).
        494 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        495 * was called on any of the events, true otherwise.
        496 */
        497goog.testing.events.fireContextMenuSequence = function(target, opt_coords) {
        498 var props = goog.userAgent.MAC ? {ctrlKey: true} : {};
        499 var button = (goog.userAgent.MAC && goog.userAgent.WEBKIT) ?
        500 goog.events.BrowserEvent.MouseButton.LEFT :
        501 goog.events.BrowserEvent.MouseButton.RIGHT;
        502
        503 var result = goog.testing.events.fireMouseDownEvent(target,
        504 button, opt_coords, props);
        505 if (goog.userAgent.WINDOWS) {
        506 // All browsers are consistent on Windows.
        507 result &= goog.testing.events.fireMouseUpEvent(target,
        508 button, opt_coords) &
        509 goog.testing.events.fireContextMenuEvent(target, opt_coords);
        510 } else {
        511 result &= goog.testing.events.fireContextMenuEvent(target, opt_coords);
        512
        513 // GECKO on Mac and Linux always fires the mouseup after the contextmenu.
        514
        515 // WEBKIT is really weird.
        516 //
        517 // On Linux, it sometimes fires mouseup, but most of the time doesn't.
        518 // It's really hard to reproduce consistently. I think there's some
        519 // internal race condition. If contextmenu is preventDefaulted, then
        520 // mouseup always fires.
        521 //
        522 // On Mac, it always fires mouseup and then fires a click.
        523 result &= goog.testing.events.fireMouseUpEvent(target,
        524 button, opt_coords, props);
        525
        526 if (goog.userAgent.WEBKIT && goog.userAgent.MAC) {
        527 result &= goog.testing.events.fireClickEvent(
        528 target, button, opt_coords, props);
        529 }
        530 }
        531 return !!result;
        532};
        533
        534
        535/**
        536 * Simulates a popstate event on the given target.
        537 * @param {EventTarget} target The target for the event.
        538 * @param {Object} state History state object.
        539 * @return {boolean} The returnValue of the event: false if preventDefault() was
        540 * called on it, true otherwise.
        541 */
        542goog.testing.events.firePopStateEvent = function(target, state) {
        543 var e = new goog.testing.events.Event(goog.events.EventType.POPSTATE, target);
        544 e.state = state;
        545 return goog.testing.events.fireBrowserEvent(e);
        546};
        547
        548
        549/**
        550 * Simulate a blur event on the given target.
        551 * @param {EventTarget} target The target for the event.
        552 * @return {boolean} The value returned by firing the blur browser event,
        553 * which returns false iff 'preventDefault' was invoked.
        554 */
        555goog.testing.events.fireBlurEvent = function(target) {
        556 var e = new goog.testing.events.Event(
        557 goog.events.EventType.BLUR, target);
        558 return goog.testing.events.fireBrowserEvent(e);
        559};
        560
        561
        562/**
        563 * Simulate a focus event on the given target.
        564 * @param {EventTarget} target The target for the event.
        565 * @return {boolean} The value returned by firing the focus browser event,
        566 * which returns false iff 'preventDefault' was invoked.
        567 */
        568goog.testing.events.fireFocusEvent = function(target) {
        569 var e = new goog.testing.events.Event(
        570 goog.events.EventType.FOCUS, target);
        571 return goog.testing.events.fireBrowserEvent(e);
        572};
        573
        574
        575/**
        576 * Simulates an event's capturing and bubbling phases.
        577 * @param {Event} event A simulated native event. It will be wrapped in a
        578 * normalized BrowserEvent and dispatched to Closure listeners on all
        579 * ancestors of its target (inclusive).
        580 * @return {boolean} The returnValue of the event: false if preventDefault() was
        581 * called on it, true otherwise.
        582 */
        583goog.testing.events.fireBrowserEvent = function(event) {
        584 event.returnValue_ = true;
        585
        586 // generate a list of ancestors
        587 var ancestors = [];
        588 for (var current = event.target; current; current = current.parentNode) {
        589 ancestors.push(current);
        590 }
        591
        592 // dispatch capturing listeners
        593 for (var j = ancestors.length - 1;
        594 j >= 0 && !event.propagationStopped_;
        595 j--) {
        596 goog.events.fireListeners(ancestors[j], event.type, true,
        597 new goog.events.BrowserEvent(event, ancestors[j]));
        598 }
        599
        600 // dispatch bubbling listeners
        601 for (var j = 0;
        602 j < ancestors.length && !event.propagationStopped_;
        603 j++) {
        604 goog.events.fireListeners(ancestors[j], event.type, false,
        605 new goog.events.BrowserEvent(event, ancestors[j]));
        606 }
        607
        608 return event.returnValue_;
        609};
        610
        611
        612/**
        613 * Simulates a touchstart event on the given target.
        614 * @param {EventTarget} target The target for the event.
        615 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
        616 * target's position (if available), otherwise (0, 0).
        617 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        618 * BrowserEvent.
        619 * @return {boolean} The returnValue of the event: false if preventDefault() was
        620 * called on it, true otherwise.
        621 */
        622goog.testing.events.fireTouchStartEvent = function(
        623 target, opt_coords, opt_eventProperties) {
        624 // TODO: Support multi-touch events with array of coordinates.
        625 var touchstart =
        626 new goog.testing.events.Event(goog.events.EventType.TOUCHSTART, target);
        627 goog.testing.events.setEventClientXY_(touchstart, opt_coords);
        628 if (opt_eventProperties) {
        629 goog.object.extend(touchstart, opt_eventProperties);
        630 }
        631 return goog.testing.events.fireBrowserEvent(touchstart);
        632};
        633
        634
        635/**
        636 * Simulates a touchmove event on the given target.
        637 * @param {EventTarget} target The target for the event.
        638 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
        639 * target's position (if available), otherwise (0, 0).
        640 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        641 * BrowserEvent.
        642 * @return {boolean} The returnValue of the event: false if preventDefault() was
        643 * called on it, true otherwise.
        644 */
        645goog.testing.events.fireTouchMoveEvent = function(
        646 target, opt_coords, opt_eventProperties) {
        647 // TODO: Support multi-touch events with array of coordinates.
        648 var touchmove =
        649 new goog.testing.events.Event(goog.events.EventType.TOUCHMOVE, target);
        650 goog.testing.events.setEventClientXY_(touchmove, opt_coords);
        651 if (opt_eventProperties) {
        652 goog.object.extend(touchmove, opt_eventProperties);
        653 }
        654 return goog.testing.events.fireBrowserEvent(touchmove);
        655};
        656
        657
        658/**
        659 * Simulates a touchend event on the given target.
        660 * @param {EventTarget} target The target for the event.
        661 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event's
        662 * target's position (if available), otherwise (0, 0).
        663 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        664 * BrowserEvent.
        665 * @return {boolean} The returnValue of the event: false if preventDefault() was
        666 * called on it, true otherwise.
        667 */
        668goog.testing.events.fireTouchEndEvent = function(
        669 target, opt_coords, opt_eventProperties) {
        670 // TODO: Support multi-touch events with array of coordinates.
        671 var touchend =
        672 new goog.testing.events.Event(goog.events.EventType.TOUCHEND, target);
        673 goog.testing.events.setEventClientXY_(touchend, opt_coords);
        674 if (opt_eventProperties) {
        675 goog.object.extend(touchend, opt_eventProperties);
        676 }
        677 return goog.testing.events.fireBrowserEvent(touchend);
        678};
        679
        680
        681/**
        682 * Simulates a simple touch sequence on the given target.
        683 * @param {EventTarget} target The target for the event.
        684 * @param {goog.math.Coordinate=} opt_coords Touch position. Defaults to event
        685 * target's position (if available), otherwise (0, 0).
        686 * @param {Object=} opt_eventProperties Event properties to be mixed into the
        687 * BrowserEvent.
        688 * @return {boolean} The returnValue of the sequence: false if preventDefault()
        689 * was called on any of the events, true otherwise.
        690 */
        691goog.testing.events.fireTouchSequence = function(
        692 target, opt_coords, opt_eventProperties) {
        693 // TODO: Support multi-touch events with array of coordinates.
        694 // Fire touchstart, touchmove, touchend then return the bitwise AND of the 3.
        695 return !!(goog.testing.events.fireTouchStartEvent(
        696 target, opt_coords, opt_eventProperties) &
        697 goog.testing.events.fireTouchEndEvent(
        698 target, opt_coords, opt_eventProperties));
        699};
        700
        701
        702/**
        703 * Mixins a listenable into the given object. This turns the object
        704 * into a goog.events.Listenable. This is useful, for example, when
        705 * you need to mock a implementation of listenable and still want it
        706 * to work with goog.events.
        707 * @param {!Object} obj The object to mixin into.
        708 */
        709goog.testing.events.mixinListenable = function(obj) {
        710 var listenable = new goog.events.EventTarget();
        711
        712 listenable.setTargetForTesting(obj);
        713
        714 var listenablePrototype = goog.events.EventTarget.prototype;
        715 var disposablePrototype = goog.Disposable.prototype;
        716 for (var key in listenablePrototype) {
        717 if (listenablePrototype.hasOwnProperty(key) ||
        718 disposablePrototype.hasOwnProperty(key)) {
        719 var member = listenablePrototype[key];
        720 if (goog.isFunction(member)) {
        721 obj[key] = goog.bind(member, listenable);
        722 } else {
        723 obj[key] = member;
        724 }
        725 }
        726 }
        727};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/functionmock.js.src.html b/docs/source/lib/goog/testing/functionmock.js.src.html new file mode 100644 index 0000000..4fbf530 --- /dev/null +++ b/docs/source/lib/goog/testing/functionmock.js.src.html @@ -0,0 +1 @@ +functionmock.js

        lib/goog/testing/functionmock.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Enable mocking of functions not attached to objects
        17 * whether they be global / top-level or anonymous methods / closures.
        18 *
        19 * See the unit tests for usage.
        20 *
        21 */
        22
        23goog.provide('goog.testing');
        24goog.provide('goog.testing.FunctionMock');
        25goog.provide('goog.testing.GlobalFunctionMock');
        26goog.provide('goog.testing.MethodMock');
        27
        28goog.require('goog.object');
        29goog.require('goog.testing.LooseMock');
        30goog.require('goog.testing.Mock');
        31goog.require('goog.testing.PropertyReplacer');
        32goog.require('goog.testing.StrictMock');
        33
        34
        35/**
        36 * Class used to mock a function. Useful for mocking closures and anonymous
        37 * callbacks etc. Creates a function object that extends goog.testing.Mock.
        38 * @param {string=} opt_functionName The optional name of the function to mock.
        39 * Set to '[anonymous mocked function]' if not passed in.
        40 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        41 * goog.testing.Mock.STRICT. The default is STRICT.
        42 * @return {!goog.testing.MockInterface} The mocked function.
        43 * @suppress {missingProperties} Mocks do not fit in the type system well.
        44 */
        45goog.testing.FunctionMock = function(opt_functionName, opt_strictness) {
        46 var fn = function() {
        47 var args = Array.prototype.slice.call(arguments);
        48 args.splice(0, 0, opt_functionName || '[anonymous mocked function]');
        49 return fn.$mockMethod.apply(fn, args);
        50 };
        51 var base = opt_strictness === goog.testing.Mock.LOOSE ?
        52 goog.testing.LooseMock : goog.testing.StrictMock;
        53 goog.object.extend(fn, new base({}));
        54
        55 return /** @type {!goog.testing.MockInterface} */ (fn);
        56};
        57
        58
        59/**
        60 * Mocks an existing function. Creates a goog.testing.FunctionMock
        61 * and registers it in the given scope with the name specified by functionName.
        62 * @param {Object} scope The scope of the method to be mocked out.
        63 * @param {string} functionName The name of the function we're going to mock.
        64 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        65 * goog.testing.Mock.STRICT. The default is STRICT.
        66 * @return {!goog.testing.MockInterface} The mocked method.
        67 */
        68goog.testing.MethodMock = function(scope, functionName, opt_strictness) {
        69 if (!(functionName in scope)) {
        70 throw Error(functionName + ' is not a property of the given scope.');
        71 }
        72
        73 var fn = goog.testing.FunctionMock(functionName, opt_strictness);
        74
        75 fn.$propertyReplacer_ = new goog.testing.PropertyReplacer();
        76 fn.$propertyReplacer_.set(scope, functionName, fn);
        77 fn.$tearDown = goog.testing.MethodMock.$tearDown;
        78
        79 return fn;
        80};
        81
        82
        83/**
        84 * Resets the global function that we mocked back to its original state.
        85 * @this {goog.testing.MockInterface}
        86 */
        87goog.testing.MethodMock.$tearDown = function() {
        88 this.$propertyReplacer_.reset();
        89};
        90
        91
        92/**
        93 * Mocks a global / top-level function. Creates a goog.testing.MethodMock
        94 * in the global scope with the name specified by functionName.
        95 * @param {string} functionName The name of the function we're going to mock.
        96 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        97 * goog.testing.Mock.STRICT. The default is STRICT.
        98 * @return {!goog.testing.MockInterface} The mocked global function.
        99 */
        100goog.testing.GlobalFunctionMock = function(functionName, opt_strictness) {
        101 return goog.testing.MethodMock(goog.global, functionName, opt_strictness);
        102};
        103
        104
        105/**
        106 * Convenience method for creating a mock for a function.
        107 * @param {string=} opt_functionName The optional name of the function to mock
        108 * set to '[anonymous mocked function]' if not passed in.
        109 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        110 * goog.testing.Mock.STRICT. The default is STRICT.
        111 * @return {!goog.testing.MockInterface} The mocked function.
        112 */
        113goog.testing.createFunctionMock = function(opt_functionName, opt_strictness) {
        114 return goog.testing.FunctionMock(opt_functionName, opt_strictness);
        115};
        116
        117
        118/**
        119 * Convenience method for creating a mock for a method.
        120 * @param {Object} scope The scope of the method to be mocked out.
        121 * @param {string} functionName The name of the function we're going to mock.
        122 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        123 * goog.testing.Mock.STRICT. The default is STRICT.
        124 * @return {!goog.testing.MockInterface} The mocked global function.
        125 */
        126goog.testing.createMethodMock = function(scope, functionName, opt_strictness) {
        127 return goog.testing.MethodMock(scope, functionName, opt_strictness);
        128};
        129
        130
        131/**
        132 * Convenience method for creating a mock for a constructor. Copies class
        133 * members to the mock.
        134 *
        135 * <p>When mocking a constructor to return a mocked instance, remember to create
        136 * the instance mock before mocking the constructor. If you mock the constructor
        137 * first, then the mock framework will be unable to examine the prototype chain
        138 * when creating the mock instance.
        139 * @param {Object} scope The scope of the constructor to be mocked out.
        140 * @param {string} constructorName The name of the constructor we're going to
        141 * mock.
        142 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        143 * goog.testing.Mock.STRICT. The default is STRICT.
        144 * @return {!goog.testing.MockInterface} The mocked constructor.
        145 */
        146goog.testing.createConstructorMock = function(scope, constructorName,
        147 opt_strictness) {
        148 var realConstructor = scope[constructorName];
        149 var constructorMock = goog.testing.MethodMock(scope, constructorName,
        150 opt_strictness);
        151
        152 // Copy class members from the real constructor to the mock. Do not copy
        153 // the closure superClass_ property (see goog.inherits), the built-in
        154 // prototype property, or properties added to Function.prototype
        155 // (see goog.MODIFY_FUNCTION_PROTOTYPES in closure/base.js).
        156 for (var property in realConstructor) {
        157 if (property != 'superClass_' &&
        158 property != 'prototype' &&
        159 realConstructor.hasOwnProperty(property)) {
        160 constructorMock[property] = realConstructor[property];
        161 }
        162 }
        163 return constructorMock;
        164};
        165
        166
        167/**
        168 * Convenience method for creating a mocks for a global / top-level function.
        169 * @param {string} functionName The name of the function we're going to mock.
        170 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        171 * goog.testing.Mock.STRICT. The default is STRICT.
        172 * @return {!goog.testing.MockInterface} The mocked global function.
        173 */
        174goog.testing.createGlobalFunctionMock = function(functionName, opt_strictness) {
        175 return goog.testing.GlobalFunctionMock(functionName, opt_strictness);
        176};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/jsunit.js.src.html b/docs/source/lib/goog/testing/jsunit.js.src.html new file mode 100644 index 0000000..d93c3b7 --- /dev/null +++ b/docs/source/lib/goog/testing/jsunit.js.src.html @@ -0,0 +1 @@ +jsunit.js

        lib/goog/testing/jsunit.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Utilities for working with JsUnit. Writes out the JsUnit file
        17 * that needs to be included in every unit test.
        18 *
        19 * Testing code should not have dependencies outside of goog.testing so as to
        20 * reduce the chance of masking missing dependencies.
        21 *
        22 */
        23
        24goog.provide('goog.testing.jsunit');
        25
        26goog.require('goog.dom.TagName');
        27goog.require('goog.testing.TestCase');
        28goog.require('goog.testing.TestRunner');
        29
        30
        31/**
        32 * Base path for JsUnit app files, relative to Closure's base path.
        33 * @type {string}
        34 */
        35goog.testing.jsunit.BASE_PATH =
        36 '../../third_party/java/jsunit/core/app/';
        37
        38
        39/**
        40 * Filename for the core JS Unit script.
        41 * @type {string}
        42 */
        43goog.testing.jsunit.CORE_SCRIPT =
        44 goog.testing.jsunit.BASE_PATH + 'jsUnitCore.js';
        45
        46
        47/**
        48 * @define {boolean} If this code is being parsed by JsTestC, we let it disable
        49 * the onload handler to avoid running the test in JsTestC.
        50 */
        51goog.define('goog.testing.jsunit.AUTO_RUN_ONLOAD', true);
        52
        53
        54/**
        55 * @define {number} Sets a delay in milliseconds after the window onload event
        56 * and running the tests. Used to prevent interference with Selenium and give
        57 * tests with asynchronous operations time to finish loading.
        58 */
        59goog.define('goog.testing.jsunit.AUTO_RUN_DELAY_IN_MS', 500);
        60
        61
        62(function() {
        63 // Only allow one global test runner to be created on a page.
        64 if (goog.global['G_testRunner'] instanceof goog.testing.TestRunner) {
        65 return;
        66 }
        67
        68 // Increases the maximum number of stack frames in Google Chrome from the
        69 // default 10 to 50 to get more useful stack traces.
        70 Error.stackTraceLimit = 50;
        71
        72 // Store a reference to the window's timeout so that it can't be overridden
        73 // by tests.
        74 /** @type {!Function} */
        75 var realTimeout = window.setTimeout;
        76
        77 // Check for JsUnit's test runner (need to check for >2.2 and <=2.2)
        78 if (top['JsUnitTestManager'] || top['jsUnitTestManager']) {
        79 // Running inside JsUnit so add support code.
        80 var path = goog.basePath + goog.testing.jsunit.CORE_SCRIPT;
        81 document.write('<script type="text/javascript" src="' +
        82 path + '"></' + 'script>');
        83
        84 } else {
        85
        86 // Create a test runner.
        87 var tr = new goog.testing.TestRunner();
        88
        89 // Export it so that it can be queried by Selenium and tests that use a
        90 // compiled test runner.
        91 goog.exportSymbol('G_testRunner', tr);
        92 goog.exportSymbol('G_testRunner.initialize', tr.initialize);
        93 goog.exportSymbol('G_testRunner.isInitialized', tr.isInitialized);
        94 goog.exportSymbol('G_testRunner.isFinished', tr.isFinished);
        95 goog.exportSymbol('G_testRunner.isSuccess', tr.isSuccess);
        96 goog.exportSymbol('G_testRunner.getReport', tr.getReport);
        97 goog.exportSymbol('G_testRunner.getRunTime', tr.getRunTime);
        98 goog.exportSymbol('G_testRunner.getNumFilesLoaded', tr.getNumFilesLoaded);
        99 goog.exportSymbol('G_testRunner.setStrict', tr.setStrict);
        100 goog.exportSymbol('G_testRunner.logTestFailure', tr.logTestFailure);
        101 goog.exportSymbol('G_testRunner.getTestResults', tr.getTestResults);
        102
        103 // Export debug as a global function for JSUnit compatibility. This just
        104 // calls log on the current test case.
        105 if (!goog.global['debug']) {
        106 goog.exportSymbol('debug', goog.bind(tr.log, tr));
        107 }
        108
        109 // If the application has defined a global error filter, set it now. This
        110 // allows users who use a base test include to set the error filter before
        111 // the testing code is loaded.
        112 if (goog.global['G_errorFilter']) {
        113 tr.setErrorFilter(goog.global['G_errorFilter']);
        114 }
        115
        116 // Add an error handler to report errors that may occur during
        117 // initialization of the page.
        118 var onerror = window.onerror;
        119 window.onerror = function(error, url, line) {
        120 // Call any existing onerror handlers.
        121 if (onerror) {
        122 onerror(error, url, line);
        123 }
        124 if (typeof error == 'object') {
        125 // Webkit started passing an event object as the only argument to
        126 // window.onerror. It doesn't contain an error message, url or line
        127 // number. We therefore log as much info as we can.
        128 if (error.target && error.target.tagName == goog.dom.TagName.SCRIPT) {
        129 tr.logError('UNKNOWN ERROR: Script ' + error.target.src);
        130 } else {
        131 tr.logError('UNKNOWN ERROR: No error information available.');
        132 }
        133 } else {
        134 tr.logError('JS ERROR: ' + error + '\nURL: ' + url + '\nLine: ' + line);
        135 }
        136 };
        137
        138 // Create an onload handler, if the test runner hasn't been initialized then
        139 // no test has been registered with the test runner by the test file. We
        140 // then create a new test case and auto discover any tests in the global
        141 // scope. If this code is being parsed by JsTestC, we let it disable the
        142 // onload handler to avoid running the test in JsTestC.
        143 if (goog.testing.jsunit.AUTO_RUN_ONLOAD) {
        144 var onload = window.onload;
        145 window.onload = function(e) {
        146 // Call any existing onload handlers.
        147 if (onload) {
        148 onload(e);
        149 }
        150 // Wait so that we don't interfere with WebDriver.
        151 realTimeout(function() {
        152 if (!tr.initialized) {
        153 var testCase = new goog.testing.TestCase(document.title);
        154 goog.testing.TestCase.initializeTestRunner(testCase);
        155 }
        156 tr.execute();
        157 }, goog.testing.jsunit.AUTO_RUN_DELAY_IN_MS);
        158 window.onload = null;
        159 };
        160 }
        161 }
        162})();
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/loosemock.js.src.html b/docs/source/lib/goog/testing/loosemock.js.src.html new file mode 100644 index 0000000..1aacbe1 --- /dev/null +++ b/docs/source/lib/goog/testing/loosemock.js.src.html @@ -0,0 +1 @@ +loosemock.js

        lib/goog/testing/loosemock.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview This file defines a loose mock implementation.
        17 */
        18
        19goog.provide('goog.testing.LooseExpectationCollection');
        20goog.provide('goog.testing.LooseMock');
        21
        22goog.require('goog.array');
        23goog.require('goog.structs.Map');
        24goog.require('goog.testing.Mock');
        25
        26
        27
        28/**
        29 * This class is an ordered collection of expectations for one method. Since
        30 * the loose mock does most of its verification at the time of $verify, this
        31 * class is necessary to manage the return/throw behavior when the mock is
        32 * being called.
        33 * @constructor
        34 * @final
        35 */
        36goog.testing.LooseExpectationCollection = function() {
        37 /**
        38 * The list of expectations. All of these should have the same name.
        39 * @type {Array<goog.testing.MockExpectation>}
        40 * @private
        41 */
        42 this.expectations_ = [];
        43};
        44
        45
        46/**
        47 * Adds an expectation to this collection.
        48 * @param {goog.testing.MockExpectation} expectation The expectation to add.
        49 */
        50goog.testing.LooseExpectationCollection.prototype.addExpectation =
        51 function(expectation) {
        52 this.expectations_.push(expectation);
        53};
        54
        55
        56/**
        57 * Gets the list of expectations in this collection.
        58 * @return {Array<goog.testing.MockExpectation>} The array of expectations.
        59 */
        60goog.testing.LooseExpectationCollection.prototype.getExpectations = function() {
        61 return this.expectations_;
        62};
        63
        64
        65
        66/**
        67 * This is a mock that does not care about the order of method calls. As a
        68 * result, it won't throw exceptions until verify() is called. The only
        69 * exception is that if a method is called that has no expectations, then an
        70 * exception will be thrown.
        71 * @param {Object|Function} objectToMock The object that should be mocked, or
        72 * the constructor of an object to mock.
        73 * @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected
        74 * calls.
        75 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
        76 * a mock should be constructed from the static functions of a class.
        77 * @param {boolean=} opt_createProxy An optional argument denoting that
        78 * a proxy for the target mock should be created.
        79 * @constructor
        80 * @extends {goog.testing.Mock}
        81 */
        82goog.testing.LooseMock = function(objectToMock, opt_ignoreUnexpectedCalls,
        83 opt_mockStaticMethods, opt_createProxy) {
        84 goog.testing.Mock.call(this, objectToMock, opt_mockStaticMethods,
        85 opt_createProxy);
        86
        87 /**
        88 * A map of method names to a LooseExpectationCollection for that method.
        89 * @type {goog.structs.Map}
        90 * @private
        91 */
        92 this.$expectations_ = new goog.structs.Map();
        93
        94 /**
        95 * The calls that have been made; we cache them to verify at the end. Each
        96 * element is an array where the first element is the name, and the second
        97 * element is the arguments.
        98 * @type {Array<Array<*>>}
        99 * @private
        100 */
        101 this.$calls_ = [];
        102
        103 /**
        104 * Whether to ignore unexpected calls.
        105 * @type {boolean}
        106 * @private
        107 */
        108 this.$ignoreUnexpectedCalls_ = !!opt_ignoreUnexpectedCalls;
        109};
        110goog.inherits(goog.testing.LooseMock, goog.testing.Mock);
        111
        112
        113/**
        114 * A setter for the ignoreUnexpectedCalls field.
        115 * @param {boolean} ignoreUnexpectedCalls Whether to ignore unexpected calls.
        116 * @return {!goog.testing.LooseMock} This mock object.
        117 */
        118goog.testing.LooseMock.prototype.$setIgnoreUnexpectedCalls = function(
        119 ignoreUnexpectedCalls) {
        120 this.$ignoreUnexpectedCalls_ = ignoreUnexpectedCalls;
        121 return this;
        122};
        123
        124
        125/** @override */
        126goog.testing.LooseMock.prototype.$recordExpectation = function() {
        127 if (!this.$expectations_.containsKey(this.$pendingExpectation.name)) {
        128 this.$expectations_.set(this.$pendingExpectation.name,
        129 new goog.testing.LooseExpectationCollection());
        130 }
        131
        132 var collection = this.$expectations_.get(this.$pendingExpectation.name);
        133 collection.addExpectation(this.$pendingExpectation);
        134};
        135
        136
        137/** @override */
        138goog.testing.LooseMock.prototype.$recordCall = function(name, args) {
        139 if (!this.$expectations_.containsKey(name)) {
        140 if (this.$ignoreUnexpectedCalls_) {
        141 return;
        142 }
        143 this.$throwCallException(name, args);
        144 }
        145
        146 // Start from the beginning of the expectations for this name,
        147 // and iterate over them until we find an expectation that matches
        148 // and also has calls remaining.
        149 var collection = this.$expectations_.get(name);
        150 var matchingExpectation = null;
        151 var expectations = collection.getExpectations();
        152 for (var i = 0; i < expectations.length; i++) {
        153 var expectation = expectations[i];
        154 if (this.$verifyCall(expectation, name, args)) {
        155 matchingExpectation = expectation;
        156 if (expectation.actualCalls < expectation.maxCalls) {
        157 break;
        158 } // else continue and see if we can find something that does match
        159 }
        160 }
        161 if (matchingExpectation == null) {
        162 this.$throwCallException(name, args, expectation);
        163 }
        164
        165 matchingExpectation.actualCalls++;
        166 if (matchingExpectation.actualCalls > matchingExpectation.maxCalls) {
        167 this.$throwException('Too many calls to ' + matchingExpectation.name +
        168 '\nExpected: ' + matchingExpectation.maxCalls + ' but was: ' +
        169 matchingExpectation.actualCalls);
        170 }
        171
        172 this.$calls_.push([name, args]);
        173 return this.$do(matchingExpectation, args);
        174};
        175
        176
        177/** @override */
        178goog.testing.LooseMock.prototype.$reset = function() {
        179 goog.testing.LooseMock.superClass_.$reset.call(this);
        180
        181 this.$expectations_ = new goog.structs.Map();
        182 this.$calls_ = [];
        183};
        184
        185
        186/** @override */
        187goog.testing.LooseMock.prototype.$replay = function() {
        188 goog.testing.LooseMock.superClass_.$replay.call(this);
        189
        190 // Verify that there are no expectations that can never be reached.
        191 // This can't catch every situation, but it is a decent sanity check
        192 // and it's similar to the behavior of EasyMock in java.
        193 var collections = this.$expectations_.getValues();
        194 for (var i = 0; i < collections.length; i++) {
        195 var expectations = collections[i].getExpectations();
        196 for (var j = 0; j < expectations.length; j++) {
        197 var expectation = expectations[j];
        198 // If this expectation can be called infinite times, then
        199 // check if any subsequent expectation has the exact same
        200 // argument list.
        201 if (!isFinite(expectation.maxCalls)) {
        202 for (var k = j + 1; k < expectations.length; k++) {
        203 var laterExpectation = expectations[k];
        204 if (laterExpectation.minCalls > 0 &&
        205 goog.array.equals(expectation.argumentList,
        206 laterExpectation.argumentList)) {
        207 var name = expectation.name;
        208 var argsString = this.$argumentsAsString(expectation.argumentList);
        209 this.$throwException([
        210 'Expected call to ', name, ' with arguments ', argsString,
        211 ' has an infinite max number of calls; can\'t expect an',
        212 ' identical call later with a positive min number of calls'
        213 ].join(''));
        214 }
        215 }
        216 }
        217 }
        218 }
        219};
        220
        221
        222/** @override */
        223goog.testing.LooseMock.prototype.$verify = function() {
        224 goog.testing.LooseMock.superClass_.$verify.call(this);
        225 var collections = this.$expectations_.getValues();
        226
        227 for (var i = 0; i < collections.length; i++) {
        228 var expectations = collections[i].getExpectations();
        229 for (var j = 0; j < expectations.length; j++) {
        230 var expectation = expectations[j];
        231 if (expectation.actualCalls > expectation.maxCalls) {
        232 this.$throwException('Too many calls to ' + expectation.name +
        233 '\nExpected: ' + expectation.maxCalls + ' but was: ' +
        234 expectation.actualCalls);
        235 } else if (expectation.actualCalls < expectation.minCalls) {
        236 this.$throwException('Not enough calls to ' + expectation.name +
        237 '\nExpected: ' + expectation.minCalls + ' but was: ' +
        238 expectation.actualCalls);
        239 }
        240 }
        241 }
        242};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/mock.js.src.html b/docs/source/lib/goog/testing/mock.js.src.html new file mode 100644 index 0000000..e7fef80 --- /dev/null +++ b/docs/source/lib/goog/testing/mock.js.src.html @@ -0,0 +1 @@ +mock.js

        lib/goog/testing/mock.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview This file defines base classes used for creating mocks in
        17 * JavaScript. The API was inspired by EasyMock.
        18 *
        19 * The basic API is:
        20 * <ul>
        21 * <li>Create an object to be mocked
        22 * <li>Create a mock object, passing in the above object to the constructor
        23 * <li>Set expectations by calling methods on the mock object
        24 * <li>Call $replay() on the mock object
        25 * <li>Pass the mock to code that will make real calls on it
        26 * <li>Call $verify() to make sure that expectations were met
        27 * </ul>
        28 *
        29 * For examples, please see the unit tests for LooseMock and StrictMock.
        30 *
        31 * Still TODO
        32 * implement better (and pluggable) argument matching
        33 * Have the exceptions for LooseMock show the number of expected/actual calls
        34 * loose and strict mocks share a lot of code - move it to the base class
        35 *
        36 */
        37
        38goog.provide('goog.testing.Mock');
        39goog.provide('goog.testing.MockExpectation');
        40
        41goog.require('goog.array');
        42goog.require('goog.object');
        43goog.require('goog.testing.JsUnitException');
        44goog.require('goog.testing.MockInterface');
        45goog.require('goog.testing.mockmatchers');
        46
        47
        48
        49/**
        50 * This is a class that represents an expectation.
        51 * @param {string} name The name of the method for this expectation.
        52 * @constructor
        53 * @final
        54 */
        55goog.testing.MockExpectation = function(name) {
        56 /**
        57 * The name of the method that is expected to be called.
        58 * @type {string}
        59 */
        60 this.name = name;
        61
        62 /**
        63 * An array of error messages for expectations not met.
        64 * @type {Array<string>}
        65 */
        66 this.errorMessages = [];
        67};
        68
        69
        70/**
        71 * The minimum number of times this method should be called.
        72 * @type {number}
        73 */
        74goog.testing.MockExpectation.prototype.minCalls = 1;
        75
        76
        77/**
        78 * The maximum number of times this method should be called.
        79 * @type {number}
        80 */
        81goog.testing.MockExpectation.prototype.maxCalls = 1;
        82
        83
        84/**
        85 * The value that this method should return.
        86 * @type {*}
        87 */
        88goog.testing.MockExpectation.prototype.returnValue;
        89
        90
        91/**
        92 * The value that will be thrown when the method is called
        93 * @type {*}
        94 */
        95goog.testing.MockExpectation.prototype.exceptionToThrow;
        96
        97
        98/**
        99 * The arguments that are expected to be passed to this function
        100 * @type {Array<*>}
        101 */
        102goog.testing.MockExpectation.prototype.argumentList;
        103
        104
        105/**
        106 * The number of times this method is called by real code.
        107 * @type {number}
        108 */
        109goog.testing.MockExpectation.prototype.actualCalls = 0;
        110
        111
        112/**
        113 * The number of times this method is called during the verification phase.
        114 * @type {number}
        115 */
        116goog.testing.MockExpectation.prototype.verificationCalls = 0;
        117
        118
        119/**
        120 * The function which will be executed when this method is called.
        121 * Method arguments will be passed to this function, and return value
        122 * of this function will be returned by the method.
        123 * @type {Function}
        124 */
        125goog.testing.MockExpectation.prototype.toDo;
        126
        127
        128/**
        129 * Allow expectation failures to include messages.
        130 * @param {string} message The failure message.
        131 */
        132goog.testing.MockExpectation.prototype.addErrorMessage = function(message) {
        133 this.errorMessages.push(message);
        134};
        135
        136
        137/**
        138 * Get the error messages seen so far.
        139 * @return {string} Error messages separated by \n.
        140 */
        141goog.testing.MockExpectation.prototype.getErrorMessage = function() {
        142 return this.errorMessages.join('\n');
        143};
        144
        145
        146/**
        147 * Get how many error messages have been seen so far.
        148 * @return {number} Count of error messages.
        149 */
        150goog.testing.MockExpectation.prototype.getErrorMessageCount = function() {
        151 return this.errorMessages.length;
        152};
        153
        154
        155
        156/**
        157 * The base class for a mock object.
        158 * @param {Object|Function} objectToMock The object that should be mocked, or
        159 * the constructor of an object to mock.
        160 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
        161 * a mock should be constructed from the static functions of a class.
        162 * @param {boolean=} opt_createProxy An optional argument denoting that
        163 * a proxy for the target mock should be created.
        164 * @constructor
        165 * @implements {goog.testing.MockInterface}
        166 */
        167goog.testing.Mock = function(objectToMock, opt_mockStaticMethods,
        168 opt_createProxy) {
        169 if (!goog.isObject(objectToMock) && !goog.isFunction(objectToMock)) {
        170 throw new Error('objectToMock must be an object or constructor.');
        171 }
        172 if (opt_createProxy && !opt_mockStaticMethods &&
        173 goog.isFunction(objectToMock)) {
        174 /**
        175 * @constructor
        176 * @final
        177 */
        178 var tempCtor = function() {};
        179 goog.inherits(tempCtor, objectToMock);
        180 this.$proxy = new tempCtor();
        181 } else if (opt_createProxy && opt_mockStaticMethods &&
        182 goog.isFunction(objectToMock)) {
        183 throw Error('Cannot create a proxy when opt_mockStaticMethods is true');
        184 } else if (opt_createProxy && !goog.isFunction(objectToMock)) {
        185 throw Error('Must have a constructor to create a proxy');
        186 }
        187
        188 if (goog.isFunction(objectToMock) && !opt_mockStaticMethods) {
        189 this.$initializeFunctions_(objectToMock.prototype);
        190 } else {
        191 this.$initializeFunctions_(objectToMock);
        192 }
        193 this.$argumentListVerifiers_ = {};
        194};
        195
        196
        197/**
        198 * Option that may be passed when constructing function, method, and
        199 * constructor mocks. Indicates that the expected calls should be accepted in
        200 * any order.
        201 * @const
        202 * @type {number}
        203 */
        204goog.testing.Mock.LOOSE = 1;
        205
        206
        207/**
        208 * Option that may be passed when constructing function, method, and
        209 * constructor mocks. Indicates that the expected calls should be accepted in
        210 * the recorded order only.
        211 * @const
        212 * @type {number}
        213 */
        214goog.testing.Mock.STRICT = 0;
        215
        216
        217/**
        218 * This array contains the name of the functions that are part of the base
        219 * Object prototype.
        220 * Basically a copy of goog.object.PROTOTYPE_FIELDS_.
        221 * @const
        222 * @type {!Array<string>}
        223 * @private
        224 */
        225goog.testing.Mock.PROTOTYPE_FIELDS_ = [
        226 'constructor',
        227 'hasOwnProperty',
        228 'isPrototypeOf',
        229 'propertyIsEnumerable',
        230 'toLocaleString',
        231 'toString',
        232 'valueOf'
        233];
        234
        235
        236/**
        237 * A proxy for the mock. This can be used for dependency injection in lieu of
        238 * the mock if the test requires a strict instanceof check.
        239 * @type {Object}
        240 */
        241goog.testing.Mock.prototype.$proxy = null;
        242
        243
        244/**
        245 * Map of argument name to optional argument list verifier function.
        246 * @type {Object}
        247 */
        248goog.testing.Mock.prototype.$argumentListVerifiers_;
        249
        250
        251/**
        252 * Whether or not we are in recording mode.
        253 * @type {boolean}
        254 * @private
        255 */
        256goog.testing.Mock.prototype.$recording_ = true;
        257
        258
        259/**
        260 * The expectation currently being created. All methods that modify the
        261 * current expectation return the Mock object for easy chaining, so this is
        262 * where we keep track of the expectation that's currently being modified.
        263 * @type {goog.testing.MockExpectation}
        264 * @protected
        265 */
        266goog.testing.Mock.prototype.$pendingExpectation;
        267
        268
        269/**
        270 * First exception thrown by this mock; used in $verify.
        271 * @type {Object}
        272 * @private
        273 */
        274goog.testing.Mock.prototype.$threwException_ = null;
        275
        276
        277/**
        278 * Initializes the functions on the mock object.
        279 * @param {Object} objectToMock The object being mocked.
        280 * @private
        281 */
        282goog.testing.Mock.prototype.$initializeFunctions_ = function(objectToMock) {
        283 // Gets the object properties.
        284 var enumerableProperties = goog.object.getKeys(objectToMock);
        285
        286 // The non enumerable properties are added if they override the ones in the
        287 // Object prototype. This is due to the fact that IE8 does not enumerate any
        288 // of the prototype Object functions even when overriden and mocking these is
        289 // sometimes needed.
        290 for (var i = 0; i < goog.testing.Mock.PROTOTYPE_FIELDS_.length; i++) {
        291 var prop = goog.testing.Mock.PROTOTYPE_FIELDS_[i];
        292 // Look at b/6758711 if you're considering adding ALL properties to ALL
        293 // mocks.
        294 if (objectToMock[prop] !== Object.prototype[prop]) {
        295 enumerableProperties.push(prop);
        296 }
        297 }
        298
        299 // Adds the properties to the mock.
        300 for (var i = 0; i < enumerableProperties.length; i++) {
        301 var prop = enumerableProperties[i];
        302 if (typeof objectToMock[prop] == 'function') {
        303 this[prop] = goog.bind(this.$mockMethod, this, prop);
        304 if (this.$proxy) {
        305 this.$proxy[prop] = goog.bind(this.$mockMethod, this, prop);
        306 }
        307 }
        308 }
        309};
        310
        311
        312/**
        313 * Registers a verfifier function to use when verifying method argument lists.
        314 * @param {string} methodName The name of the method for which the verifierFn
        315 * should be used.
        316 * @param {Function} fn Argument list verifier function. Should take 2 argument
        317 * arrays as arguments, and return true if they are considered equivalent.
        318 * @return {!goog.testing.Mock} This mock object.
        319 */
        320goog.testing.Mock.prototype.$registerArgumentListVerifier = function(methodName,
        321 fn) {
        322 this.$argumentListVerifiers_[methodName] = fn;
        323 return this;
        324};
        325
        326
        327/**
        328 * The function that replaces all methods on the mock object.
        329 * @param {string} name The name of the method being mocked.
        330 * @return {*} In record mode, returns the mock object. In replay mode, returns
        331 * whatever the creator of the mock set as the return value.
        332 */
        333goog.testing.Mock.prototype.$mockMethod = function(name) {
        334 try {
        335 // Shift off the name argument so that args contains the arguments to
        336 // the mocked method.
        337 var args = goog.array.slice(arguments, 1);
        338 if (this.$recording_) {
        339 this.$pendingExpectation = new goog.testing.MockExpectation(name);
        340 this.$pendingExpectation.argumentList = args;
        341 this.$recordExpectation();
        342 return this;
        343 } else {
        344 return this.$recordCall(name, args);
        345 }
        346 } catch (ex) {
        347 this.$recordAndThrow(ex);
        348 }
        349};
        350
        351
        352/**
        353 * Records the currently pending expectation, intended to be overridden by a
        354 * subclass.
        355 * @protected
        356 */
        357goog.testing.Mock.prototype.$recordExpectation = function() {};
        358
        359
        360/**
        361 * Records an actual method call, intended to be overridden by a
        362 * subclass. The subclass must find the pending expectation and return the
        363 * correct value.
        364 * @param {string} name The name of the method being called.
        365 * @param {Array<?>} args The arguments to the method.
        366 * @return {*} The return expected by the mock.
        367 * @protected
        368 */
        369goog.testing.Mock.prototype.$recordCall = function(name, args) {
        370 return undefined;
        371};
        372
        373
        374/**
        375 * If the expectation expects to throw, this method will throw.
        376 * @param {goog.testing.MockExpectation} expectation The expectation.
        377 */
        378goog.testing.Mock.prototype.$maybeThrow = function(expectation) {
        379 if (typeof expectation.exceptionToThrow != 'undefined') {
        380 throw expectation.exceptionToThrow;
        381 }
        382};
        383
        384
        385/**
        386 * If this expectation defines a function to be called,
        387 * it will be called and its result will be returned.
        388 * Otherwise, if the expectation expects to throw, it will throw.
        389 * Otherwise, this method will return defined value.
        390 * @param {goog.testing.MockExpectation} expectation The expectation.
        391 * @param {Array<?>} args The arguments to the method.
        392 * @return {*} The return value expected by the mock.
        393 */
        394goog.testing.Mock.prototype.$do = function(expectation, args) {
        395 if (typeof expectation.toDo == 'undefined') {
        396 this.$maybeThrow(expectation);
        397 return expectation.returnValue;
        398 } else {
        399 return expectation.toDo.apply(this, args);
        400 }
        401};
        402
        403
        404/**
        405 * Specifies a return value for the currently pending expectation.
        406 * @param {*} val The return value.
        407 * @return {!goog.testing.Mock} This mock object.
        408 */
        409goog.testing.Mock.prototype.$returns = function(val) {
        410 this.$pendingExpectation.returnValue = val;
        411 return this;
        412};
        413
        414
        415/**
        416 * Specifies a value for the currently pending expectation to throw.
        417 * @param {*} val The value to throw.
        418 * @return {!goog.testing.Mock} This mock object.
        419 */
        420goog.testing.Mock.prototype.$throws = function(val) {
        421 this.$pendingExpectation.exceptionToThrow = val;
        422 return this;
        423};
        424
        425
        426/**
        427 * Specifies a function to call for currently pending expectation.
        428 * Note, that using this method overrides declarations made
        429 * using $returns() and $throws() methods.
        430 * @param {Function} func The function to call.
        431 * @return {!goog.testing.Mock} This mock object.
        432 */
        433goog.testing.Mock.prototype.$does = function(func) {
        434 this.$pendingExpectation.toDo = func;
        435 return this;
        436};
        437
        438
        439/**
        440 * Allows the expectation to be called 0 or 1 times.
        441 * @return {!goog.testing.Mock} This mock object.
        442 */
        443goog.testing.Mock.prototype.$atMostOnce = function() {
        444 this.$pendingExpectation.minCalls = 0;
        445 this.$pendingExpectation.maxCalls = 1;
        446 return this;
        447};
        448
        449
        450/**
        451 * Allows the expectation to be called any number of times, as long as it's
        452 * called once.
        453 * @return {!goog.testing.Mock} This mock object.
        454 */
        455goog.testing.Mock.prototype.$atLeastOnce = function() {
        456 this.$pendingExpectation.maxCalls = Infinity;
        457 return this;
        458};
        459
        460
        461/**
        462 * Allows the expectation to be called exactly once.
        463 * @return {!goog.testing.Mock} This mock object.
        464 */
        465goog.testing.Mock.prototype.$once = function() {
        466 this.$pendingExpectation.minCalls = 1;
        467 this.$pendingExpectation.maxCalls = 1;
        468 return this;
        469};
        470
        471
        472/**
        473 * Disallows the expectation from being called.
        474 * @return {!goog.testing.Mock} This mock object.
        475 */
        476goog.testing.Mock.prototype.$never = function() {
        477 this.$pendingExpectation.minCalls = 0;
        478 this.$pendingExpectation.maxCalls = 0;
        479 return this;
        480};
        481
        482
        483/**
        484 * Allows the expectation to be called any number of times.
        485 * @return {!goog.testing.Mock} This mock object.
        486 */
        487goog.testing.Mock.prototype.$anyTimes = function() {
        488 this.$pendingExpectation.minCalls = 0;
        489 this.$pendingExpectation.maxCalls = Infinity;
        490 return this;
        491};
        492
        493
        494/**
        495 * Specifies the number of times the expectation should be called.
        496 * @param {number} times The number of times this method will be called.
        497 * @return {!goog.testing.Mock} This mock object.
        498 */
        499goog.testing.Mock.prototype.$times = function(times) {
        500 this.$pendingExpectation.minCalls = times;
        501 this.$pendingExpectation.maxCalls = times;
        502 return this;
        503};
        504
        505
        506/**
        507 * Switches from recording to replay mode.
        508 * @override
        509 */
        510goog.testing.Mock.prototype.$replay = function() {
        511 this.$recording_ = false;
        512};
        513
        514
        515/**
        516 * Resets the state of this mock object. This clears all pending expectations
        517 * without verifying, and puts the mock in recording mode.
        518 * @override
        519 */
        520goog.testing.Mock.prototype.$reset = function() {
        521 this.$recording_ = true;
        522 this.$threwException_ = null;
        523 delete this.$pendingExpectation;
        524};
        525
        526
        527/**
        528 * Throws an exception and records that an exception was thrown.
        529 * @param {string} comment A short comment about the exception.
        530 * @param {?string=} opt_message A longer message about the exception.
        531 * @throws {Object} JsUnitException object.
        532 * @protected
        533 */
        534goog.testing.Mock.prototype.$throwException = function(comment, opt_message) {
        535 this.$recordAndThrow(new goog.testing.JsUnitException(comment, opt_message));
        536};
        537
        538
        539/**
        540 * Throws an exception and records that an exception was thrown.
        541 * @param {Object} ex Exception.
        542 * @throws {Object} #ex.
        543 * @protected
        544 */
        545goog.testing.Mock.prototype.$recordAndThrow = function(ex) {
        546 // If it's an assert exception, record it.
        547 if (ex['isJsUnitException']) {
        548 var testRunner = goog.global['G_testRunner'];
        549 if (testRunner) {
        550 var logTestFailureFunction = testRunner['logTestFailure'];
        551 if (logTestFailureFunction) {
        552 logTestFailureFunction.call(testRunner, ex);
        553 }
        554 }
        555
        556 if (!this.$threwException_) {
        557 // Only remember first exception thrown.
        558 this.$threwException_ = ex;
        559 }
        560 }
        561 throw ex;
        562};
        563
        564
        565/**
        566 * Verify that all of the expectations were met. Should be overridden by
        567 * subclasses.
        568 * @override
        569 */
        570goog.testing.Mock.prototype.$verify = function() {
        571 if (this.$threwException_) {
        572 throw this.$threwException_;
        573 }
        574};
        575
        576
        577/**
        578 * Verifies that a method call matches an expectation.
        579 * @param {goog.testing.MockExpectation} expectation The expectation to check.
        580 * @param {string} name The name of the called method.
        581 * @param {Array<*>?} args The arguments passed to the mock.
        582 * @return {boolean} Whether the call matches the expectation.
        583 */
        584goog.testing.Mock.prototype.$verifyCall = function(expectation, name, args) {
        585 if (expectation.name != name) {
        586 return false;
        587 }
        588 var verifierFn =
        589 this.$argumentListVerifiers_.hasOwnProperty(expectation.name) ?
        590 this.$argumentListVerifiers_[expectation.name] :
        591 goog.testing.mockmatchers.flexibleArrayMatcher;
        592
        593 return verifierFn(expectation.argumentList, args, expectation);
        594};
        595
        596
        597/**
        598 * Render the provided argument array to a string to help
        599 * clients with debugging tests.
        600 * @param {Array<*>?} args The arguments passed to the mock.
        601 * @return {string} Human-readable string.
        602 */
        603goog.testing.Mock.prototype.$argumentsAsString = function(args) {
        604 var retVal = [];
        605 for (var i = 0; i < args.length; i++) {
        606 try {
        607 retVal.push(goog.typeOf(args[i]));
        608 } catch (e) {
        609 retVal.push('[unknown]');
        610 }
        611 }
        612 return '(' + retVal.join(', ') + ')';
        613};
        614
        615
        616/**
        617 * Throw an exception based on an incorrect method call.
        618 * @param {string} name Name of method called.
        619 * @param {Array<*>?} args Arguments passed to the mock.
        620 * @param {goog.testing.MockExpectation=} opt_expectation Expected next call,
        621 * if any.
        622 */
        623goog.testing.Mock.prototype.$throwCallException = function(name, args,
        624 opt_expectation) {
        625 var errorStringBuffer = [];
        626 var actualArgsString = this.$argumentsAsString(args);
        627 var expectedArgsString = opt_expectation ?
        628 this.$argumentsAsString(opt_expectation.argumentList) : '';
        629
        630 if (opt_expectation && opt_expectation.name == name) {
        631 errorStringBuffer.push('Bad arguments to ', name, '().\n',
        632 'Actual: ', actualArgsString, '\n',
        633 'Expected: ', expectedArgsString, '\n',
        634 opt_expectation.getErrorMessage());
        635 } else {
        636 errorStringBuffer.push('Unexpected call to ', name,
        637 actualArgsString, '.');
        638 if (opt_expectation) {
        639 errorStringBuffer.push('\nNext expected call was to ',
        640 opt_expectation.name,
        641 expectedArgsString);
        642 }
        643 }
        644 this.$throwException(errorStringBuffer.join(''));
        645};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/mockclock.js.src.html b/docs/source/lib/goog/testing/mockclock.js.src.html new file mode 100644 index 0000000..01b8354 --- /dev/null +++ b/docs/source/lib/goog/testing/mockclock.js.src.html @@ -0,0 +1 @@ +mockclock.js

        lib/goog/testing/mockclock.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Mock Clock implementation for working with setTimeout,
        17 * setInterval, clearTimeout and clearInterval within unit tests.
        18 *
        19 * Derived from jsUnitMockTimeout.js, contributed to JsUnit by
        20 * Pivotal Computer Systems, www.pivotalsf.com
        21 *
        22 */
        23
        24goog.provide('goog.testing.MockClock');
        25
        26goog.require('goog.Disposable');
        27goog.require('goog.async.run');
        28goog.require('goog.testing.PropertyReplacer');
        29goog.require('goog.testing.events');
        30goog.require('goog.testing.events.Event');
        31goog.require('goog.testing.watchers');
        32
        33
        34
        35/**
        36 * Class for unit testing code that uses setTimeout and clearTimeout.
        37 *
        38 * NOTE: If you are using MockClock to test code that makes use of
        39 * goog.fx.Animation, then you must either:
        40 *
        41 * 1. Install and dispose of the MockClock in setUpPage() and tearDownPage()
        42 * respectively (rather than setUp()/tearDown()).
        43 *
        44 * or
        45 *
        46 * 2. Ensure that every test clears the animation queue by calling
        47 * mockClock.tick(x) at the end of each test function (where `x` is large
        48 * enough to complete all animations).
        49 *
        50 * Otherwise, if any animation is left pending at the time that
        51 * MockClock.dispose() is called, that will permanently prevent any future
        52 * animations from playing on the page.
        53 *
        54 * @param {boolean=} opt_autoInstall Install the MockClock at construction time.
        55 * @constructor
        56 * @extends {goog.Disposable}
        57 * @final
        58 */
        59goog.testing.MockClock = function(opt_autoInstall) {
        60 goog.Disposable.call(this);
        61
        62 /**
        63 * Reverse-order queue of timers to fire.
        64 *
        65 * The last item of the queue is popped off. Insertion happens from the
        66 * right. For example, the expiration times for each element of the queue
        67 * might be in the order 300, 200, 200.
        68 *
        69 * @type {Array<Object>}
        70 * @private
        71 */
        72 this.queue_ = [];
        73
        74 /**
        75 * Set of timeouts that should be treated as cancelled.
        76 *
        77 * Rather than removing cancelled timers directly from the queue, this set
        78 * simply marks them as deleted so that they can be ignored when their
        79 * turn comes up. The keys are the timeout keys that are cancelled, each
        80 * mapping to true.
        81 *
        82 * @type {Object}
        83 * @private
        84 */
        85 this.deletedKeys_ = {};
        86
        87 if (opt_autoInstall) {
        88 this.install();
        89 }
        90};
        91goog.inherits(goog.testing.MockClock, goog.Disposable);
        92
        93
        94/**
        95 * Default wait timeout for mocking requestAnimationFrame (in milliseconds).
        96 *
        97 * @type {number}
        98 * @const
        99 */
        100goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT = 20;
        101
        102
        103/**
        104 * ID to use for next timeout. Timeout IDs must never be reused, even across
        105 * MockClock instances.
        106 * @public {number}
        107 */
        108goog.testing.MockClock.nextId = Math.round(Math.random() * 10000);
        109
        110
        111/**
        112 * Count of the number of timeouts made by this instance.
        113 * @type {number}
        114 * @private
        115 */
        116goog.testing.MockClock.prototype.timeoutsMade_ = 0;
        117
        118
        119/**
        120 * PropertyReplacer instance which overwrites and resets setTimeout,
        121 * setInterval, etc. or null if the MockClock is not installed.
        122 * @type {goog.testing.PropertyReplacer}
        123 * @private
        124 */
        125goog.testing.MockClock.prototype.replacer_ = null;
        126
        127
        128/**
        129 * Map of deleted keys. These keys represents keys that were deleted in a
        130 * clearInterval, timeoutid -> object.
        131 * @type {Object}
        132 * @private
        133 */
        134goog.testing.MockClock.prototype.deletedKeys_ = null;
        135
        136
        137/**
        138 * The current simulated time in milliseconds.
        139 * @type {number}
        140 * @private
        141 */
        142goog.testing.MockClock.prototype.nowMillis_ = 0;
        143
        144
        145/**
        146 * Additional delay between the time a timeout was set to fire, and the time
        147 * it actually fires. Useful for testing workarounds for this Firefox 2 bug:
        148 * https://bugzilla.mozilla.org/show_bug.cgi?id=291386
        149 * May be negative.
        150 * @type {number}
        151 * @private
        152 */
        153goog.testing.MockClock.prototype.timeoutDelay_ = 0;
        154
        155
        156/**
        157 * The real set timeout for reference.
        158 * @const @private {!Function}
        159 */
        160goog.testing.MockClock.REAL_SETTIMEOUT_ = goog.global.setTimeout;
        161
        162
        163/**
        164 * Installs the MockClock by overriding the global object's implementation of
        165 * setTimeout, setInterval, clearTimeout and clearInterval.
        166 */
        167goog.testing.MockClock.prototype.install = function() {
        168 if (!this.replacer_) {
        169 if (goog.testing.MockClock.REAL_SETTIMEOUT_ !== goog.global.setTimeout) {
        170 if (typeof console !== 'undefined' && console.warn) {
        171 console.warn('Non default setTimeout detected. ' +
        172 'Use of multiple MockClock instances or other clock mocking ' +
        173 'should be avoided due to unspecified behavior and ' +
        174 'the resulting fragility.');
        175 }
        176 }
        177
        178 var r = this.replacer_ = new goog.testing.PropertyReplacer();
        179 r.set(goog.global, 'setTimeout', goog.bind(this.setTimeout_, this));
        180 r.set(goog.global, 'setInterval', goog.bind(this.setInterval_, this));
        181 r.set(goog.global, 'setImmediate', goog.bind(this.setImmediate_, this));
        182 r.set(goog.global, 'clearTimeout', goog.bind(this.clearTimeout_, this));
        183 r.set(goog.global, 'clearInterval', goog.bind(this.clearInterval_, this));
        184 // goog.Promise uses goog.async.run. In order to be able to test
        185 // Promise-based code, we need to make sure that goog.async.run uses
        186 // nextTick instead of native browser Promises. This means that it will
        187 // default to setImmediate, which is replaced above. Note that we test for
        188 // the presence of goog.async.run.forceNextTick to be resilient to the case
        189 // where tests replace goog.async.run directly.
        190 goog.async.run.forceNextTick && goog.async.run.forceNextTick(
        191 goog.testing.MockClock.REAL_SETTIMEOUT_);
        192
        193 // Replace the requestAnimationFrame functions.
        194 this.replaceRequestAnimationFrame_();
        195
        196 // PropertyReplacer#set can't be called with renameable functions.
        197 this.oldGoogNow_ = goog.now;
        198 goog.now = goog.bind(this.getCurrentTime, this);
        199 }
        200};
        201
        202
        203/**
        204 * Installs the mocks for requestAnimationFrame and cancelRequestAnimationFrame.
        205 * @private
        206 */
        207goog.testing.MockClock.prototype.replaceRequestAnimationFrame_ = function() {
        208 var r = this.replacer_;
        209 var requestFuncs = ['requestAnimationFrame',
        210 'webkitRequestAnimationFrame',
        211 'mozRequestAnimationFrame',
        212 'oRequestAnimationFrame',
        213 'msRequestAnimationFrame'];
        214
        215 var cancelFuncs = ['cancelAnimationFrame',
        216 'cancelRequestAnimationFrame',
        217 'webkitCancelRequestAnimationFrame',
        218 'mozCancelRequestAnimationFrame',
        219 'oCancelRequestAnimationFrame',
        220 'msCancelRequestAnimationFrame'];
        221
        222 for (var i = 0; i < requestFuncs.length; ++i) {
        223 if (goog.global && goog.global[requestFuncs[i]]) {
        224 r.set(goog.global, requestFuncs[i],
        225 goog.bind(this.requestAnimationFrame_, this));
        226 }
        227 }
        228
        229 for (var i = 0; i < cancelFuncs.length; ++i) {
        230 if (goog.global && goog.global[cancelFuncs[i]]) {
        231 r.set(goog.global, cancelFuncs[i],
        232 goog.bind(this.cancelRequestAnimationFrame_, this));
        233 }
        234 }
        235};
        236
        237
        238/**
        239 * Removes the MockClock's hooks into the global object's functions and revert
        240 * to their original values.
        241 */
        242goog.testing.MockClock.prototype.uninstall = function() {
        243 if (this.replacer_) {
        244 this.replacer_.reset();
        245 this.replacer_ = null;
        246 goog.now = this.oldGoogNow_;
        247 }
        248
        249 this.fireResetEvent();
        250};
        251
        252
        253/** @override */
        254goog.testing.MockClock.prototype.disposeInternal = function() {
        255 this.uninstall();
        256 this.queue_ = null;
        257 this.deletedKeys_ = null;
        258 goog.testing.MockClock.superClass_.disposeInternal.call(this);
        259};
        260
        261
        262/**
        263 * Resets the MockClock, removing all timeouts that are scheduled and resets
        264 * the fake timer count.
        265 */
        266goog.testing.MockClock.prototype.reset = function() {
        267 this.queue_ = [];
        268 this.deletedKeys_ = {};
        269 this.nowMillis_ = 0;
        270 this.timeoutsMade_ = 0;
        271 this.timeoutDelay_ = 0;
        272
        273 this.fireResetEvent();
        274};
        275
        276
        277/**
        278 * Signals that the mock clock has been reset, allowing objects that
        279 * maintain their own internal state to reset.
        280 */
        281goog.testing.MockClock.prototype.fireResetEvent = function() {
        282 goog.testing.watchers.signalClockReset();
        283};
        284
        285
        286/**
        287 * Sets the amount of time between when a timeout is scheduled to fire and when
        288 * it actually fires.
        289 * @param {number} delay The delay in milliseconds. May be negative.
        290 */
        291goog.testing.MockClock.prototype.setTimeoutDelay = function(delay) {
        292 this.timeoutDelay_ = delay;
        293};
        294
        295
        296/**
        297 * @return {number} delay The amount of time between when a timeout is
        298 * scheduled to fire and when it actually fires, in milliseconds. May
        299 * be negative.
        300 */
        301goog.testing.MockClock.prototype.getTimeoutDelay = function() {
        302 return this.timeoutDelay_;
        303};
        304
        305
        306/**
        307 * Increments the MockClock's time by a given number of milliseconds, running
        308 * any functions that are now overdue.
        309 * @param {number=} opt_millis Number of milliseconds to increment the counter.
        310 * If not specified, clock ticks 1 millisecond.
        311 * @return {number} Current mock time in milliseconds.
        312 */
        313goog.testing.MockClock.prototype.tick = function(opt_millis) {
        314 if (typeof opt_millis != 'number') {
        315 opt_millis = 1;
        316 }
        317 var endTime = this.nowMillis_ + opt_millis;
        318 this.runFunctionsWithinRange_(endTime);
        319 this.nowMillis_ = endTime;
        320 return endTime;
        321};
        322
        323
        324/**
        325 * Takes a promise and then ticks the mock clock. If the promise successfully
        326 * resolves, returns the value produced by the promise. If the promise is
        327 * rejected, it throws the rejection as an exception. If the promise is not
        328 * resolved at all, throws an exception.
        329 * Also ticks the general clock by the specified amount.
        330 *
        331 * @param {!goog.Thenable<T>} promise A promise that should be resolved after
        332 * the mockClock is ticked for the given opt_millis.
        333 * @param {number=} opt_millis Number of milliseconds to increment the counter.
        334 * If not specified, clock ticks 1 millisecond.
        335 * @return {T}
        336 * @template T
        337 */
        338goog.testing.MockClock.prototype.tickPromise = function(promise, opt_millis) {
        339 var value;
        340 var error;
        341 var resolved = false;
        342 promise.then(function(v) {
        343 value = v;
        344 resolved = true;
        345 }, function(e) {
        346 error = e;
        347 resolved = true;
        348 });
        349 this.tick(opt_millis);
        350 if (!resolved) {
        351 throw new Error(
        352 'Promise was expected to be resolved after mock clock tick.');
        353 }
        354 if (error) {
        355 throw error;
        356 }
        357 return value;
        358};
        359
        360
        361/**
        362 * @return {number} The number of timeouts that have been scheduled.
        363 */
        364goog.testing.MockClock.prototype.getTimeoutsMade = function() {
        365 return this.timeoutsMade_;
        366};
        367
        368
        369/**
        370 * @return {number} The MockClock's current time in milliseconds.
        371 */
        372goog.testing.MockClock.prototype.getCurrentTime = function() {
        373 return this.nowMillis_;
        374};
        375
        376
        377/**
        378 * @param {number} timeoutKey The timeout key.
        379 * @return {boolean} Whether the timer has been set and not cleared,
        380 * independent of the timeout's expiration. In other words, the timeout
        381 * could have passed or could be scheduled for the future. Either way,
        382 * this function returns true or false depending only on whether the
        383 * provided timeoutKey represents a timeout that has been set and not
        384 * cleared.
        385 */
        386goog.testing.MockClock.prototype.isTimeoutSet = function(timeoutKey) {
        387 return timeoutKey < goog.testing.MockClock.nextId &&
        388 timeoutKey >= goog.testing.MockClock.nextId - this.timeoutsMade_ &&
        389 !this.deletedKeys_[timeoutKey];
        390};
        391
        392
        393/**
        394 * Runs any function that is scheduled before a certain time. Timeouts can
        395 * be made to fire early or late if timeoutDelay_ is non-0.
        396 * @param {number} endTime The latest time in the range, in milliseconds.
        397 * @private
        398 */
        399goog.testing.MockClock.prototype.runFunctionsWithinRange_ = function(
        400 endTime) {
        401 var adjustedEndTime = endTime - this.timeoutDelay_;
        402
        403 // Repeatedly pop off the last item since the queue is always sorted.
        404 while (this.queue_ && this.queue_.length &&
        405 this.queue_[this.queue_.length - 1].runAtMillis <= adjustedEndTime) {
        406 var timeout = this.queue_.pop();
        407
        408 if (!(timeout.timeoutKey in this.deletedKeys_)) {
        409 // Only move time forwards.
        410 this.nowMillis_ = Math.max(this.nowMillis_,
        411 timeout.runAtMillis + this.timeoutDelay_);
        412 // Call timeout in global scope and pass the timeout key as the argument.
        413 timeout.funcToCall.call(goog.global, timeout.timeoutKey);
        414 // In case the interval was cleared in the funcToCall
        415 if (timeout.recurring) {
        416 this.scheduleFunction_(
        417 timeout.timeoutKey, timeout.funcToCall, timeout.millis, true);
        418 }
        419 }
        420 }
        421};
        422
        423
        424/**
        425 * Schedules a function to be run at a certain time.
        426 * @param {number} timeoutKey The timeout key.
        427 * @param {Function} funcToCall The function to call.
        428 * @param {number} millis The number of milliseconds to call it in.
        429 * @param {boolean} recurring Whether to function call should recur.
        430 * @private
        431 */
        432goog.testing.MockClock.prototype.scheduleFunction_ = function(
        433 timeoutKey, funcToCall, millis, recurring) {
        434 if (!goog.isFunction(funcToCall)) {
        435 // Early error for debuggability rather than dying in the next .tick()
        436 throw new TypeError('The provided callback must be a function, not a ' +
        437 typeof funcToCall);
        438 }
        439
        440 var timeout = {
        441 runAtMillis: this.nowMillis_ + millis,
        442 funcToCall: funcToCall,
        443 recurring: recurring,
        444 timeoutKey: timeoutKey,
        445 millis: millis
        446 };
        447
        448 goog.testing.MockClock.insert_(timeout, this.queue_);
        449};
        450
        451
        452/**
        453 * Inserts a timer descriptor into a descending-order queue.
        454 *
        455 * Later-inserted duplicates appear at lower indices. For example, the
        456 * asterisk in (5,4,*,3,2,1) would be the insertion point for 3.
        457 *
        458 * @param {Object} timeout The timeout to insert, with numerical runAtMillis
        459 * property.
        460 * @param {Array<Object>} queue The queue to insert into, with each element
        461 * having a numerical runAtMillis property.
        462 * @private
        463 */
        464goog.testing.MockClock.insert_ = function(timeout, queue) {
        465 // Although insertion of N items is quadratic, requiring goog.structs.Heap
        466 // from a unit test will make tests more prone to breakage. Since unit
        467 // tests are normally small, scalability is not a primary issue.
        468
        469 // Find an insertion point. Since the queue is in reverse order (so we
        470 // can pop rather than unshift), and later timers with the same time stamp
        471 // should be executed later, we look for the element strictly greater than
        472 // the one we are inserting.
        473
        474 for (var i = queue.length; i != 0; i--) {
        475 if (queue[i - 1].runAtMillis > timeout.runAtMillis) {
        476 break;
        477 }
        478 queue[i] = queue[i - 1];
        479 }
        480
        481 queue[i] = timeout;
        482};
        483
        484
        485/**
        486 * Maximum 32-bit signed integer.
        487 *
        488 * Timeouts over this time return immediately in many browsers, due to integer
        489 * overflow. Such known browsers include Firefox, Chrome, and Safari, but not
        490 * IE.
        491 *
        492 * @type {number}
        493 * @private
        494 */
        495goog.testing.MockClock.MAX_INT_ = 2147483647;
        496
        497
        498/**
        499 * Schedules a function to be called after {@code millis} milliseconds.
        500 * Mock implementation for setTimeout.
        501 * @param {Function} funcToCall The function to call.
        502 * @param {number=} opt_millis The number of milliseconds to call it after.
        503 * @return {number} The number of timeouts created.
        504 * @private
        505 */
        506goog.testing.MockClock.prototype.setTimeout_ = function(
        507 funcToCall, opt_millis) {
        508 var millis = opt_millis || 0;
        509 if (millis > goog.testing.MockClock.MAX_INT_) {
        510 throw Error(
        511 'Bad timeout value: ' + millis + '. Timeouts over MAX_INT ' +
        512 '(24.8 days) cause timeouts to be fired ' +
        513 'immediately in most browsers, except for IE.');
        514 }
        515 this.timeoutsMade_++;
        516 this.scheduleFunction_(goog.testing.MockClock.nextId, funcToCall, millis,
        517 false);
        518 return goog.testing.MockClock.nextId++;
        519};
        520
        521
        522/**
        523 * Schedules a function to be called every {@code millis} milliseconds.
        524 * Mock implementation for setInterval.
        525 * @param {Function} funcToCall The function to call.
        526 * @param {number=} opt_millis The number of milliseconds between calls.
        527 * @return {number} The number of timeouts created.
        528 * @private
        529 */
        530goog.testing.MockClock.prototype.setInterval_ =
        531 function(funcToCall, opt_millis) {
        532 var millis = opt_millis || 0;
        533 this.timeoutsMade_++;
        534 this.scheduleFunction_(goog.testing.MockClock.nextId, funcToCall, millis,
        535 true);
        536 return goog.testing.MockClock.nextId++;
        537};
        538
        539
        540/**
        541 * Schedules a function to be called when an animation frame is triggered.
        542 * Mock implementation for requestAnimationFrame.
        543 * @param {Function} funcToCall The function to call.
        544 * @return {number} The number of timeouts created.
        545 * @private
        546 */
        547goog.testing.MockClock.prototype.requestAnimationFrame_ = function(funcToCall) {
        548 return this.setTimeout_(goog.bind(function() {
        549 if (funcToCall) {
        550 funcToCall(this.getCurrentTime());
        551 } else if (goog.global.mozRequestAnimationFrame) {
        552 var event = new goog.testing.events.Event('MozBeforePaint', goog.global);
        553 event['timeStamp'] = this.getCurrentTime();
        554 goog.testing.events.fireBrowserEvent(event);
        555 }
        556 }, this), goog.testing.MockClock.REQUEST_ANIMATION_FRAME_TIMEOUT);
        557};
        558
        559
        560/**
        561 * Schedules a function to be called immediately after the current JS
        562 * execution.
        563 * Mock implementation for setImmediate.
        564 * @param {Function} funcToCall The function to call.
        565 * @return {number} The number of timeouts created.
        566 * @private
        567 */
        568goog.testing.MockClock.prototype.setImmediate_ = function(funcToCall) {
        569 return this.setTimeout_(funcToCall, 0);
        570};
        571
        572
        573/**
        574 * Clears a timeout.
        575 * Mock implementation for clearTimeout.
        576 * @param {number} timeoutKey The timeout key to clear.
        577 * @private
        578 */
        579goog.testing.MockClock.prototype.clearTimeout_ = function(timeoutKey) {
        580 // Some common libraries register static state with timers.
        581 // This is bad. It leads to all sorts of crazy test problems where
        582 // 1) Test A sets up a new mock clock and a static timer.
        583 // 2) Test B sets up a new mock clock, but re-uses the static timer
        584 // from Test A.
        585 // 3) A timeout key from test A gets cleared, breaking a timeout in
        586 // Test B.
        587 //
        588 // For now, we just hackily fail silently if someone tries to clear a timeout
        589 // key before we've allocated it.
        590 // Ideally, we should throw an exception if we see this happening.
        591 if (this.isTimeoutSet(timeoutKey)) {
        592 this.deletedKeys_[timeoutKey] = true;
        593 }
        594};
        595
        596
        597/**
        598 * Clears an interval.
        599 * Mock implementation for clearInterval.
        600 * @param {number} timeoutKey The interval key to clear.
        601 * @private
        602 */
        603goog.testing.MockClock.prototype.clearInterval_ = function(timeoutKey) {
        604 this.clearTimeout_(timeoutKey);
        605};
        606
        607
        608/**
        609 * Clears a requestAnimationFrame.
        610 * Mock implementation for cancelRequestAnimationFrame.
        611 * @param {number} timeoutKey The requestAnimationFrame key to clear.
        612 * @private
        613 */
        614goog.testing.MockClock.prototype.cancelRequestAnimationFrame_ =
        615 function(timeoutKey) {
        616 this.clearTimeout_(timeoutKey);
        617};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/mockcontrol.js.src.html b/docs/source/lib/goog/testing/mockcontrol.js.src.html new file mode 100644 index 0000000..f596062 --- /dev/null +++ b/docs/source/lib/goog/testing/mockcontrol.js.src.html @@ -0,0 +1 @@ +mockcontrol.js

        lib/goog/testing/mockcontrol.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A MockControl holds a set of mocks for a particular test.
        17 * It consolidates calls to $replay, $verify, and $tearDown, which simplifies
        18 * the test and helps avoid omissions.
        19 *
        20 * You can create and control a mock:
        21 * var mockFoo = mockControl.addMock(new MyMock(Foo));
        22 *
        23 * MockControl also exposes some convenience functions that create
        24 * controlled mocks for common mocks: StrictMock, LooseMock,
        25 * FunctionMock, MethodMock, and GlobalFunctionMock.
        26 *
        27 */
        28
        29
        30goog.provide('goog.testing.MockControl');
        31
        32goog.require('goog.array');
        33goog.require('goog.testing');
        34goog.require('goog.testing.LooseMock');
        35goog.require('goog.testing.StrictMock');
        36
        37
        38
        39/**
        40 * Controls a set of mocks. Controlled mocks are replayed, verified, and
        41 * cleaned-up at the same time.
        42 * @constructor
        43 */
        44goog.testing.MockControl = function() {
        45 /**
        46 * The list of mocks being controlled.
        47 * @type {Array<goog.testing.MockInterface>}
        48 * @private
        49 */
        50 this.mocks_ = [];
        51};
        52
        53
        54/**
        55 * Takes control of this mock.
        56 * @param {goog.testing.MockInterface} mock Mock to be controlled.
        57 * @return {goog.testing.MockInterface} The same mock passed in,
        58 * for convenience.
        59 */
        60goog.testing.MockControl.prototype.addMock = function(mock) {
        61 this.mocks_.push(mock);
        62 return mock;
        63};
        64
        65
        66/**
        67 * Calls replay on each controlled mock.
        68 */
        69goog.testing.MockControl.prototype.$replayAll = function() {
        70 goog.array.forEach(this.mocks_, function(m) {
        71 m.$replay();
        72 });
        73};
        74
        75
        76/**
        77 * Calls reset on each controlled mock.
        78 */
        79goog.testing.MockControl.prototype.$resetAll = function() {
        80 goog.array.forEach(this.mocks_, function(m) {
        81 m.$reset();
        82 });
        83};
        84
        85
        86/**
        87 * Calls verify on each controlled mock.
        88 */
        89goog.testing.MockControl.prototype.$verifyAll = function() {
        90 goog.array.forEach(this.mocks_, function(m) {
        91 m.$verify();
        92 });
        93};
        94
        95
        96/**
        97 * Calls tearDown on each controlled mock, if necesssary.
        98 */
        99goog.testing.MockControl.prototype.$tearDown = function() {
        100 goog.array.forEach(this.mocks_, function(m) {
        101 // $tearDown if defined.
        102 if (m.$tearDown) {
        103 m.$tearDown();
        104 }
        105 // TODO(user): Somehow determine if verifyAll should have been called
        106 // but was not.
        107 });
        108};
        109
        110
        111/**
        112 * Creates a controlled StrictMock. Passes its arguments through to the
        113 * StrictMock constructor.
        114 * @param {Object|Function} objectToMock The object that should be mocked, or
        115 * the constructor of an object to mock.
        116 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
        117 * a mock should be constructed from the static functions of a class.
        118 * @param {boolean=} opt_createProxy An optional argument denoting that
        119 * a proxy for the target mock should be created.
        120 * @return {!goog.testing.StrictMock} The mock object.
        121 */
        122goog.testing.MockControl.prototype.createStrictMock = function(
        123 objectToMock, opt_mockStaticMethods, opt_createProxy) {
        124 var m = new goog.testing.StrictMock(objectToMock, opt_mockStaticMethods,
        125 opt_createProxy);
        126 this.addMock(m);
        127 return m;
        128};
        129
        130
        131/**
        132 * Creates a controlled LooseMock. Passes its arguments through to the
        133 * LooseMock constructor.
        134 * @param {Object|Function} objectToMock The object that should be mocked, or
        135 * the constructor of an object to mock.
        136 * @param {boolean=} opt_ignoreUnexpectedCalls Whether to ignore unexpected
        137 * calls.
        138 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
        139 * a mock should be constructed from the static functions of a class.
        140 * @param {boolean=} opt_createProxy An optional argument denoting that
        141 * a proxy for the target mock should be created.
        142 * @return {!goog.testing.LooseMock} The mock object.
        143 */
        144goog.testing.MockControl.prototype.createLooseMock = function(
        145 objectToMock, opt_ignoreUnexpectedCalls,
        146 opt_mockStaticMethods, opt_createProxy) {
        147 var m = new goog.testing.LooseMock(objectToMock, opt_ignoreUnexpectedCalls,
        148 opt_mockStaticMethods, opt_createProxy);
        149 this.addMock(m);
        150 return m;
        151};
        152
        153
        154/**
        155 * Creates a controlled FunctionMock. Passes its arguments through to the
        156 * FunctionMock constructor.
        157 * @param {string=} opt_functionName The optional name of the function to mock
        158 * set to '[anonymous mocked function]' if not passed in.
        159 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        160 * goog.testing.Mock.STRICT. The default is STRICT.
        161 * @return {goog.testing.MockInterface} The mocked function.
        162 */
        163goog.testing.MockControl.prototype.createFunctionMock = function(
        164 opt_functionName, opt_strictness) {
        165 var m = goog.testing.createFunctionMock(opt_functionName, opt_strictness);
        166 this.addMock(m);
        167 return m;
        168};
        169
        170
        171/**
        172 * Creates a controlled MethodMock. Passes its arguments through to the
        173 * MethodMock constructor.
        174 * @param {Object} scope The scope of the method to be mocked out.
        175 * @param {string} functionName The name of the function we're going to mock.
        176 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        177 * goog.testing.Mock.STRICT. The default is STRICT.
        178 * @return {!goog.testing.MockInterface} The mocked method.
        179 */
        180goog.testing.MockControl.prototype.createMethodMock = function(
        181 scope, functionName, opt_strictness) {
        182 var m = goog.testing.createMethodMock(scope, functionName, opt_strictness);
        183 this.addMock(m);
        184 return m;
        185};
        186
        187
        188/**
        189 * Creates a controlled MethodMock for a constructor. Passes its arguments
        190 * through to the MethodMock constructor. See
        191 * {@link goog.testing.createConstructorMock} for details.
        192 * @param {Object} scope The scope of the constructor to be mocked out.
        193 * @param {string} constructorName The name of the function we're going to mock.
        194 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        195 * goog.testing.Mock.STRICT. The default is STRICT.
        196 * @return {!goog.testing.MockInterface} The mocked method.
        197 */
        198goog.testing.MockControl.prototype.createConstructorMock = function(
        199 scope, constructorName, opt_strictness) {
        200 var m = goog.testing.createConstructorMock(scope, constructorName,
        201 opt_strictness);
        202 this.addMock(m);
        203 return m;
        204};
        205
        206
        207/**
        208 * Creates a controlled GlobalFunctionMock. Passes its arguments through to the
        209 * GlobalFunctionMock constructor.
        210 * @param {string} functionName The name of the function we're going to mock.
        211 * @param {number=} opt_strictness One of goog.testing.Mock.LOOSE or
        212 * goog.testing.Mock.STRICT. The default is STRICT.
        213 * @return {!goog.testing.MockInterface} The mocked function.
        214 */
        215goog.testing.MockControl.prototype.createGlobalFunctionMock = function(
        216 functionName, opt_strictness) {
        217 var m = goog.testing.createGlobalFunctionMock(functionName, opt_strictness);
        218 this.addMock(m);
        219 return m;
        220};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/mockinterface.js.src.html b/docs/source/lib/goog/testing/mockinterface.js.src.html new file mode 100644 index 0000000..b644f39 --- /dev/null +++ b/docs/source/lib/goog/testing/mockinterface.js.src.html @@ -0,0 +1 @@ +mockinterface.js

        lib/goog/testing/mockinterface.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview An interface that all mocks should share.
        17 * @author nicksantos@google.com (Nick Santos)
        18 */
        19
        20goog.provide('goog.testing.MockInterface');
        21
        22
        23
        24/** @interface */
        25goog.testing.MockInterface = function() {};
        26
        27
        28/**
        29 * Write down all the expected functions that have been called on the
        30 * mock so far. From here on out, future function calls will be
        31 * compared against this list.
        32 */
        33goog.testing.MockInterface.prototype.$replay = function() {};
        34
        35
        36/**
        37 * Reset the mock.
        38 */
        39goog.testing.MockInterface.prototype.$reset = function() {};
        40
        41
        42/**
        43 * Assert that the expected function calls match the actual calls.
        44 */
        45goog.testing.MockInterface.prototype.$verify = function() {};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/mockmatchers.js.src.html b/docs/source/lib/goog/testing/mockmatchers.js.src.html new file mode 100644 index 0000000..fda5916 --- /dev/null +++ b/docs/source/lib/goog/testing/mockmatchers.js.src.html @@ -0,0 +1 @@ +mockmatchers.js

        lib/goog/testing/mockmatchers.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Matchers to be used with the mock utilities. They allow for
        17 * flexible matching by type. Custom matchers can be created by passing a
        18 * matcher function into an ArgumentMatcher instance.
        19 *
        20 * For examples, please see the unit test.
        21 *
        22 */
        23
        24
        25goog.provide('goog.testing.mockmatchers');
        26goog.provide('goog.testing.mockmatchers.ArgumentMatcher');
        27goog.provide('goog.testing.mockmatchers.IgnoreArgument');
        28goog.provide('goog.testing.mockmatchers.InstanceOf');
        29goog.provide('goog.testing.mockmatchers.ObjectEquals');
        30goog.provide('goog.testing.mockmatchers.RegexpMatch');
        31goog.provide('goog.testing.mockmatchers.SaveArgument');
        32goog.provide('goog.testing.mockmatchers.TypeOf');
        33
        34goog.require('goog.array');
        35goog.require('goog.dom');
        36goog.require('goog.testing.asserts');
        37
        38
        39
        40/**
        41 * A simple interface for executing argument matching. A match in this case is
        42 * testing to see if a supplied object fits a given criteria. True is returned
        43 * if the given criteria is met.
        44 * @param {Function=} opt_matchFn A function that evaluates a given argument
        45 * and returns true if it meets a given criteria.
        46 * @param {?string=} opt_matchName The name expressing intent as part of
        47 * an error message for when a match fails.
        48 * @constructor
        49 */
        50goog.testing.mockmatchers.ArgumentMatcher =
        51 function(opt_matchFn, opt_matchName) {
        52 /**
        53 * A function that evaluates a given argument and returns true if it meets a
        54 * given criteria.
        55 * @type {Function}
        56 * @private
        57 */
        58 this.matchFn_ = opt_matchFn || null;
        59
        60 /**
        61 * A string indicating the match intent (e.g. isBoolean or isString).
        62 * @type {?string}
        63 * @private
        64 */
        65 this.matchName_ = opt_matchName || null;
        66};
        67
        68
        69/**
        70 * A function that takes a match argument and an optional MockExpectation
        71 * which (if provided) will get error information and returns whether or
        72 * not it matches.
        73 * @param {*} toVerify The argument that should be verified.
        74 * @param {goog.testing.MockExpectation?=} opt_expectation The expectation
        75 * for this match.
        76 * @return {boolean} Whether or not a given argument passes verification.
        77 */
        78goog.testing.mockmatchers.ArgumentMatcher.prototype.matches =
        79 function(toVerify, opt_expectation) {
        80 if (this.matchFn_) {
        81 var isamatch = this.matchFn_(toVerify);
        82 if (!isamatch && opt_expectation) {
        83 if (this.matchName_) {
        84 opt_expectation.addErrorMessage('Expected: ' +
        85 this.matchName_ + ' but was: ' + _displayStringForValue(toVerify));
        86 } else {
        87 opt_expectation.addErrorMessage('Expected: missing mockmatcher' +
        88 ' description but was: ' +
        89 _displayStringForValue(toVerify));
        90 }
        91 }
        92 return isamatch;
        93 } else {
        94 throw Error('No match function defined for this mock matcher');
        95 }
        96};
        97
        98
        99
        100/**
        101 * A matcher that verifies that an argument is an instance of a given class.
        102 * @param {Function} ctor The class that will be used for verification.
        103 * @constructor
        104 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        105 * @final
        106 */
        107goog.testing.mockmatchers.InstanceOf = function(ctor) {
        108 goog.testing.mockmatchers.ArgumentMatcher.call(this,
        109 function(obj) {
        110 return obj instanceof ctor;
        111 // NOTE: Browser differences on ctor.toString() output
        112 // make using that here problematic. So for now, just let
        113 // people know the instanceOf() failed without providing
        114 // browser specific details...
        115 }, 'instanceOf()');
        116};
        117goog.inherits(goog.testing.mockmatchers.InstanceOf,
        118 goog.testing.mockmatchers.ArgumentMatcher);
        119
        120
        121
        122/**
        123 * A matcher that verifies that an argument is of a given type (e.g. "object").
        124 * @param {string} type The type that a given argument must have.
        125 * @constructor
        126 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        127 * @final
        128 */
        129goog.testing.mockmatchers.TypeOf = function(type) {
        130 goog.testing.mockmatchers.ArgumentMatcher.call(this,
        131 function(obj) {
        132 return goog.typeOf(obj) == type;
        133 }, 'typeOf(' + type + ')');
        134};
        135goog.inherits(goog.testing.mockmatchers.TypeOf,
        136 goog.testing.mockmatchers.ArgumentMatcher);
        137
        138
        139
        140/**
        141 * A matcher that verifies that an argument matches a given RegExp.
        142 * @param {RegExp} regexp The regular expression that the argument must match.
        143 * @constructor
        144 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        145 * @final
        146 */
        147goog.testing.mockmatchers.RegexpMatch = function(regexp) {
        148 goog.testing.mockmatchers.ArgumentMatcher.call(this,
        149 function(str) {
        150 return regexp.test(str);
        151 }, 'match(' + regexp + ')');
        152};
        153goog.inherits(goog.testing.mockmatchers.RegexpMatch,
        154 goog.testing.mockmatchers.ArgumentMatcher);
        155
        156
        157
        158/**
        159 * A matcher that always returns true. It is useful when the user does not care
        160 * for some arguments.
        161 * For example: mockFunction('username', 'password', IgnoreArgument);
        162 * @constructor
        163 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        164 * @final
        165 */
        166goog.testing.mockmatchers.IgnoreArgument = function() {
        167 goog.testing.mockmatchers.ArgumentMatcher.call(this,
        168 function() {
        169 return true;
        170 }, 'true');
        171};
        172goog.inherits(goog.testing.mockmatchers.IgnoreArgument,
        173 goog.testing.mockmatchers.ArgumentMatcher);
        174
        175
        176
        177/**
        178 * A matcher that verifies that the argument is an object that equals the given
        179 * expected object, using a deep comparison.
        180 * @param {Object} expectedObject An object to match against when
        181 * verifying the argument.
        182 * @constructor
        183 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        184 */
        185goog.testing.mockmatchers.ObjectEquals = function(expectedObject) {
        186 goog.testing.mockmatchers.ArgumentMatcher.call(this,
        187 function(matchObject) {
        188 assertObjectEquals('Expected equal objects', expectedObject,
        189 matchObject);
        190 return true;
        191 }, 'objectEquals(' + expectedObject + ')');
        192};
        193goog.inherits(goog.testing.mockmatchers.ObjectEquals,
        194 goog.testing.mockmatchers.ArgumentMatcher);
        195
        196
        197/** @override */
        198goog.testing.mockmatchers.ObjectEquals.prototype.matches =
        199 function(toVerify, opt_expectation) {
        200 // Override the default matches implementation to capture the exception thrown
        201 // by assertObjectEquals (if any) and add that message to the expectation.
        202 try {
        203 return goog.testing.mockmatchers.ObjectEquals.superClass_.matches.call(
        204 this, toVerify, opt_expectation);
        205 } catch (e) {
        206 if (opt_expectation) {
        207 opt_expectation.addErrorMessage(e.message);
        208 }
        209 return false;
        210 }
        211};
        212
        213
        214
        215/**
        216 * A matcher that saves the argument that it is verifying so that your unit test
        217 * can perform extra tests with this argument later. For example, if the
        218 * argument is a callback method, the unit test can then later call this
        219 * callback to test the asynchronous portion of the call.
        220 * @param {goog.testing.mockmatchers.ArgumentMatcher|Function=} opt_matcher
        221 * Argument matcher or matching function that will be used to validate the
        222 * argument. By default, argument will always be valid.
        223 * @param {?string=} opt_matchName The name expressing intent as part of
        224 * an error message for when a match fails.
        225 * @constructor
        226 * @extends {goog.testing.mockmatchers.ArgumentMatcher}
        227 * @final
        228 */
        229goog.testing.mockmatchers.SaveArgument = function(opt_matcher, opt_matchName) {
        230 goog.testing.mockmatchers.ArgumentMatcher.call(
        231 this, /** @type {Function} */ (opt_matcher), opt_matchName);
        232
        233 if (opt_matcher instanceof goog.testing.mockmatchers.ArgumentMatcher) {
        234 /**
        235 * Delegate match requests to this matcher.
        236 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        237 * @private
        238 */
        239 this.delegateMatcher_ = opt_matcher;
        240 } else if (!opt_matcher) {
        241 this.delegateMatcher_ = goog.testing.mockmatchers.ignoreArgument;
        242 }
        243};
        244goog.inherits(goog.testing.mockmatchers.SaveArgument,
        245 goog.testing.mockmatchers.ArgumentMatcher);
        246
        247
        248/** @override */
        249goog.testing.mockmatchers.SaveArgument.prototype.matches = function(
        250 toVerify, opt_expectation) {
        251 this.arg = toVerify;
        252 if (this.delegateMatcher_) {
        253 return this.delegateMatcher_.matches(toVerify, opt_expectation);
        254 }
        255 return goog.testing.mockmatchers.SaveArgument.superClass_.matches.call(
        256 this, toVerify, opt_expectation);
        257};
        258
        259
        260/**
        261 * Saved argument that was verified.
        262 * @type {*}
        263 */
        264goog.testing.mockmatchers.SaveArgument.prototype.arg;
        265
        266
        267/**
        268 * An instance of the IgnoreArgument matcher. Returns true for all matches.
        269 * @type {goog.testing.mockmatchers.IgnoreArgument}
        270 */
        271goog.testing.mockmatchers.ignoreArgument =
        272 new goog.testing.mockmatchers.IgnoreArgument();
        273
        274
        275/**
        276 * A matcher that verifies that an argument is an array.
        277 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        278 */
        279goog.testing.mockmatchers.isArray =
        280 new goog.testing.mockmatchers.ArgumentMatcher(goog.isArray,
        281 'isArray');
        282
        283
        284/**
        285 * A matcher that verifies that an argument is a array-like. A NodeList is an
        286 * example of a collection that is very close to an array.
        287 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        288 */
        289goog.testing.mockmatchers.isArrayLike =
        290 new goog.testing.mockmatchers.ArgumentMatcher(goog.isArrayLike,
        291 'isArrayLike');
        292
        293
        294/**
        295 * A matcher that verifies that an argument is a date-like.
        296 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        297 */
        298goog.testing.mockmatchers.isDateLike =
        299 new goog.testing.mockmatchers.ArgumentMatcher(goog.isDateLike,
        300 'isDateLike');
        301
        302
        303/**
        304 * A matcher that verifies that an argument is a string.
        305 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        306 */
        307goog.testing.mockmatchers.isString =
        308 new goog.testing.mockmatchers.ArgumentMatcher(goog.isString,
        309 'isString');
        310
        311
        312/**
        313 * A matcher that verifies that an argument is a boolean.
        314 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        315 */
        316goog.testing.mockmatchers.isBoolean =
        317 new goog.testing.mockmatchers.ArgumentMatcher(goog.isBoolean,
        318 'isBoolean');
        319
        320
        321/**
        322 * A matcher that verifies that an argument is a number.
        323 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        324 */
        325goog.testing.mockmatchers.isNumber =
        326 new goog.testing.mockmatchers.ArgumentMatcher(goog.isNumber,
        327 'isNumber');
        328
        329
        330/**
        331 * A matcher that verifies that an argument is a function.
        332 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        333 */
        334goog.testing.mockmatchers.isFunction =
        335 new goog.testing.mockmatchers.ArgumentMatcher(goog.isFunction,
        336 'isFunction');
        337
        338
        339/**
        340 * A matcher that verifies that an argument is an object.
        341 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        342 */
        343goog.testing.mockmatchers.isObject =
        344 new goog.testing.mockmatchers.ArgumentMatcher(goog.isObject,
        345 'isObject');
        346
        347
        348/**
        349 * A matcher that verifies that an argument is like a DOM node.
        350 * @type {goog.testing.mockmatchers.ArgumentMatcher}
        351 */
        352goog.testing.mockmatchers.isNodeLike =
        353 new goog.testing.mockmatchers.ArgumentMatcher(goog.dom.isNodeLike,
        354 'isNodeLike');
        355
        356
        357/**
        358 * A function that checks to see if an array matches a given set of
        359 * expectations. The expectations array can be a mix of ArgumentMatcher
        360 * implementations and values. True will be returned if values are identical or
        361 * if a matcher returns a positive result.
        362 * @param {Array<?>} expectedArr An array of expectations which can be either
        363 * values to check for equality or ArgumentMatchers.
        364 * @param {Array<?>} arr The array to match.
        365 * @param {goog.testing.MockExpectation?=} opt_expectation The expectation
        366 * for this match.
        367 * @return {boolean} Whether or not the given array matches the expectations.
        368 */
        369goog.testing.mockmatchers.flexibleArrayMatcher =
        370 function(expectedArr, arr, opt_expectation) {
        371 return goog.array.equals(expectedArr, arr, function(a, b) {
        372 var errCount = 0;
        373 if (opt_expectation) {
        374 errCount = opt_expectation.getErrorMessageCount();
        375 }
        376 var isamatch = a === b ||
        377 a instanceof goog.testing.mockmatchers.ArgumentMatcher &&
        378 a.matches(b, opt_expectation);
        379 var failureMessage = null;
        380 if (!isamatch) {
        381 failureMessage = goog.testing.asserts.findDifferences(a, b);
        382 isamatch = !failureMessage;
        383 }
        384 if (!isamatch && opt_expectation) {
        385 // If the error count changed, the match sent out an error
        386 // message. If the error count has not changed, then
        387 // we need to send out an error message...
        388 if (errCount == opt_expectation.getErrorMessageCount()) {
        389 // Use the _displayStringForValue() from assert.js
        390 // for consistency...
        391 if (!failureMessage) {
        392 failureMessage = 'Expected: ' + _displayStringForValue(a) +
        393 ' but was: ' + _displayStringForValue(b);
        394 }
        395 opt_expectation.addErrorMessage(failureMessage);
        396 }
        397 }
        398 return isamatch;
        399 });
        400};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/objectpropertystring.js.src.html b/docs/source/lib/goog/testing/objectpropertystring.js.src.html new file mode 100644 index 0000000..df83670 --- /dev/null +++ b/docs/source/lib/goog/testing/objectpropertystring.js.src.html @@ -0,0 +1 @@ +objectpropertystring.js

        lib/goog/testing/objectpropertystring.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Helper for passing property names as string literals in
        17 * compiled test code.
        18 *
        19 */
        20
        21goog.provide('goog.testing.ObjectPropertyString');
        22
        23
        24
        25/**
        26 * Object to pass a property name as a string literal and its containing object
        27 * when the JSCompiler is rewriting these names. This should only be used in
        28 * test code.
        29 *
        30 * @param {Object} object The containing object.
        31 * @param {Object|string} propertyString Property name as a string literal.
        32 * @constructor
        33 * @final
        34 */
        35goog.testing.ObjectPropertyString = function(object, propertyString) {
        36 this.object_ = object;
        37 this.propertyString_ = /** @type {string} */ (propertyString);
        38};
        39
        40
        41/**
        42 * @type {Object}
        43 * @private
        44 */
        45goog.testing.ObjectPropertyString.prototype.object_;
        46
        47
        48/**
        49 * @type {string}
        50 * @private
        51 */
        52goog.testing.ObjectPropertyString.prototype.propertyString_;
        53
        54
        55/**
        56 * @return {Object} The object.
        57 */
        58goog.testing.ObjectPropertyString.prototype.getObject = function() {
        59 return this.object_;
        60};
        61
        62
        63/**
        64 * @return {string} The property string.
        65 */
        66goog.testing.ObjectPropertyString.prototype.getPropertyString = function() {
        67 return this.propertyString_;
        68};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/propertyreplacer.js.src.html b/docs/source/lib/goog/testing/propertyreplacer.js.src.html new file mode 100644 index 0000000..db7bce8 --- /dev/null +++ b/docs/source/lib/goog/testing/propertyreplacer.js.src.html @@ -0,0 +1 @@ +propertyreplacer.js

        lib/goog/testing/propertyreplacer.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Helper class for creating stubs for testing.
        17 *
        18 */
        19
        20goog.provide('goog.testing.PropertyReplacer');
        21
        22/** @suppress {extraRequire} Needed for some tests to compile. */
        23goog.require('goog.testing.ObjectPropertyString');
        24goog.require('goog.userAgent');
        25
        26
        27
        28/**
        29 * Helper class for stubbing out variables and object properties for unit tests.
        30 * This class can change the value of some variables before running the test
        31 * cases, and to reset them in the tearDown phase.
        32 * See googletest.StubOutForTesting as an analogy in Python:
        33 * http://protobuf.googlecode.com/svn/trunk/python/stubout.py
        34 *
        35 * Example usage:
        36 * <pre>var stubs = new goog.testing.PropertyReplacer();
        37 *
        38 * function setUp() {
        39 * // Mock functions used in all test cases.
        40 * stubs.set(Math, 'random', function() {
        41 * return 4; // Chosen by fair dice roll. Guaranteed to be random.
        42 * });
        43 * }
        44 *
        45 * function tearDown() {
        46 * stubs.reset();
        47 * }
        48 *
        49 * function testThreeDice() {
        50 * // Mock a constant used only in this test case.
        51 * stubs.set(goog.global, 'DICE_COUNT', 3);
        52 * assertEquals(12, rollAllDice());
        53 * }</pre>
        54 *
        55 * Constraints on altered objects:
        56 * <ul>
        57 * <li>DOM subclasses aren't supported.
        58 * <li>The value of the objects' constructor property must either be equal to
        59 * the real constructor or kept untouched.
        60 * </ul>
        61 *
        62 * @constructor
        63 * @final
        64 */
        65goog.testing.PropertyReplacer = function() {
        66 /**
        67 * Stores the values changed by the set() method in chronological order.
        68 * Its items are objects with 3 fields: 'object', 'key', 'value'. The
        69 * original value for the given key in the given object is stored under the
        70 * 'value' key.
        71 * @type {Array<Object>}
        72 * @private
        73 */
        74 this.original_ = [];
        75};
        76
        77
        78/**
        79 * Indicates that a key didn't exist before having been set by the set() method.
        80 * @private @const
        81 */
        82goog.testing.PropertyReplacer.NO_SUCH_KEY_ = {};
        83
        84
        85/**
        86 * Tells if the given key exists in the object. Ignores inherited fields.
        87 * @param {Object|Function} obj The JavaScript or native object or function
        88 * whose key is to be checked.
        89 * @param {string} key The key to check.
        90 * @return {boolean} Whether the object has the key as own key.
        91 * @private
        92 */
        93goog.testing.PropertyReplacer.hasKey_ = function(obj, key) {
        94 if (!(key in obj)) {
        95 return false;
        96 }
        97 // hasOwnProperty is only reliable with JavaScript objects. It returns false
        98 // for built-in DOM attributes.
        99 if (Object.prototype.hasOwnProperty.call(obj, key)) {
        100 return true;
        101 }
        102 // In all browsers except Opera obj.constructor never equals to Object if
        103 // obj is an instance of a native class. In Opera we have to fall back on
        104 // examining obj.toString().
        105 if (obj.constructor == Object &&
        106 (!goog.userAgent.OPERA ||
        107 Object.prototype.toString.call(obj) == '[object Object]')) {
        108 return false;
        109 }
        110 try {
        111 // Firefox hack to consider "className" part of the HTML elements or
        112 // "body" part of document. Although they are defined in the prototype of
        113 // HTMLElement or Document, accessing them this way throws an exception.
        114 // <pre>
        115 // var dummy = document.body.constructor.prototype.className
        116 // [Exception... "Cannot modify properties of a WrappedNative"]
        117 // </pre>
        118 var dummy = obj.constructor.prototype[key];
        119 } catch (e) {
        120 return true;
        121 }
        122 return !(key in obj.constructor.prototype);
        123};
        124
        125
        126/**
        127 * Deletes a key from an object. Sets it to undefined or empty string if the
        128 * delete failed.
        129 * @param {Object|Function} obj The object or function to delete a key from.
        130 * @param {string} key The key to delete.
        131 * @private
        132 */
        133goog.testing.PropertyReplacer.deleteKey_ = function(obj, key) {
        134 try {
        135 delete obj[key];
        136 // Delete has no effect for built-in properties of DOM nodes in FF.
        137 if (!goog.testing.PropertyReplacer.hasKey_(obj, key)) {
        138 return;
        139 }
        140 } catch (e) {
        141 // IE throws TypeError when trying to delete properties of native objects
        142 // (e.g. DOM nodes or window), even if they have been added by JavaScript.
        143 }
        144
        145 obj[key] = undefined;
        146 if (obj[key] == 'undefined') {
        147 // Some properties such as className in IE are always evaluated as string
        148 // so undefined will become 'undefined'.
        149 obj[key] = '';
        150 }
        151};
        152
        153
        154/**
        155 * Adds or changes a value in an object while saving its original state.
        156 * @param {Object|Function} obj The JavaScript or native object or function to
        157 * alter. See the constraints in the class description.
        158 * @param {string} key The key to change the value for.
        159 * @param {*} value The new value to set.
        160 */
        161goog.testing.PropertyReplacer.prototype.set = function(obj, key, value) {
        162 var origValue = goog.testing.PropertyReplacer.hasKey_(obj, key) ? obj[key] :
        163 goog.testing.PropertyReplacer.NO_SUCH_KEY_;
        164 this.original_.push({object: obj, key: key, value: origValue});
        165 obj[key] = value;
        166};
        167
        168
        169/**
        170 * Changes an existing value in an object to another one of the same type while
        171 * saving its original state. The advantage of {@code replace} over {@link #set}
        172 * is that {@code replace} protects against typos and erroneously passing tests
        173 * after some members have been renamed during a refactoring.
        174 * @param {Object|Function} obj The JavaScript or native object or function to
        175 * alter. See the constraints in the class description.
        176 * @param {string} key The key to change the value for. It has to be present
        177 * either in {@code obj} or in its prototype chain.
        178 * @param {*} value The new value to set. It has to have the same type as the
        179 * original value. The types are compared with {@link goog.typeOf}.
        180 * @throws {Error} In case of missing key or type mismatch.
        181 */
        182goog.testing.PropertyReplacer.prototype.replace = function(obj, key, value) {
        183 if (!(key in obj)) {
        184 throw Error('Cannot replace missing property "' + key + '" in ' + obj);
        185 }
        186 if (goog.typeOf(obj[key]) != goog.typeOf(value)) {
        187 throw Error('Cannot replace property "' + key + '" in ' + obj +
        188 ' with a value of different type');
        189 }
        190 this.set(obj, key, value);
        191};
        192
        193
        194/**
        195 * Builds an object structure for the provided namespace path. Doesn't
        196 * overwrite those prefixes of the path that are already objects or functions.
        197 * @param {string} path The path to create or alter, e.g. 'goog.ui.Menu'.
        198 * @param {*} value The value to set.
        199 */
        200goog.testing.PropertyReplacer.prototype.setPath = function(path, value) {
        201 var parts = path.split('.');
        202 var obj = goog.global;
        203 for (var i = 0; i < parts.length - 1; i++) {
        204 var part = parts[i];
        205 if (part == 'prototype' && !obj[part]) {
        206 throw Error('Cannot set the prototype of ' + parts.slice(0, i).join('.'));
        207 }
        208 if (!goog.isObject(obj[part]) && !goog.isFunction(obj[part])) {
        209 this.set(obj, part, {});
        210 }
        211 obj = obj[part];
        212 }
        213 this.set(obj, parts[parts.length - 1], value);
        214};
        215
        216
        217/**
        218 * Deletes the key from the object while saving its original value.
        219 * @param {Object|Function} obj The JavaScript or native object or function to
        220 * alter. See the constraints in the class description.
        221 * @param {string} key The key to delete.
        222 */
        223goog.testing.PropertyReplacer.prototype.remove = function(obj, key) {
        224 if (goog.testing.PropertyReplacer.hasKey_(obj, key)) {
        225 this.original_.push({object: obj, key: key, value: obj[key]});
        226 goog.testing.PropertyReplacer.deleteKey_(obj, key);
        227 }
        228};
        229
        230
        231/**
        232 * Resets all changes made by goog.testing.PropertyReplacer.prototype.set.
        233 */
        234goog.testing.PropertyReplacer.prototype.reset = function() {
        235 for (var i = this.original_.length - 1; i >= 0; i--) {
        236 var original = this.original_[i];
        237 if (original.value == goog.testing.PropertyReplacer.NO_SUCH_KEY_) {
        238 goog.testing.PropertyReplacer.deleteKey_(original.object, original.key);
        239 } else {
        240 original.object[original.key] = original.value;
        241 }
        242 delete this.original_[i];
        243 }
        244 this.original_.length = 0;
        245};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/recordfunction.js.src.html b/docs/source/lib/goog/testing/recordfunction.js.src.html new file mode 100644 index 0000000..2a596df --- /dev/null +++ b/docs/source/lib/goog/testing/recordfunction.js.src.html @@ -0,0 +1 @@ +recordfunction.js

        lib/goog/testing/recordfunction.js

        1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Helper class for recording the calls of a function.
        17 *
        18 * Example:
        19 * <pre>
        20 * var stubs = new goog.testing.PropertyReplacer();
        21 *
        22 * function tearDown() {
        23 * stubs.reset();
        24 * }
        25 *
        26 * function testShuffle() {
        27 * stubs.set(Math, 'random', goog.testing.recordFunction(Math.random));
        28 * var arr = shuffle([1, 2, 3, 4, 5]);
        29 * assertSameElements([1, 2, 3, 4, 5], arr);
        30 * assertEquals(4, Math.random.getCallCount());
        31 * }
        32 *
        33 * function testOpenDialog() {
        34 * stubs.set(goog.ui, 'Dialog',
        35 * goog.testing.recordConstructor(goog.ui.Dialog));
        36 * openConfirmDialog();
        37 * var lastDialogInstance = goog.ui.Dialog.getLastCall().getThis();
        38 * assertEquals('confirm', lastDialogInstance.getTitle());
        39 * }
        40 * </pre>
        41 *
        42 */
        43
        44goog.provide('goog.testing.FunctionCall');
        45goog.provide('goog.testing.recordConstructor');
        46goog.provide('goog.testing.recordFunction');
        47
        48goog.require('goog.testing.asserts');
        49
        50
        51/**
        52 * Wraps the function into another one which calls the inner function and
        53 * records its calls. The recorded function will have 3 static methods:
        54 * {@code getCallCount}, {@code getCalls} and {@code getLastCall} but won't
        55 * inherit the original function's prototype and static fields.
        56 *
        57 * @param {!Function=} opt_f The function to wrap and record. Defaults to
        58 * {@link goog.nullFunction}.
        59 * @return {!Function} The wrapped function.
        60 */
        61goog.testing.recordFunction = function(opt_f) {
        62 var f = opt_f || goog.nullFunction;
        63 var calls = [];
        64
        65 function recordedFunction() {
        66 try {
        67 var ret = f.apply(this, arguments);
        68 calls.push(new goog.testing.FunctionCall(f, this, arguments, ret, null));
        69 return ret;
        70 } catch (err) {
        71 calls.push(new goog.testing.FunctionCall(f, this, arguments, undefined,
        72 err));
        73 throw err;
        74 }
        75 }
        76
        77 /**
        78 * @return {number} Total number of calls.
        79 */
        80 recordedFunction.getCallCount = function() {
        81 return calls.length;
        82 };
        83
        84 /**
        85 * Asserts that the function was called {@code expected} times.
        86 * @param {number} expected The expected number of calls.
        87 */
        88 recordedFunction.assertCallCount = function(expected) {
        89 var actual = calls.length;
        90 assertEquals(
        91 'Expected ' + expected + ' call(s), but was ' + actual + '.',
        92 expected, actual);
        93 };
        94
        95 /**
        96 * @return {!Array<!goog.testing.FunctionCall>} All calls of the recorded
        97 * function.
        98 */
        99 recordedFunction.getCalls = function() {
        100 return calls;
        101 };
        102
        103
        104 /**
        105 * @return {goog.testing.FunctionCall} Last call of the recorded function or
        106 * null if it hasn't been called.
        107 */
        108 recordedFunction.getLastCall = function() {
        109 return calls[calls.length - 1] || null;
        110 };
        111
        112 /**
        113 * Returns and removes the last call of the recorded function.
        114 * @return {goog.testing.FunctionCall} Last call of the recorded function or
        115 * null if it hasn't been called.
        116 */
        117 recordedFunction.popLastCall = function() {
        118 return calls.pop() || null;
        119 };
        120
        121 /**
        122 * Resets the recorded function and removes all calls.
        123 */
        124 recordedFunction.reset = function() {
        125 calls.length = 0;
        126 };
        127
        128 return recordedFunction;
        129};
        130
        131
        132/**
        133 * Same as {@link goog.testing.recordFunction} but the recorded function will
        134 * have the same prototype and static fields as the original one. It can be
        135 * used with constructors.
        136 *
        137 * @param {!Function} ctor The function to wrap and record.
        138 * @return {!Function} The wrapped function.
        139 */
        140goog.testing.recordConstructor = function(ctor) {
        141 var recordedConstructor = goog.testing.recordFunction(ctor);
        142 recordedConstructor.prototype = ctor.prototype;
        143 goog.mixin(recordedConstructor, ctor);
        144 return recordedConstructor;
        145};
        146
        147
        148
        149/**
        150 * Struct for a single function call.
        151 * @param {!Function} func The called function.
        152 * @param {!Object} thisContext {@code this} context of called function.
        153 * @param {!Arguments} args Arguments of the called function.
        154 * @param {*} ret Return value of the function or undefined in case of error.
        155 * @param {*} error The error thrown by the function or null if none.
        156 * @constructor
        157 */
        158goog.testing.FunctionCall = function(func, thisContext, args, ret, error) {
        159 this.function_ = func;
        160 this.thisContext_ = thisContext;
        161 this.arguments_ = Array.prototype.slice.call(args);
        162 this.returnValue_ = ret;
        163 this.error_ = error;
        164};
        165
        166
        167/**
        168 * @return {!Function} The called function.
        169 */
        170goog.testing.FunctionCall.prototype.getFunction = function() {
        171 return this.function_;
        172};
        173
        174
        175/**
        176 * @return {!Object} {@code this} context of called function. It is the same as
        177 * the created object if the function is a constructor.
        178 */
        179goog.testing.FunctionCall.prototype.getThis = function() {
        180 return this.thisContext_;
        181};
        182
        183
        184/**
        185 * @return {!Array<?>} Arguments of the called function.
        186 */
        187goog.testing.FunctionCall.prototype.getArguments = function() {
        188 return this.arguments_;
        189};
        190
        191
        192/**
        193 * Returns the nth argument of the called function.
        194 * @param {number} index 0-based index of the argument.
        195 * @return {*} The argument value or undefined if there is no such argument.
        196 */
        197goog.testing.FunctionCall.prototype.getArgument = function(index) {
        198 return this.arguments_[index];
        199};
        200
        201
        202/**
        203 * @return {*} Return value of the function or undefined in case of error.
        204 */
        205goog.testing.FunctionCall.prototype.getReturnValue = function() {
        206 return this.returnValue_;
        207};
        208
        209
        210/**
        211 * @return {*} The error thrown by the function or null if none.
        212 */
        213goog.testing.FunctionCall.prototype.getError = function() {
        214 return this.error_;
        215};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/stacktrace.js.src.html b/docs/source/lib/goog/testing/stacktrace.js.src.html new file mode 100644 index 0000000..5fdefd0 --- /dev/null +++ b/docs/source/lib/goog/testing/stacktrace.js.src.html @@ -0,0 +1 @@ +stacktrace.js

        lib/goog/testing/stacktrace.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Tools for parsing and pretty printing error stack traces.
        17 *
        18 */
        19
        20goog.provide('goog.testing.stacktrace');
        21goog.provide('goog.testing.stacktrace.Frame');
        22
        23
        24
        25/**
        26 * Class representing one stack frame.
        27 * @param {string} context Context object, empty in case of global functions or
        28 * if the browser doesn't provide this information.
        29 * @param {string} name Function name, empty in case of anonymous functions.
        30 * @param {string} alias Alias of the function if available. For example the
        31 * function name will be 'c' and the alias will be 'b' if the function is
        32 * defined as <code>a.b = function c() {};</code>.
        33 * @param {string} args Arguments of the function in parentheses if available.
        34 * @param {string} path File path or URL including line number and optionally
        35 * column number separated by colons.
        36 * @constructor
        37 * @final
        38 */
        39goog.testing.stacktrace.Frame = function(context, name, alias, args, path) {
        40 this.context_ = context;
        41 this.name_ = name;
        42 this.alias_ = alias;
        43 this.args_ = args;
        44 this.path_ = path;
        45};
        46
        47
        48/**
        49 * @return {string} The function name or empty string if the function is
        50 * anonymous and the object field which it's assigned to is unknown.
        51 */
        52goog.testing.stacktrace.Frame.prototype.getName = function() {
        53 return this.name_;
        54};
        55
        56
        57/**
        58 * @return {boolean} Whether the stack frame contains an anonymous function.
        59 */
        60goog.testing.stacktrace.Frame.prototype.isAnonymous = function() {
        61 return !this.name_ || this.context_ == '[object Object]';
        62};
        63
        64
        65/**
        66 * Brings one frame of the stack trace into a common format across browsers.
        67 * @return {string} Pretty printed stack frame.
        68 */
        69goog.testing.stacktrace.Frame.prototype.toCanonicalString = function() {
        70 var htmlEscape = goog.testing.stacktrace.htmlEscape_;
        71 var deobfuscate = goog.testing.stacktrace.maybeDeobfuscateFunctionName_;
        72
        73 var canonical = [
        74 this.context_ ? htmlEscape(this.context_) + '.' : '',
        75 this.name_ ? htmlEscape(deobfuscate(this.name_)) : 'anonymous',
        76 htmlEscape(this.args_),
        77 this.alias_ ? ' [as ' + htmlEscape(deobfuscate(this.alias_)) + ']' : ''
        78 ];
        79
        80 if (this.path_) {
        81 canonical.push(' at ');
        82 canonical.push(htmlEscape(this.path_));
        83 }
        84 return canonical.join('');
        85};
        86
        87
        88/**
        89 * Maximum number of steps while the call chain is followed.
        90 * @private {number}
        91 * @const
        92 */
        93goog.testing.stacktrace.MAX_DEPTH_ = 20;
        94
        95
        96/**
        97 * Maximum length of a string that can be matched with a RegExp on
        98 * Firefox 3x. Exceeding this approximate length will cause string.match
        99 * to exceed Firefox's stack quota. This situation can be encountered
        100 * when goog.globalEval is invoked with a long argument; such as
        101 * when loading a module.
        102 * @private {number}
        103 * @const
        104 */
        105goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;
        106
        107
        108/**
        109 * RegExp pattern for JavaScript identifiers. We don't support Unicode
        110 * identifiers defined in ECMAScript v3.
        111 * @private {string}
        112 * @const
        113 */
        114goog.testing.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';
        115
        116
        117/**
        118 * RegExp pattern for function name alias in the V8 stack trace.
        119 * @private {string}
        120 * @const
        121 */
        122goog.testing.stacktrace.V8_ALIAS_PATTERN_ =
        123 '(?: \\[as (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';
        124
        125
        126/**
        127 * RegExp pattern for the context of a function call in a V8 stack trace.
        128 * Creates an optional submatch for the namespace identifier including the
        129 * "new" keyword for constructor calls (e.g. "new foo.Bar").
        130 * @private {string}
        131 * @const
        132 */
        133goog.testing.stacktrace.V8_CONTEXT_PATTERN_ =
        134 '(?:((?:new )?(?:\\[object Object\\]|' +
        135 goog.testing.stacktrace.IDENTIFIER_PATTERN_ +
        136 '(?:\\.' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*))\\.)?';
        137
        138
        139/**
        140 * RegExp pattern for function names and constructor calls in the V8 stack
        141 * trace.
        142 * @private {string}
        143 * @const
        144 */
        145goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ =
        146 '(?:new )?(?:' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ +
        147 '|<anonymous>)';
        148
        149
        150/**
        151 * RegExp pattern for function call in the V8 stack trace. Creates 3 submatches
        152 * with context object (optional), function name and function alias (optional).
        153 * @private {string}
        154 * @const
        155 */
        156goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ =
        157 ' ' + goog.testing.stacktrace.V8_CONTEXT_PATTERN_ +
        158 '(' + goog.testing.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +
        159 goog.testing.stacktrace.V8_ALIAS_PATTERN_;
        160
        161
        162/**
        163 * RegExp pattern for an URL + position inside the file.
        164 * @private {string}
        165 * @const
        166 */
        167goog.testing.stacktrace.URL_PATTERN_ =
        168 '((?:http|https|file)://[^\\s)]+|javascript:.*)';
        169
        170
        171/**
        172 * RegExp pattern for an URL + line number + column number in V8.
        173 * The URL is either in submatch 1 or submatch 2.
        174 * @private {string}
        175 * @const
        176 */
        177goog.testing.stacktrace.CHROME_URL_PATTERN_ = ' (?:' +
        178 '\\(unknown source\\)' + '|' +
        179 '\\(native\\)' + '|' +
        180 '\\((.+)\\)|(.+))';
        181
        182
        183/**
        184 * Regular expression for parsing one stack frame in V8. For more information
        185 * on V8 stack frame formats, see
        186 * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi.
        187 * @private {!RegExp}
        188 * @const
        189 */
        190goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^ at' +
        191 '(?:' + goog.testing.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +
        192 goog.testing.stacktrace.CHROME_URL_PATTERN_ + '$');
        193
        194
        195/**
        196 * RegExp pattern for function call in the Firefox stack trace.
        197 * Creates 2 submatches with function name (optional) and arguments.
        198 * @private {string}
        199 * @const
        200 */
        201goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =
        202 '(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')?' +
        203 '(\\(.*\\))?@';
        204
        205
        206/**
        207 * Regular expression for parsing one stack frame in Firefox.
        208 * @private {!RegExp}
        209 * @const
        210 */
        211goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +
        212 goog.testing.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +
        213 '(?::0|' + goog.testing.stacktrace.URL_PATTERN_ + ')$');
        214
        215
        216/**
        217 * RegExp pattern for an anonymous function call in an Opera stack frame.
        218 * Creates 2 (optional) submatches: the context object and function name.
        219 * @private {string}
        220 * @const
        221 */
        222goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =
        223 '<anonymous function(?:\\: ' +
        224 '(?:(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ +
        225 '(?:\\.' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +
        226 '(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '))?>';
        227
        228
        229/**
        230 * RegExp pattern for a function call in an Opera stack frame.
        231 * Creates 4 (optional) submatches: the function name (if not anonymous),
        232 * the aliased context object and function name (if anonymous), and the
        233 * function call arguments.
        234 * @private {string}
        235 * @const
        236 */
        237goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ =
        238 '(?:(?:(' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')|' +
        239 goog.testing.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +
        240 ')(\\(.*\\)))?@';
        241
        242
        243/**
        244 * Regular expression for parsing on stack frame in Opera 11.68 - 12.17.
        245 * Newer versions of Opera use V8 and stack frames should match against
        246 * goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_.
        247 * @private {!RegExp}
        248 * @const
        249 */
        250goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp('^' +
        251 goog.testing.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +
        252 goog.testing.stacktrace.URL_PATTERN_ + '?$');
        253
        254
        255/**
        256 * Regular expression for finding the function name in its source.
        257 * @private {!RegExp}
        258 * @const
        259 */
        260goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_ = new RegExp(
        261 '^function (' + goog.testing.stacktrace.IDENTIFIER_PATTERN_ + ')');
        262
        263
        264/**
        265 * RegExp pattern for function call in a IE stack trace. This expression allows
        266 * for identifiers like 'Anonymous function', 'eval code', and 'Global code'.
        267 * @private {string}
        268 * @const
        269 */
        270goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ = '(' +
        271 goog.testing.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';
        272
        273
        274/**
        275 * Regular expression for parsing a stack frame in IE.
        276 * @private {!RegExp}
        277 * @const
        278 */
        279goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_ = new RegExp('^ at ' +
        280 goog.testing.stacktrace.IE_FUNCTION_CALL_PATTERN_ +
        281 '\\s*\\((eval code:[^)]*|' + goog.testing.stacktrace.URL_PATTERN_ +
        282 ')\\)?$');
        283
        284
        285/**
        286 * Creates a stack trace by following the call chain. Based on
        287 * {@link goog.debug.getStacktrace}.
        288 * @return {!Array<!goog.testing.stacktrace.Frame>} Stack frames.
        289 * @private
        290 * @suppress {es5Strict}
        291 */
        292goog.testing.stacktrace.followCallChain_ = function() {
        293 var frames = [];
        294 var fn = arguments.callee.caller;
        295 var depth = 0;
        296
        297 while (fn && depth < goog.testing.stacktrace.MAX_DEPTH_) {
        298 var fnString = Function.prototype.toString.call(fn);
        299 var match = fnString.match(goog.testing.stacktrace.FUNCTION_SOURCE_REGEXP_);
        300 var functionName = match ? match[1] : '';
        301
        302 var argsBuilder = ['('];
        303 if (fn.arguments) {
        304 for (var i = 0; i < fn.arguments.length; i++) {
        305 var arg = fn.arguments[i];
        306 if (i > 0) {
        307 argsBuilder.push(', ');
        308 }
        309 if (goog.isString(arg)) {
        310 argsBuilder.push('"', arg, '"');
        311 } else {
        312 // Some args are mocks, and we don't want to fail from them not having
        313 // expected a call to toString, so instead insert a static string.
        314 if (arg && arg['$replay']) {
        315 argsBuilder.push('goog.testing.Mock');
        316 } else {
        317 argsBuilder.push(String(arg));
        318 }
        319 }
        320 }
        321 } else {
        322 // Opera 10 doesn't know the arguments of native functions.
        323 argsBuilder.push('unknown');
        324 }
        325 argsBuilder.push(')');
        326 var args = argsBuilder.join('');
        327
        328 frames.push(new goog.testing.stacktrace.Frame('', functionName, '', args,
        329 ''));
        330
        331 /** @preserveTry */
        332 try {
        333 fn = fn.caller;
        334 } catch (e) {
        335 break;
        336 }
        337 depth++;
        338 }
        339
        340 return frames;
        341};
        342
        343
        344/**
        345 * Parses one stack frame.
        346 * @param {string} frameStr The stack frame as string.
        347 * @return {goog.testing.stacktrace.Frame} Stack frame object or null if the
        348 * parsing failed.
        349 * @private
        350 */
        351goog.testing.stacktrace.parseStackFrame_ = function(frameStr) {
        352 // This match includes newer versions of Opera (15+).
        353 var m = frameStr.match(goog.testing.stacktrace.V8_STACK_FRAME_REGEXP_);
        354 if (m) {
        355 return new goog.testing.stacktrace.Frame(m[1] || '', m[2] || '', m[3] || '',
        356 '', m[4] || m[5] || m[6] || '');
        357 }
        358
        359 if (frameStr.length >
        360 goog.testing.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {
        361 return goog.testing.stacktrace.parseLongFirefoxFrame_(frameStr);
        362 }
        363
        364 m = frameStr.match(goog.testing.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);
        365 if (m) {
        366 return new goog.testing.stacktrace.Frame('', m[1] || '', '', m[2] || '',
        367 m[3] || '');
        368 }
        369
        370 // Match against Presto Opera 11.68 - 12.17.
        371 m = frameStr.match(goog.testing.stacktrace.OPERA_STACK_FRAME_REGEXP_);
        372 if (m) {
        373 return new goog.testing.stacktrace.Frame(m[2] || '', m[1] || m[3] || '',
        374 '', m[4] || '', m[5] || '');
        375 }
        376
        377 m = frameStr.match(goog.testing.stacktrace.IE_STACK_FRAME_REGEXP_);
        378 if (m) {
        379 return new goog.testing.stacktrace.Frame('', m[1] || '', '', '',
        380 m[2] || '');
        381 }
        382
        383 return null;
        384};
        385
        386
        387/**
        388 * Parses a long firefox stack frame.
        389 * @param {string} frameStr The stack frame as string.
        390 * @return {!goog.testing.stacktrace.Frame} Stack frame object.
        391 * @private
        392 */
        393goog.testing.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {
        394 var firstParen = frameStr.indexOf('(');
        395 var lastAmpersand = frameStr.lastIndexOf('@');
        396 var lastColon = frameStr.lastIndexOf(':');
        397 var functionName = '';
        398 if ((firstParen >= 0) && (firstParen < lastAmpersand)) {
        399 functionName = frameStr.substring(0, firstParen);
        400 }
        401 var loc = '';
        402 if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {
        403 loc = frameStr.substring(lastAmpersand + 1);
        404 }
        405 var args = '';
        406 if ((firstParen >= 0 && lastAmpersand > 0) &&
        407 (firstParen < lastAmpersand)) {
        408 args = frameStr.substring(firstParen, lastAmpersand);
        409 }
        410 return new goog.testing.stacktrace.Frame('', functionName, '', args, loc);
        411};
        412
        413
        414/**
        415 * Function to deobfuscate function names.
        416 * @type {function(string): string}
        417 * @private
        418 */
        419goog.testing.stacktrace.deobfuscateFunctionName_;
        420
        421
        422/**
        423 * Sets function to deobfuscate function names.
        424 * @param {function(string): string} fn function to deobfuscate function names.
        425 */
        426goog.testing.stacktrace.setDeobfuscateFunctionName = function(fn) {
        427 goog.testing.stacktrace.deobfuscateFunctionName_ = fn;
        428};
        429
        430
        431/**
        432 * Deobfuscates a compiled function name with the function passed to
        433 * {@link #setDeobfuscateFunctionName}. Returns the original function name if
        434 * the deobfuscator hasn't been set.
        435 * @param {string} name The function name to deobfuscate.
        436 * @return {string} The deobfuscated function name.
        437 * @private
        438 */
        439goog.testing.stacktrace.maybeDeobfuscateFunctionName_ = function(name) {
        440 return goog.testing.stacktrace.deobfuscateFunctionName_ ?
        441 goog.testing.stacktrace.deobfuscateFunctionName_(name) : name;
        442};
        443
        444
        445/**
        446 * Escapes the special character in HTML.
        447 * @param {string} text Plain text.
        448 * @return {string} Escaped text.
        449 * @private
        450 */
        451goog.testing.stacktrace.htmlEscape_ = function(text) {
        452 return text.replace(/&/g, '&amp;').
        453 replace(/</g, '&lt;').
        454 replace(/>/g, '&gt;').
        455 replace(/"/g, '&quot;');
        456};
        457
        458
        459/**
        460 * Converts the stack frames into canonical format. Chops the beginning and the
        461 * end of it which come from the testing environment, not from the test itself.
        462 * @param {!Array<goog.testing.stacktrace.Frame>} frames The frames.
        463 * @return {string} Canonical, pretty printed stack trace.
        464 * @private
        465 */
        466goog.testing.stacktrace.framesToString_ = function(frames) {
        467 // Removes the anonymous calls from the end of the stack trace (they come
        468 // from testrunner.js, testcase.js and asserts.js), so the stack trace will
        469 // end with the test... method.
        470 var lastIndex = frames.length - 1;
        471 while (frames[lastIndex] && frames[lastIndex].isAnonymous()) {
        472 lastIndex--;
        473 }
        474
        475 // Removes the beginning of the stack trace until the call of the private
        476 // _assert function (inclusive), so the stack trace will begin with a public
        477 // asserter. Does nothing if _assert is not present in the stack trace.
        478 var privateAssertIndex = -1;
        479 for (var i = 0; i < frames.length; i++) {
        480 if (frames[i] && frames[i].getName() == '_assert') {
        481 privateAssertIndex = i;
        482 break;
        483 }
        484 }
        485
        486 var canonical = [];
        487 for (var i = privateAssertIndex + 1; i <= lastIndex; i++) {
        488 canonical.push('> ');
        489 if (frames[i]) {
        490 canonical.push(frames[i].toCanonicalString());
        491 } else {
        492 canonical.push('(unknown)');
        493 }
        494 canonical.push('\n');
        495 }
        496 return canonical.join('');
        497};
        498
        499
        500/**
        501 * Parses the browser's native stack trace.
        502 * @param {string} stack Stack trace.
        503 * @return {!Array<goog.testing.stacktrace.Frame>} Stack frames. The
        504 * unrecognized frames will be nulled out.
        505 * @private
        506 */
        507goog.testing.stacktrace.parse_ = function(stack) {
        508 var lines = stack.replace(/\s*$/, '').split('\n');
        509 var frames = [];
        510 for (var i = 0; i < lines.length; i++) {
        511 frames.push(goog.testing.stacktrace.parseStackFrame_(lines[i]));
        512 }
        513 return frames;
        514};
        515
        516
        517/**
        518 * Brings the stack trace into a common format across browsers.
        519 * @param {string} stack Browser-specific stack trace.
        520 * @return {string} Same stack trace in common format.
        521 */
        522goog.testing.stacktrace.canonicalize = function(stack) {
        523 var frames = goog.testing.stacktrace.parse_(stack);
        524 return goog.testing.stacktrace.framesToString_(frames);
        525};
        526
        527
        528/**
        529 * Returns the native stack trace.
        530 * @return {string|!Array<!CallSite>}
        531 * @private
        532 */
        533goog.testing.stacktrace.getNativeStack_ = function() {
        534 var tmpError = new Error();
        535 if (tmpError.stack) {
        536 return tmpError.stack;
        537 }
        538
        539 // IE10 will only create a stack trace when the Error is thrown.
        540 // We use null.x() to throw an exception because the closure compiler may
        541 // replace "throw" with a function call in an attempt to minimize the binary
        542 // size, which in turn has the side effect of adding an unwanted stack frame.
        543 try {
        544 null.x();
        545 } catch (e) {
        546 return e.stack;
        547 }
        548 return '';
        549};
        550
        551
        552/**
        553 * Gets the native stack trace if available otherwise follows the call chain.
        554 * @return {string} The stack trace in canonical format.
        555 */
        556goog.testing.stacktrace.get = function() {
        557 var stack = goog.testing.stacktrace.getNativeStack_();
        558 var frames;
        559 if (!stack) {
        560 frames = goog.testing.stacktrace.followCallChain_();
        561 } else if (goog.isArray(stack)) {
        562 frames = goog.testing.stacktrace.callSitesToFrames_(stack);
        563 } else {
        564 frames = goog.testing.stacktrace.parse_(stack);
        565 }
        566 return goog.testing.stacktrace.framesToString_(frames);
        567};
        568
        569
        570/**
        571 * Converts an array of CallSite (elements of a stack trace in V8) to an array
        572 * of Frames.
        573 * @param {!Array<!CallSite>} stack The stack as an array of CallSites.
        574 * @return {!Array<!goog.testing.stacktrace.Frame>} The stack as an array of
        575 * Frames.
        576 * @private
        577 */
        578goog.testing.stacktrace.callSitesToFrames_ = function(stack) {
        579 var frames = [];
        580 for (var i = 0; i < stack.length; i++) {
        581 var callSite = stack[i];
        582 var functionName = callSite.getFunctionName() || 'unknown';
        583 var fileName = callSite.getFileName();
        584 var path = fileName ? fileName + ':' + callSite.getLineNumber() + ':' +
        585 callSite.getColumnNumber() : 'unknown';
        586 frames.push(
        587 new goog.testing.stacktrace.Frame('', functionName, '', '', path));
        588 }
        589 return frames;
        590};
        591
        592
        593goog.exportSymbol('setDeobfuscateFunctionName',
        594 goog.testing.stacktrace.setDeobfuscateFunctionName);
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/strictmock.js.src.html b/docs/source/lib/goog/testing/strictmock.js.src.html new file mode 100644 index 0000000..755a5ca --- /dev/null +++ b/docs/source/lib/goog/testing/strictmock.js.src.html @@ -0,0 +1 @@ +strictmock.js

        lib/goog/testing/strictmock.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview This file defines a strict mock implementation.
        17 */
        18
        19goog.provide('goog.testing.StrictMock');
        20
        21goog.require('goog.array');
        22goog.require('goog.testing.Mock');
        23
        24
        25
        26/**
        27 * This is a mock that verifies that methods are called in the order that they
        28 * are specified during the recording phase. Since it verifies order, it
        29 * follows 'fail fast' semantics. If it detects a deviation from the
        30 * expectations, it will throw an exception and not wait for verify to be
        31 * called.
        32 * @param {Object|Function} objectToMock The object that should be mocked, or
        33 * the constructor of an object to mock.
        34 * @param {boolean=} opt_mockStaticMethods An optional argument denoting that
        35 * a mock should be constructed from the static functions of a class.
        36 * @param {boolean=} opt_createProxy An optional argument denoting that
        37 * a proxy for the target mock should be created.
        38 * @constructor
        39 * @extends {goog.testing.Mock}
        40 * @final
        41 */
        42goog.testing.StrictMock = function(objectToMock, opt_mockStaticMethods,
        43 opt_createProxy) {
        44 goog.testing.Mock.call(this, objectToMock, opt_mockStaticMethods,
        45 opt_createProxy);
        46
        47 /**
        48 * An array of MockExpectations.
        49 * @type {Array<goog.testing.MockExpectation>}
        50 * @private
        51 */
        52 this.$expectations_ = [];
        53};
        54goog.inherits(goog.testing.StrictMock, goog.testing.Mock);
        55
        56
        57/** @override */
        58goog.testing.StrictMock.prototype.$recordExpectation = function() {
        59 this.$expectations_.push(this.$pendingExpectation);
        60};
        61
        62
        63/** @override */
        64goog.testing.StrictMock.prototype.$recordCall = function(name, args) {
        65 if (this.$expectations_.length == 0) {
        66 this.$throwCallException(name, args);
        67 }
        68
        69 // If the current expectation has a different name, make sure it was called
        70 // enough and then discard it. We're through with it.
        71 var currentExpectation = this.$expectations_[0];
        72 while (!this.$verifyCall(currentExpectation, name, args)) {
        73
        74 // This might be an item which has passed its min, and we can now
        75 // look past it, or it might be below its min and generate an error.
        76 if (currentExpectation.actualCalls < currentExpectation.minCalls) {
        77 this.$throwCallException(name, args, currentExpectation);
        78 }
        79
        80 this.$expectations_.shift();
        81 if (this.$expectations_.length < 1) {
        82 // Nothing left, but this may be a failed attempt to call the previous
        83 // item on the list, which may have been between its min and max.
        84 this.$throwCallException(name, args, currentExpectation);
        85 }
        86 currentExpectation = this.$expectations_[0];
        87 }
        88
        89 if (currentExpectation.maxCalls == 0) {
        90 this.$throwCallException(name, args);
        91 }
        92
        93 currentExpectation.actualCalls++;
        94 // If we hit the max number of calls for this expectation, we're finished
        95 // with it.
        96 if (currentExpectation.actualCalls == currentExpectation.maxCalls) {
        97 this.$expectations_.shift();
        98 }
        99
        100 return this.$do(currentExpectation, args);
        101};
        102
        103
        104/** @override */
        105goog.testing.StrictMock.prototype.$reset = function() {
        106 goog.testing.StrictMock.superClass_.$reset.call(this);
        107
        108 goog.array.clear(this.$expectations_);
        109};
        110
        111
        112/** @override */
        113goog.testing.StrictMock.prototype.$verify = function() {
        114 goog.testing.StrictMock.superClass_.$verify.call(this);
        115
        116 while (this.$expectations_.length > 0) {
        117 var expectation = this.$expectations_[0];
        118 if (expectation.actualCalls < expectation.minCalls) {
        119 this.$throwException('Missing a call to ' + expectation.name +
        120 '\nExpected: ' + expectation.minCalls + ' but was: ' +
        121 expectation.actualCalls);
        122
        123 } else {
        124 // Don't need to check max, that's handled when the call is made
        125 this.$expectations_.shift();
        126 }
        127 }
        128};
        129
        130
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/testcase.js.src.html b/docs/source/lib/goog/testing/testcase.js.src.html new file mode 100644 index 0000000..aed219f --- /dev/null +++ b/docs/source/lib/goog/testing/testcase.js.src.html @@ -0,0 +1 @@ +testcase.js

        lib/goog/testing/testcase.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A class representing a set of test functions to be run.
        17 *
        18 * Testing code should not have dependencies outside of goog.testing so as to
        19 * reduce the chance of masking missing dependencies.
        20 *
        21 * This file does not compile correctly with --collapse_properties. Use
        22 * --property_renaming=ALL_UNQUOTED instead.
        23 *
        24 */
        25
        26goog.provide('goog.testing.TestCase');
        27goog.provide('goog.testing.TestCase.Error');
        28goog.provide('goog.testing.TestCase.Order');
        29goog.provide('goog.testing.TestCase.Result');
        30goog.provide('goog.testing.TestCase.Test');
        31
        32
        33goog.require('goog.Promise');
        34goog.require('goog.Thenable');
        35goog.require('goog.asserts');
        36goog.require('goog.dom.TagName');
        37goog.require('goog.object');
        38goog.require('goog.testing.asserts');
        39goog.require('goog.testing.stacktrace');
        40
        41
        42
        43/**
        44 * A class representing a JsUnit test case. A TestCase is made up of a number
        45 * of test functions which can be run. Individual test cases can override the
        46 * following functions to set up their test environment:
        47 * - runTests - completely override the test's runner
        48 * - setUpPage - called before any of the test functions are run
        49 * - tearDownPage - called after all tests are finished
        50 * - setUp - called before each of the test functions
        51 * - tearDown - called after each of the test functions
        52 * - shouldRunTests - called before a test run, all tests are skipped if it
        53 * returns false. Can be used to disable tests on browsers
        54 * where they aren't expected to pass.
        55 *
        56 * Use {@link #autoDiscoverLifecycle} and {@link #autoDiscoverTests}
        57 *
        58 * @param {string=} opt_name The name of the test case, defaults to
        59 * 'Untitled Test Case'.
        60 * @constructor
        61 */
        62goog.testing.TestCase = function(opt_name) {
        63 /**
        64 * A name for the test case.
        65 * @type {string}
        66 * @private
        67 */
        68 this.name_ = opt_name || 'Untitled Test Case';
        69
        70 /**
        71 * Array of test functions that can be executed.
        72 * @type {!Array<!goog.testing.TestCase.Test>}
        73 * @private
        74 */
        75 this.tests_ = [];
        76
        77 /**
        78 * Set of test names and/or indices to execute, or null if all tests should
        79 * be executed.
        80 *
        81 * Indices are included to allow automation tools to run a subset of the
        82 * tests without knowing the exact contents of the test file.
        83 *
        84 * Indices should only be used with SORTED ordering.
        85 *
        86 * Example valid values:
        87 * <ul>
        88 * <li>[testName]
        89 * <li>[testName1, testName2]
        90 * <li>[2] - will run the 3rd test in the order specified
        91 * <li>[1,3,5]
        92 * <li>[testName1, testName2, 3, 5] - will work
        93 * <ul>
        94 * @type {Object}
        95 * @private
        96 */
        97 this.testsToRun_ = null;
        98
        99 /**
        100 * The order to run the auto-discovered tests in.
        101 * @type {string}
        102 */
        103 this.order = goog.testing.TestCase.Order.SORTED;
        104
        105 /** @private {function(!goog.testing.TestCase.Result)} */
        106 this.runNextTestCallback_ = goog.nullFunction;
        107
        108 /**
        109 * The number of {@link runNextTest_} frames currently on the stack.
        110 * When this exceeds {@link MAX_STACK_DEPTH_}, test execution is rescheduled
        111 * for a later tick of the event loop.
        112 * @see {finishTestInvocation_}
        113 * @private {number}
        114 */
        115 this.depth_ = 0;
        116
        117 /** @private {goog.testing.TestCase.Test} */
        118 this.curTest_ = null;
        119
        120 /**
        121 * Object used to encapsulate the test results.
        122 * @type {!goog.testing.TestCase.Result}
        123 * @protected
        124 * @suppress {underscore|visibility}
        125 */
        126 this.result_ = new goog.testing.TestCase.Result(this);
        127};
        128
        129
        130/**
        131 * The order to run the auto-discovered tests.
        132 * @enum {string}
        133 */
        134goog.testing.TestCase.Order = {
        135 /**
        136 * This is browser dependent and known to be different in FF and Safari
        137 * compared to others.
        138 */
        139 NATURAL: 'natural',
        140
        141 /** Random order. */
        142 RANDOM: 'random',
        143
        144 /** Sorted based on the name. */
        145 SORTED: 'sorted'
        146};
        147
        148
        149/**
        150 * @return {string} The name of the test.
        151 */
        152goog.testing.TestCase.prototype.getName = function() {
        153 return this.name_;
        154};
        155
        156
        157/**
        158 * The maximum amount of time in milliseconds that the test case can take
        159 * before it is forced to yield and reschedule. This prevents the test runner
        160 * from blocking the browser and potentially hurting the test harness.
        161 * @type {number}
        162 */
        163goog.testing.TestCase.maxRunTime = 200;
        164
        165
        166/**
        167 * The maximum number of {@link runNextTest_} frames that can be on the stack
        168 * before the test case is forced to yield and reschedule. Although modern
        169 * browsers can handle thousands of stack frames, this is set conservatively
        170 * because maximum stack depth has never been standardized, and engine-specific
        171 * techniques like tail cail optimization can affect the exact depth.
        172 * @private @const
        173 */
        174goog.testing.TestCase.MAX_STACK_DEPTH_ = 100;
        175
        176
        177/**
        178 * Save a reference to {@code window.setTimeout}, so any code that overrides the
        179 * default behavior (the MockClock, for example) doesn't affect our runner.
        180 * @type {function((Function|string), number=, *=): number}
        181 * @private
        182 */
        183goog.testing.TestCase.protectedSetTimeout_ = goog.global.setTimeout;
        184
        185
        186/**
        187 * Save a reference to {@code window.clearTimeout}, so any code that overrides
        188 * the default behavior (e.g. MockClock) doesn't affect our runner.
        189 * @type {function((null|number|undefined)): void}
        190 * @private
        191 */
        192goog.testing.TestCase.protectedClearTimeout_ = goog.global.clearTimeout;
        193
        194
        195/**
        196 * Save a reference to {@code window.Date}, so any code that overrides
        197 * the default behavior doesn't affect our runner.
        198 * @type {function(new: Date)}
        199 * @private
        200 */
        201goog.testing.TestCase.protectedDate_ = Date;
        202
        203
        204/**
        205 * Saved string referencing goog.global.setTimeout's string serialization. IE
        206 * sometimes fails to uphold equality for setTimeout, but the string version
        207 * stays the same.
        208 * @type {string}
        209 * @private
        210 */
        211goog.testing.TestCase.setTimeoutAsString_ = String(goog.global.setTimeout);
        212
        213
        214/**
        215 * TODO(user) replace this with prototype.currentTest.
        216 * Name of the current test that is running, or null if none is running.
        217 * @type {?string}
        218 */
        219goog.testing.TestCase.currentTestName = null;
        220
        221
        222/**
        223 * Avoid a dependency on goog.userAgent and keep our own reference of whether
        224 * the browser is IE.
        225 * @type {boolean}
        226 */
        227goog.testing.TestCase.IS_IE = typeof opera == 'undefined' &&
        228 !!goog.global.navigator &&
        229 goog.global.navigator.userAgent.indexOf('MSIE') != -1;
        230
        231
        232/**
        233 * Exception object that was detected before a test runs.
        234 * @type {*}
        235 * @protected
        236 */
        237goog.testing.TestCase.prototype.exceptionBeforeTest;
        238
        239
        240/**
        241 * Whether the test case has ever tried to execute.
        242 * @type {boolean}
        243 */
        244goog.testing.TestCase.prototype.started = false;
        245
        246
        247/**
        248 * Whether the test case is running.
        249 * @type {boolean}
        250 */
        251goog.testing.TestCase.prototype.running = false;
        252
        253
        254/**
        255 * Timestamp for when the test was started.
        256 * @type {number}
        257 * @private
        258 */
        259goog.testing.TestCase.prototype.startTime_ = 0;
        260
        261
        262/**
        263 * Time since the last batch of tests was started, if batchTime exceeds
        264 * {@link #maxRunTime} a timeout will be used to stop the tests blocking the
        265 * browser and a new batch will be started.
        266 * @type {number}
        267 * @private
        268 */
        269goog.testing.TestCase.prototype.batchTime_ = 0;
        270
        271
        272/**
        273 * Pointer to the current test.
        274 * @type {number}
        275 * @private
        276 */
        277goog.testing.TestCase.prototype.currentTestPointer_ = 0;
        278
        279
        280/**
        281 * Optional callback that will be executed when the test has finalized.
        282 * @type {Function}
        283 * @private
        284 */
        285goog.testing.TestCase.prototype.onCompleteCallback_ = null;
        286
        287
        288/**
        289 * Adds a new test to the test case.
        290 * @param {goog.testing.TestCase.Test} test The test to add.
        291 */
        292goog.testing.TestCase.prototype.add = function(test) {
        293 if (this.started) {
        294 throw Error('Tests cannot be added after execute() has been called. ' +
        295 'Test: ' + test.name);
        296 }
        297
        298 this.tests_.push(test);
        299};
        300
        301
        302/**
        303 * Creates and adds a new test.
        304 *
        305 * Convenience function to make syntax less awkward when not using automatic
        306 * test discovery.
        307 *
        308 * @param {string} name The test name.
        309 * @param {!Function} ref Reference to the test function.
        310 * @param {!Object=} opt_scope Optional scope that the test function should be
        311 * called in.
        312 */
        313goog.testing.TestCase.prototype.addNewTest = function(name, ref, opt_scope) {
        314 var test = new goog.testing.TestCase.Test(name, ref, opt_scope || this);
        315 this.add(test);
        316};
        317
        318
        319/**
        320 * Sets the tests.
        321 * @param {!Array<goog.testing.TestCase.Test>} tests A new test array.
        322 * @protected
        323 */
        324goog.testing.TestCase.prototype.setTests = function(tests) {
        325 this.tests_ = tests;
        326};
        327
        328
        329/**
        330 * Gets the tests.
        331 * @return {!Array<goog.testing.TestCase.Test>} The test array.
        332 */
        333goog.testing.TestCase.prototype.getTests = function() {
        334 return this.tests_;
        335};
        336
        337
        338/**
        339 * Returns the number of tests contained in the test case.
        340 * @return {number} The number of tests.
        341 */
        342goog.testing.TestCase.prototype.getCount = function() {
        343 return this.tests_.length;
        344};
        345
        346
        347/**
        348 * Returns the number of tests actually run in the test case, i.e. subtracting
        349 * any which are skipped.
        350 * @return {number} The number of un-ignored tests.
        351 */
        352goog.testing.TestCase.prototype.getActuallyRunCount = function() {
        353 return this.testsToRun_ ? goog.object.getCount(this.testsToRun_) : 0;
        354};
        355
        356
        357/**
        358 * Returns the current test and increments the pointer.
        359 * @return {goog.testing.TestCase.Test} The current test case.
        360 */
        361goog.testing.TestCase.prototype.next = function() {
        362 var test;
        363 while ((test = this.tests_[this.currentTestPointer_++])) {
        364 if (!this.testsToRun_ || this.testsToRun_[test.name] ||
        365 this.testsToRun_[this.currentTestPointer_ - 1]) {
        366 return test;
        367 }
        368 }
        369 return null;
        370};
        371
        372
        373/**
        374 * Resets the test case pointer, so that next returns the first test.
        375 */
        376goog.testing.TestCase.prototype.reset = function() {
        377 this.currentTestPointer_ = 0;
        378 this.result_ = new goog.testing.TestCase.Result(this);
        379};
        380
        381
        382/**
        383 * Sets the callback function that should be executed when the tests have
        384 * completed.
        385 * @param {Function} fn The callback function.
        386 */
        387goog.testing.TestCase.prototype.setCompletedCallback = function(fn) {
        388 this.onCompleteCallback_ = fn;
        389};
        390
        391
        392/**
        393 * @param {goog.testing.TestCase.Order} order The sort order for running tests.
        394 */
        395goog.testing.TestCase.prototype.setOrder = function(order) {
        396 this.order = order;
        397};
        398
        399
        400/**
        401 * @param {Object<string, boolean>} testsToRun Set of tests to run. Entries in
        402 * the set may be test names, like "testFoo", or numeric indicies. Only
        403 * tests identified by name or by index will be executed.
        404 */
        405goog.testing.TestCase.prototype.setTestsToRun = function(testsToRun) {
        406 this.testsToRun_ = testsToRun;
        407};
        408
        409
        410/**
        411 * Can be overridden in test classes to indicate whether the tests in a case
        412 * should be run in that particular situation. For example, this could be used
        413 * to stop tests running in a particular browser, where browser support for
        414 * the class under test was absent.
        415 * @return {boolean} Whether any of the tests in the case should be run.
        416 */
        417goog.testing.TestCase.prototype.shouldRunTests = function() {
        418 return true;
        419};
        420
        421
        422/**
        423 * Executes the tests, yielding asynchronously if execution time exceeds
        424 * {@link maxRunTime}. There is no guarantee that the test case has finished
        425 * once this method has returned. To be notified when the test case
        426 * has finished, use {@link #setCompletedCallback} or
        427 * {@link #runTestsReturningPromise}.
        428 */
        429goog.testing.TestCase.prototype.execute = function() {
        430 if (!this.prepareForRun_()) {
        431 return;
        432 }
        433 this.log('Starting tests: ' + this.name_);
        434 this.cycleTests();
        435};
        436
        437
        438/**
        439 * Sets up the internal state of the test case for a run.
        440 * @return {boolean} If false, preparation failed because the test case
        441 * is not supposed to run in the present environment.
        442 * @private
        443 */
        444goog.testing.TestCase.prototype.prepareForRun_ = function() {
        445 this.started = true;
        446 this.reset();
        447 this.startTime_ = this.now();
        448 this.running = true;
        449 this.result_.totalCount = this.getCount();
        450 if (!this.shouldRunTests()) {
        451 this.log('shouldRunTests() returned false, skipping these tests.');
        452 this.result_.testSuppressed = true;
        453 this.finalize();
        454 return false;
        455 }
        456 return true;
        457};
        458
        459
        460/**
        461 * Finalizes the test case, called when the tests have finished executing.
        462 */
        463goog.testing.TestCase.prototype.finalize = function() {
        464 this.saveMessage('Done');
        465
        466 this.tearDownPage();
        467
        468 var restoredSetTimeout =
        469 goog.testing.TestCase.protectedSetTimeout_ == goog.global.setTimeout &&
        470 goog.testing.TestCase.protectedClearTimeout_ == goog.global.clearTimeout;
        471 if (!restoredSetTimeout && goog.testing.TestCase.IS_IE &&
        472 String(goog.global.setTimeout) ==
        473 goog.testing.TestCase.setTimeoutAsString_) {
        474 // In strange cases, IE's value of setTimeout *appears* to change, but
        475 // the string representation stays stable.
        476 restoredSetTimeout = true;
        477 }
        478
        479 if (!restoredSetTimeout) {
        480 var message = 'ERROR: Test did not restore setTimeout and clearTimeout';
        481 this.saveMessage(message);
        482 var err = new goog.testing.TestCase.Error(this.name_, message);
        483 this.result_.errors.push(err);
        484 }
        485 goog.global.clearTimeout = goog.testing.TestCase.protectedClearTimeout_;
        486 goog.global.setTimeout = goog.testing.TestCase.protectedSetTimeout_;
        487 this.endTime_ = this.now();
        488 this.running = false;
        489 this.result_.runTime = this.endTime_ - this.startTime_;
        490 this.result_.numFilesLoaded = this.countNumFilesLoaded_();
        491 this.result_.complete = true;
        492
        493 this.log(this.result_.getSummary());
        494 if (this.result_.isSuccess()) {
        495 this.log('Tests complete');
        496 } else {
        497 this.log('Tests Failed');
        498 }
        499 if (this.onCompleteCallback_) {
        500 var fn = this.onCompleteCallback_;
        501 // Execute's the completed callback in the context of the global object.
        502 fn();
        503 this.onCompleteCallback_ = null;
        504 }
        505};
        506
        507
        508/**
        509 * Saves a message to the result set.
        510 * @param {string} message The message to save.
        511 */
        512goog.testing.TestCase.prototype.saveMessage = function(message) {
        513 this.result_.messages.push(this.getTimeStamp_() + ' ' + message);
        514};
        515
        516
        517/**
        518 * @return {boolean} Whether the test case is running inside the multi test
        519 * runner.
        520 */
        521goog.testing.TestCase.prototype.isInsideMultiTestRunner = function() {
        522 var top = goog.global['top'];
        523 return top && typeof top['_allTests'] != 'undefined';
        524};
        525
        526
        527/**
        528 * Logs an object to the console, if available.
        529 * @param {*} val The value to log. Will be ToString'd.
        530 */
        531goog.testing.TestCase.prototype.log = function(val) {
        532 if (!this.isInsideMultiTestRunner() && goog.global.console) {
        533 if (typeof val == 'string') {
        534 val = this.getTimeStamp_() + ' : ' + val;
        535 }
        536 if (val instanceof Error && val.stack) {
        537 // Chrome does console.log asynchronously in a different process
        538 // (http://code.google.com/p/chromium/issues/detail?id=50316).
        539 // This is an acute problem for Errors, which almost never survive.
        540 // Grab references to the immutable strings so they survive.
        541 goog.global.console.log(val, val.message, val.stack);
        542 // TODO(gboyer): Consider for Chrome cloning any object if we can ensure
        543 // there are no circular references.
        544 } else {
        545 goog.global.console.log(val);
        546 }
        547 }
        548};
        549
        550
        551/**
        552 * @return {boolean} Whether the test was a success.
        553 */
        554goog.testing.TestCase.prototype.isSuccess = function() {
        555 return !!this.result_ && this.result_.isSuccess();
        556};
        557
        558
        559/**
        560 * Returns a string detailing the results from the test.
        561 * @param {boolean=} opt_verbose If true results will include data about all
        562 * tests, not just what failed.
        563 * @return {string} The results from the test.
        564 */
        565goog.testing.TestCase.prototype.getReport = function(opt_verbose) {
        566 var rv = [];
        567
        568 if (this.running) {
        569 rv.push(this.name_ + ' [RUNNING]');
        570 } else {
        571 var label = this.result_.isSuccess() ? 'PASSED' : 'FAILED';
        572 rv.push(this.name_ + ' [' + label + ']');
        573 }
        574
        575 if (goog.global.location) {
        576 rv.push(this.trimPath_(goog.global.location.href));
        577 }
        578
        579 rv.push(this.result_.getSummary());
        580
        581 if (opt_verbose) {
        582 rv.push('.', this.result_.messages.join('\n'));
        583 } else if (!this.result_.isSuccess()) {
        584 rv.push(this.result_.errors.join('\n'));
        585 }
        586
        587 rv.push(' ');
        588
        589 return rv.join('\n');
        590};
        591
        592
        593/**
        594 * Returns the test results.
        595 * @return {!goog.testing.TestCase.Result}
        596 * @package
        597 */
        598goog.testing.TestCase.prototype.getResult = function() {
        599 return this.result_;
        600};
        601
        602
        603/**
        604 * Returns the amount of time it took for the test to run.
        605 * @return {number} The run time, in milliseconds.
        606 */
        607goog.testing.TestCase.prototype.getRunTime = function() {
        608 return this.result_.runTime;
        609};
        610
        611
        612/**
        613 * Returns the number of script files that were loaded in order to run the test.
        614 * @return {number} The number of script files.
        615 */
        616goog.testing.TestCase.prototype.getNumFilesLoaded = function() {
        617 return this.result_.numFilesLoaded;
        618};
        619
        620
        621/**
        622 * Returns the test results object: a map from test names to a list of test
        623 * failures (if any exist).
        624 * @return {!Object<string, !Array<string>>} Tests results object.
        625 */
        626goog.testing.TestCase.prototype.getTestResults = function() {
        627 return this.result_.resultsByName;
        628};
        629
        630
        631/**
        632 * Executes each of the tests, yielding asynchronously if execution time
        633 * exceeds {@link #maxRunTime}. There is no guarantee that the test case
        634 * has finished execution once this method has returned.
        635 * To be notified when the test case has finished execution, use
        636 * {@link #setCompletedCallback} or {@link #runTestsReturningPromise}.
        637 *
        638 * Overridable by the individual test case. This allows test cases to defer
        639 * when the test is actually started. If overridden, finalize must be called
        640 * by the test to indicate it has finished.
        641 */
        642goog.testing.TestCase.prototype.runTests = function() {
        643 try {
        644 this.setUpPage();
        645 } catch (e) {
        646 this.exceptionBeforeTest = e;
        647 }
        648 this.execute();
        649};
        650
        651
        652/**
        653 * Executes each of the tests, returning a promise that resolves with the
        654 * test results once they are done running.
        655 * @return {!IThenable.<!goog.testing.TestCase.Result>}
        656 * @final
        657 * @package
        658 */
        659goog.testing.TestCase.prototype.runTestsReturningPromise = function() {
        660 try {
        661 this.setUpPage();
        662 } catch (e) {
        663 this.exceptionBeforeTest = e;
        664 }
        665 if (!this.prepareForRun_()) {
        666 return goog.Promise.resolve(this.result_);
        667 }
        668 this.log('Starting tests: ' + this.name_);
        669 this.saveMessage('Start');
        670 this.batchTime_ = this.now();
        671 return new goog.Promise(function(resolve) {
        672 this.runNextTestCallback_ = resolve;
        673 this.runNextTest_();
        674 }, this);
        675};
        676
        677
        678/**
        679 * Executes the next test method synchronously or with promises, depending on
        680 * the test method's return value.
        681 *
        682 * If the test method returns a promise, the next test method will run once
        683 * the promise is resolved or rejected. If the test method does not
        684 * return a promise, it is assumed to be synchronous, and execution proceeds
        685 * immediately to the next test method. This means that test cases can run
        686 * partially synchronously and partially asynchronously, depending on
        687 * the return values of their test methods. In particular, a test case
        688 * executes synchronously until the first promise is returned from a
        689 * test method (or until a resource limit is reached; see
        690 * {@link finishTestInvocation_}).
        691 * @private
        692 */
        693goog.testing.TestCase.prototype.runNextTest_ = function() {
        694 this.curTest_ = this.next();
        695 if (!this.curTest_ || !this.running) {
        696 this.finalize();
        697 this.runNextTestCallback_(this.result_);
        698 return;
        699 }
        700 this.result_.runCount++;
        701 this.log('Running test: ' + this.curTest_.name);
        702 if (this.maybeFailTestEarly(this.curTest_)) {
        703 this.finishTestInvocation_();
        704 return;
        705 }
        706 goog.testing.TestCase.currentTestName = this.curTest_.name;
        707 this.invokeTestFunction_(
        708 this.setUp, this.safeRunTest_, this.safeTearDown_);
        709};
        710
        711
        712/**
        713 * Calls the given test function, handling errors appropriately.
        714 * @private
        715 */
        716goog.testing.TestCase.prototype.safeRunTest_ = function() {
        717 this.invokeTestFunction_(
        718 goog.bind(this.curTest_.ref, this.curTest_.scope),
        719 this.safeTearDown_,
        720 this.safeTearDown_);
        721};
        722
        723
        724/**
        725 * Calls {@link tearDown}, handling errors appropriately.
        726 * @param {*=} opt_error Error associated with the test, if any.
        727 * @private
        728 */
        729goog.testing.TestCase.prototype.safeTearDown_ = function(opt_error) {
        730 if (arguments.length == 1) {
        731 this.doError(this.curTest_, opt_error);
        732 }
        733 this.invokeTestFunction_(
        734 this.tearDown, this.finishTestInvocation_, this.finishTestInvocation_);
        735};
        736
        737
        738/**
        739 * Calls the given {@code fn}, then calls either {@code onSuccess} or
        740 * {@code onFailure}, either synchronously or using promises, depending on
        741 * {@code fn}'s return value.
        742 *
        743 * If {@code fn} throws an exception, {@code onFailure} is called immediately
        744 * with the exception.
        745 *
        746 * If {@code fn} returns a promise, and the promise is eventually resolved,
        747 * {@code onSuccess} is called with no arguments. If the promise is eventually
        748 * rejected, {@code onFailure} is called with the rejection reason.
        749 *
        750 * Otherwise, if {@code fn} neither returns a promise nor throws an exception,
        751 * {@code onSuccess} is called immediately with no arguments.
        752 *
        753 * {@code fn}, {@code onSuccess}, and {@code onFailure} are all called with
        754 * the TestCase instance as the method receiver.
        755 *
        756 * @param {function()} fn The function to call.
        757 * @param {function()} onSuccess Success callback.
        758 * @param {function(*)} onFailure Failure callback.
        759 * @private
        760 */
        761goog.testing.TestCase.prototype.invokeTestFunction_ = function(
        762 fn, onSuccess, onFailure) {
        763 try {
        764 var retval = fn.call(this);
        765 if (goog.Thenable.isImplementedBy(retval) ||
        766 goog.isFunction(retval && retval['then'])) {
        767 var self = this;
        768 retval.then(
        769 function() {
        770 self.resetBatchTimeAfterPromise_();
        771 onSuccess.call(self);
        772 },
        773 function(e) {
        774 self.resetBatchTimeAfterPromise_();
        775 onFailure.call(self, e);
        776 });
        777 } else {
        778 onSuccess.call(this);
        779 }
        780 } catch (e) {
        781 onFailure.call(this, e);
        782 }
        783};
        784
        785
        786/**
        787 * Resets the batch run timer. This should only be called after resolving a
        788 * promise since Promise.then() has an implicit yield.
        789 * @private
        790 */
        791goog.testing.TestCase.prototype.resetBatchTimeAfterPromise_ = function() {
        792 this.batchTime_ = this.now();
        793};
        794
        795
        796/**
        797 * Finishes up bookkeeping for the current test function, and schedules
        798 * the next test function to run, either immediately or asychronously.
        799 * @param {*=} opt_error Optional error resulting from the test invocation.
        800 * @private
        801 */
        802goog.testing.TestCase.prototype.finishTestInvocation_ = function(opt_error) {
        803 if (arguments.length == 1) {
        804 this.doError(this.curTest_, opt_error);
        805 }
        806
        807 // If no errors have been recorded for the test, it is a success.
        808 if (!(this.curTest_.name in this.result_.resultsByName) ||
        809 !this.result_.resultsByName[this.curTest_.name].length) {
        810 this.doSuccess(this.curTest_);
        811 }
        812
        813 goog.testing.TestCase.currentTestName = null;
        814
        815 // If the test case has consumed too much time or stack space,
        816 // yield to avoid blocking the browser. Otherwise, proceed to the next test.
        817 if (this.depth_ > goog.testing.TestCase.MAX_STACK_DEPTH_ ||
        818 this.now() - this.batchTime_ > goog.testing.TestCase.maxRunTime) {
        819 this.saveMessage('Breaking async');
        820 this.timeout(goog.bind(this.startNextBatch_, this), 0);
        821 } else {
        822 ++this.depth_;
        823 this.runNextTest_();
        824 }
        825};
        826
        827
        828/**
        829 * Start a new batch to tests after yielding, resetting batchTime and depth.
        830 * @private
        831 */
        832goog.testing.TestCase.prototype.startNextBatch_ = function() {
        833 this.batchTime_ = this.now();
        834 this.depth_ = 0;
        835 this.runNextTest_();
        836};
        837
        838
        839/**
        840 * Reorders the tests depending on the {@code order} field.
        841 * @private
        842 */
        843goog.testing.TestCase.prototype.orderTests_ = function() {
        844 switch (this.order) {
        845 case goog.testing.TestCase.Order.RANDOM:
        846 // Fisher-Yates shuffle
        847 var i = this.tests_.length;
        848 while (i > 1) {
        849 // goog.math.randomInt is inlined to reduce dependencies.
        850 var j = Math.floor(Math.random() * i); // exclusive
        851 i--;
        852 var tmp = this.tests_[i];
        853 this.tests_[i] = this.tests_[j];
        854 this.tests_[j] = tmp;
        855 }
        856 break;
        857
        858 case goog.testing.TestCase.Order.SORTED:
        859 this.tests_.sort(function(t1, t2) {
        860 if (t1.name == t2.name) {
        861 return 0;
        862 }
        863 return t1.name < t2.name ? -1 : 1;
        864 });
        865 break;
        866
        867 // Do nothing for NATURAL.
        868 }
        869};
        870
        871
        872/**
        873 * Gets list of objects that potentially contain test cases. For IE 8 and below,
        874 * this is the global "this" (for properties set directly on the global this or
        875 * window) and the RuntimeObject (for global variables and functions). For all
        876 * other browsers, the array simply contains the global this.
        877 *
        878 * @param {string=} opt_prefix An optional prefix. If specified, only get things
        879 * under this prefix. Note that the prefix is only honored in IE, since it
        880 * supports the RuntimeObject:
        881 * http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx
        882 * TODO: Remove this option.
        883 * @return {!Array<!Object>} A list of objects that should be inspected.
        884 */
        885goog.testing.TestCase.prototype.getGlobals = function(opt_prefix) {
        886 return goog.testing.TestCase.getGlobals(opt_prefix);
        887};
        888
        889
        890/**
        891 * Gets list of objects that potentially contain test cases. For IE 8 and below,
        892 * this is the global "this" (for properties set directly on the global this or
        893 * window) and the RuntimeObject (for global variables and functions). For all
        894 * other browsers, the array simply contains the global this.
        895 *
        896 * @param {string=} opt_prefix An optional prefix. If specified, only get things
        897 * under this prefix. Note that the prefix is only honored in IE, since it
        898 * supports the RuntimeObject:
        899 * http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx
        900 * TODO: Remove this option.
        901 * @return {!Array<!Object>} A list of objects that should be inspected.
        902 */
        903goog.testing.TestCase.getGlobals = function(opt_prefix) {
        904 // Look in the global scope for most browsers, on IE we use the little known
        905 // RuntimeObject which holds references to all globals. We reference this
        906 // via goog.global so that there isn't an aliasing that throws an exception
        907 // in Firefox.
        908 return typeof goog.global['RuntimeObject'] != 'undefined' ?
        909 [goog.global['RuntimeObject']((opt_prefix || '') + '*'), goog.global] :
        910 [goog.global];
        911};
        912
        913
        914/**
        915 * Gets called before any tests are executed. Can be overridden to set up the
        916 * environment for the whole test case.
        917 */
        918goog.testing.TestCase.prototype.setUpPage = function() {};
        919
        920
        921/**
        922 * Gets called after all tests have been executed. Can be overridden to tear
        923 * down the entire test case.
        924 */
        925goog.testing.TestCase.prototype.tearDownPage = function() {};
        926
        927
        928/**
        929 * Gets called before every goog.testing.TestCase.Test is been executed. Can be
        930 * overridden to add set up functionality to each test.
        931 */
        932goog.testing.TestCase.prototype.setUp = function() {};
        933
        934
        935/**
        936 * Gets called after every goog.testing.TestCase.Test has been executed. Can be
        937 * overriden to add tear down functionality to each test.
        938 */
        939goog.testing.TestCase.prototype.tearDown = function() {};
        940
        941
        942/**
        943 * @return {string} The function name prefix used to auto-discover tests.
        944 */
        945goog.testing.TestCase.prototype.getAutoDiscoveryPrefix = function() {
        946 return 'test';
        947};
        948
        949
        950/**
        951 * @return {number} Time since the last batch of tests was started.
        952 * @protected
        953 */
        954goog.testing.TestCase.prototype.getBatchTime = function() {
        955 return this.batchTime_;
        956};
        957
        958
        959/**
        960 * @param {number} batchTime Time since the last batch of tests was started.
        961 * @protected
        962 */
        963goog.testing.TestCase.prototype.setBatchTime = function(batchTime) {
        964 this.batchTime_ = batchTime;
        965};
        966
        967
        968/**
        969 * Creates a {@code goog.testing.TestCase.Test} from an auto-discovered
        970 * function.
        971 * @param {string} name The name of the function.
        972 * @param {function() : void} ref The auto-discovered function.
        973 * @return {!goog.testing.TestCase.Test} The newly created test.
        974 * @protected
        975 */
        976goog.testing.TestCase.prototype.createTestFromAutoDiscoveredFunction =
        977 function(name, ref) {
        978 return new goog.testing.TestCase.Test(name, ref, goog.global);
        979};
        980
        981
        982/**
        983 * Adds any functions defined on 'obj' (the global object, by default)
        984 * that correspond to lifecycle events for the test case. Overrides
        985 * setUp, tearDown, setUpPage, tearDownPage, runTests, and shouldRunTests
        986 * if they are defined on 'obj'.
        987 * @param {!Object=} opt_obj Defaults to goog.global.
        988 */
        989goog.testing.TestCase.prototype.autoDiscoverLifecycle = function(opt_obj) {
        990 var obj = opt_obj || goog.global;
        991 if (obj['setUp']) {
        992 this.setUp = goog.bind(obj['setUp'], obj);
        993 }
        994 if (obj['tearDown']) {
        995 this.tearDown = goog.bind(obj['tearDown'], obj);
        996 }
        997 if (obj['setUpPage']) {
        998 this.setUpPage = goog.bind(obj['setUpPage'], obj);
        999 }
        1000 if (obj['tearDownPage']) {
        1001 this.tearDownPage = goog.bind(obj['tearDownPage'], obj);
        1002 }
        1003 if (obj['runTests']) {
        1004 this.runTests = goog.bind(obj['runTests'], obj);
        1005 }
        1006 if (obj['shouldRunTests']) {
        1007 this.shouldRunTests = goog.bind(obj['shouldRunTests'], obj);
        1008 }
        1009};
        1010
        1011
        1012// TODO(johnlenz): make this package private
        1013/**
        1014 * @param {!Object} obj An object from which to extract test and lifecycle
        1015 * methods.
        1016 */
        1017goog.testing.TestCase.prototype.setTestObj = function(obj) {
        1018 // Drop any previously added (likely auto-discovered) tests, only one source
        1019 // of discovered test and life-cycle methods is allowed.
        1020 goog.asserts.assert(this.tests_.length == 0,
        1021 'Test methods have already been configured.');
        1022
        1023 var regex = new RegExp('^' + this.getAutoDiscoveryPrefix());
        1024 for (var name in obj) {
        1025 if (regex.test(name)) {
        1026 var testMethod = obj[name];
        1027 if (goog.isFunction(testMethod)) {
        1028 this.addNewTest(name, testMethod, obj);
        1029 }
        1030 }
        1031 }
        1032
        1033 this.autoDiscoverLifecycle(obj);
        1034};
        1035
        1036
        1037/**
        1038 * Adds any functions defined in the global scope that are prefixed with "test"
        1039 * to the test case.
        1040 */
        1041goog.testing.TestCase.prototype.autoDiscoverTests = function() {
        1042 var prefix = this.getAutoDiscoveryPrefix();
        1043 var testSources = this.getGlobals(prefix);
        1044
        1045 var foundTests = [];
        1046
        1047 for (var i = 0; i < testSources.length; i++) {
        1048 var testSource = testSources[i];
        1049 for (var name in testSource) {
        1050 if ((new RegExp('^' + prefix)).test(name)) {
        1051 var ref;
        1052 try {
        1053 ref = testSource[name];
        1054 } catch (ex) {
        1055 // NOTE(brenneman): When running tests from a file:// URL on Firefox
        1056 // 3.5 for Windows, any reference to goog.global.sessionStorage raises
        1057 // an "Operation is not supported" exception. Ignore any exceptions
        1058 // raised by simply accessing global properties.
        1059 ref = undefined;
        1060 }
        1061
        1062 if (goog.isFunction(ref)) {
        1063 foundTests.push(this.createTestFromAutoDiscoveredFunction(name, ref));
        1064 }
        1065 }
        1066 }
        1067 }
        1068
        1069 for (var i = 0; i < foundTests.length; i++) {
        1070 this.add(foundTests[i]);
        1071 }
        1072 this.orderTests_();
        1073
        1074 this.log(this.getCount() + ' tests auto-discovered');
        1075
        1076 // TODO(user): Do this as a separate call. Unfortunately, a lot of projects
        1077 // currently override autoDiscoverTests and expect lifecycle events to be
        1078 // registered as a part of this call.
        1079 this.autoDiscoverLifecycle();
        1080};
        1081
        1082
        1083/**
        1084 * Checks to see if the test should be marked as failed before it is run.
        1085 *
        1086 * If there was an error in setUpPage, we treat that as a failure for all tests
        1087 * and mark them all as having failed.
        1088 *
        1089 * @param {goog.testing.TestCase.Test} testCase The current test case.
        1090 * @return {boolean} Whether the test was marked as failed.
        1091 * @protected
        1092 */
        1093goog.testing.TestCase.prototype.maybeFailTestEarly = function(testCase) {
        1094 if (this.exceptionBeforeTest) {
        1095 // We just use the first error to report an error on a failed test.
        1096 this.curTest_.name = 'setUpPage for ' + this.curTest_.name;
        1097 this.doError(this.curTest_, this.exceptionBeforeTest);
        1098 return true;
        1099 }
        1100 return false;
        1101};
        1102
        1103
        1104/**
        1105 * Cycles through the tests, yielding asynchronously if the execution time
        1106 * execeeds {@link #maxRunTime}. In particular, there is no guarantee that
        1107 * the test case has finished execution once this method has returned.
        1108 * To be notified when the test case has finished execution, use
        1109 * {@link #setCompletedCallback} or {@link #runTestsReturningPromise}.
        1110 */
        1111goog.testing.TestCase.prototype.cycleTests = function() {
        1112 this.saveMessage('Start');
        1113 this.batchTime_ = this.now();
        1114 if (this.running) {
        1115 this.runNextTestCallback_ = goog.nullFunction;
        1116 // Kick off the tests. runNextTest_ will schedule all of the tests,
        1117 // using a mixture of synchronous and asynchronous strategies.
        1118 this.runNextTest_();
        1119 }
        1120};
        1121
        1122
        1123/**
        1124 * Counts the number of files that were loaded for dependencies that are
        1125 * required to run the test.
        1126 * @return {number} The number of files loaded.
        1127 * @private
        1128 */
        1129goog.testing.TestCase.prototype.countNumFilesLoaded_ = function() {
        1130 var scripts = document.getElementsByTagName(goog.dom.TagName.SCRIPT);
        1131 var count = 0;
        1132 for (var i = 0, n = scripts.length; i < n; i++) {
        1133 if (scripts[i].src) {
        1134 count++;
        1135 }
        1136 }
        1137 return count;
        1138};
        1139
        1140
        1141/**
        1142 * Calls a function after a delay, using the protected timeout.
        1143 * @param {Function} fn The function to call.
        1144 * @param {number} time Delay in milliseconds.
        1145 * @return {number} The timeout id.
        1146 * @protected
        1147 */
        1148goog.testing.TestCase.prototype.timeout = function(fn, time) {
        1149 // NOTE: invoking protectedSetTimeout_ as a member of goog.testing.TestCase
        1150 // would result in an Illegal Invocation error. The method must be executed
        1151 // with the global context.
        1152 var protectedSetTimeout = goog.testing.TestCase.protectedSetTimeout_;
        1153 return protectedSetTimeout(fn, time);
        1154};
        1155
        1156
        1157/**
        1158 * Clears a timeout created by {@code this.timeout()}.
        1159 * @param {number} id A timeout id.
        1160 * @protected
        1161 */
        1162goog.testing.TestCase.prototype.clearTimeout = function(id) {
        1163 // NOTE: see execution note for protectedSetTimeout above.
        1164 var protectedClearTimeout = goog.testing.TestCase.protectedClearTimeout_;
        1165 protectedClearTimeout(id);
        1166};
        1167
        1168
        1169/**
        1170 * @return {number} The current time in milliseconds, don't use goog.now as some
        1171 * tests override it.
        1172 * @protected
        1173 */
        1174goog.testing.TestCase.prototype.now = function() {
        1175 // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.
        1176 var protectedDate = goog.testing.TestCase.protectedDate_;
        1177 return new protectedDate().getTime();
        1178};
        1179
        1180
        1181/**
        1182 * Returns the current time.
        1183 * @return {string} HH:MM:SS.
        1184 * @private
        1185 */
        1186goog.testing.TestCase.prototype.getTimeStamp_ = function() {
        1187 // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.
        1188 var protectedDate = goog.testing.TestCase.protectedDate_;
        1189 var d = new protectedDate();
        1190
        1191 // Ensure millis are always 3-digits
        1192 var millis = '00' + d.getMilliseconds();
        1193 millis = millis.substr(millis.length - 3);
        1194
        1195 return this.pad_(d.getHours()) + ':' + this.pad_(d.getMinutes()) + ':' +
        1196 this.pad_(d.getSeconds()) + '.' + millis;
        1197};
        1198
        1199
        1200/**
        1201 * Pads a number to make it have a leading zero if it's less than 10.
        1202 * @param {number} number The number to pad.
        1203 * @return {string} The resulting string.
        1204 * @private
        1205 */
        1206goog.testing.TestCase.prototype.pad_ = function(number) {
        1207 return number < 10 ? '0' + number : String(number);
        1208};
        1209
        1210
        1211/**
        1212 * Trims a path to be only that after google3.
        1213 * @param {string} path The path to trim.
        1214 * @return {string} The resulting string.
        1215 * @private
        1216 */
        1217goog.testing.TestCase.prototype.trimPath_ = function(path) {
        1218 return path.substring(path.indexOf('google3') + 8);
        1219};
        1220
        1221
        1222/**
        1223 * Handles a test that passed.
        1224 * @param {goog.testing.TestCase.Test} test The test that passed.
        1225 * @protected
        1226 */
        1227goog.testing.TestCase.prototype.doSuccess = function(test) {
        1228 this.result_.successCount++;
        1229 // An empty list of error messages indicates that the test passed.
        1230 // If we already have a failure for this test, do not set to empty list.
        1231 if (!(test.name in this.result_.resultsByName)) {
        1232 this.result_.resultsByName[test.name] = [];
        1233 }
        1234 var message = test.name + ' : PASSED';
        1235 this.saveMessage(message);
        1236 this.log(message);
        1237};
        1238
        1239
        1240/**
        1241 * Handles a test that failed.
        1242 * @param {goog.testing.TestCase.Test} test The test that failed.
        1243 * @param {*=} opt_e The exception object associated with the
        1244 * failure or a string.
        1245 * @protected
        1246 */
        1247goog.testing.TestCase.prototype.doError = function(test, opt_e) {
        1248 var message = test.name + ' : FAILED';
        1249 this.log(message);
        1250 this.saveMessage(message);
        1251 var err = this.logError(test.name, opt_e);
        1252 this.result_.errors.push(err);
        1253 if (test.name in this.result_.resultsByName) {
        1254 this.result_.resultsByName[test.name].push(err.toString());
        1255 } else {
        1256 this.result_.resultsByName[test.name] = [err.toString()];
        1257 }
        1258};
        1259
        1260
        1261/**
        1262 * @param {string} name Failed test name.
        1263 * @param {*=} opt_e The exception object associated with the
        1264 * failure or a string.
        1265 * @return {!goog.testing.TestCase.Error} Error object.
        1266 */
        1267goog.testing.TestCase.prototype.logError = function(name, opt_e) {
        1268 var errMsg = null;
        1269 var stack = null;
        1270 if (opt_e) {
        1271 this.log(opt_e);
        1272 if (goog.isString(opt_e)) {
        1273 errMsg = opt_e;
        1274 } else {
        1275 errMsg = opt_e.message || opt_e.description || opt_e.toString();
        1276 stack = opt_e.stack ? goog.testing.stacktrace.canonicalize(opt_e.stack) :
        1277 opt_e['stackTrace'];
        1278 }
        1279 } else {
        1280 errMsg = 'An unknown error occurred';
        1281 }
        1282 var err = new goog.testing.TestCase.Error(name, errMsg, stack);
        1283
        1284 // Avoid double logging.
        1285 if (!opt_e || !opt_e['isJsUnitException'] ||
        1286 !opt_e['loggedJsUnitException']) {
        1287 this.saveMessage(err.toString());
        1288 }
        1289 if (opt_e && opt_e['isJsUnitException']) {
        1290 opt_e['loggedJsUnitException'] = true;
        1291 }
        1292
        1293 return err;
        1294};
        1295
        1296
        1297
        1298/**
        1299 * A class representing a single test function.
        1300 * @param {string} name The test name.
        1301 * @param {Function} ref Reference to the test function.
        1302 * @param {Object=} opt_scope Optional scope that the test function should be
        1303 * called in.
        1304 * @constructor
        1305 */
        1306goog.testing.TestCase.Test = function(name, ref, opt_scope) {
        1307 /**
        1308 * The name of the test.
        1309 * @type {string}
        1310 */
        1311 this.name = name;
        1312
        1313 /**
        1314 * Reference to the test function.
        1315 * @type {Function}
        1316 */
        1317 this.ref = ref;
        1318
        1319 /**
        1320 * Scope that the test function should be called in.
        1321 * @type {Object}
        1322 */
        1323 this.scope = opt_scope || null;
        1324};
        1325
        1326
        1327/**
        1328 * Executes the test function.
        1329 * @package
        1330 */
        1331goog.testing.TestCase.Test.prototype.execute = function() {
        1332 this.ref.call(this.scope);
        1333};
        1334
        1335
        1336
        1337/**
        1338 * A class for representing test results. A bag of public properties.
        1339 * @param {goog.testing.TestCase} testCase The test case that owns this result.
        1340 * @constructor
        1341 * @final
        1342 */
        1343goog.testing.TestCase.Result = function(testCase) {
        1344 /**
        1345 * The test case that owns this result.
        1346 * @type {goog.testing.TestCase}
        1347 * @private
        1348 */
        1349 this.testCase_ = testCase;
        1350
        1351 /**
        1352 * Total number of tests that should have been run.
        1353 * @type {number}
        1354 */
        1355 this.totalCount = 0;
        1356
        1357 /**
        1358 * Total number of tests that were actually run.
        1359 * @type {number}
        1360 */
        1361 this.runCount = 0;
        1362
        1363 /**
        1364 * Number of successful tests.
        1365 * @type {number}
        1366 */
        1367 this.successCount = 0;
        1368
        1369 /**
        1370 * The amount of time the tests took to run.
        1371 * @type {number}
        1372 */
        1373 this.runTime = 0;
        1374
        1375 /**
        1376 * The number of files loaded to run this test.
        1377 * @type {number}
        1378 */
        1379 this.numFilesLoaded = 0;
        1380
        1381 /**
        1382 * Whether this test case was suppressed by shouldRunTests() returning false.
        1383 * @type {boolean}
        1384 */
        1385 this.testSuppressed = false;
        1386
        1387 /**
        1388 * Test results for each test that was run. The test name is always added
        1389 * as the key in the map, and the array of strings is an optional list
        1390 * of failure messages. If the array is empty, the test passed. Otherwise,
        1391 * the test failed.
        1392 * @type {!Object<string, !Array<string>>}
        1393 */
        1394 this.resultsByName = {};
        1395
        1396 /**
        1397 * Errors encountered while running the test.
        1398 * @type {!Array<goog.testing.TestCase.Error>}
        1399 */
        1400 this.errors = [];
        1401
        1402 /**
        1403 * Messages to show the user after running the test.
        1404 * @type {!Array<string>}
        1405 */
        1406 this.messages = [];
        1407
        1408 /**
        1409 * Whether the tests have completed.
        1410 * @type {boolean}
        1411 */
        1412 this.complete = false;
        1413};
        1414
        1415
        1416/**
        1417 * @return {boolean} Whether the test was successful.
        1418 */
        1419goog.testing.TestCase.Result.prototype.isSuccess = function() {
        1420 return this.complete && this.errors.length == 0;
        1421};
        1422
        1423
        1424/**
        1425 * @return {string} A summary of the tests, including total number of tests that
        1426 * passed, failed, and the time taken.
        1427 */
        1428goog.testing.TestCase.Result.prototype.getSummary = function() {
        1429 var summary = this.runCount + ' of ' + this.totalCount + ' tests run in ' +
        1430 this.runTime + 'ms.\n';
        1431 if (this.testSuppressed) {
        1432 summary += 'Tests not run because shouldRunTests() returned false.';
        1433 } else {
        1434 var failures = this.totalCount - this.successCount;
        1435 var suppressionMessage = '';
        1436
        1437 var countOfRunTests = this.testCase_.getActuallyRunCount();
        1438 if (countOfRunTests) {
        1439 failures = countOfRunTests - this.successCount;
        1440 suppressionMessage = ', ' +
        1441 (this.totalCount - countOfRunTests) + ' suppressed by querystring';
        1442 }
        1443 summary += this.successCount + ' passed, ' +
        1444 failures + ' failed' + suppressionMessage + '.\n' +
        1445 Math.round(this.runTime / this.runCount) + ' ms/test. ' +
        1446 this.numFilesLoaded + ' files loaded.';
        1447 }
        1448
        1449 return summary;
        1450};
        1451
        1452
        1453/**
        1454 * Initializes the given test case with the global test runner 'G_testRunner'.
        1455 * @param {goog.testing.TestCase} testCase The test case to install.
        1456 */
        1457goog.testing.TestCase.initializeTestRunner = function(testCase) {
        1458 testCase.autoDiscoverTests();
        1459
        1460 if (goog.global.location) {
        1461 var search = goog.global.location.search;
        1462 testCase.setOrder(goog.testing.TestCase.parseOrder_(search) ||
        1463 goog.testing.TestCase.Order.SORTED);
        1464 testCase.setTestsToRun(goog.testing.TestCase.parseRunTests_(search));
        1465 }
        1466
        1467 var gTestRunner = goog.global['G_testRunner'];
        1468 if (gTestRunner) {
        1469 gTestRunner['initialize'](testCase);
        1470 } else {
        1471 throw Error('G_testRunner is undefined. Please ensure goog.testing.jsunit' +
        1472 ' is included.');
        1473 }
        1474};
        1475
        1476
        1477/**
        1478 * Parses URL query parameters for the 'order' parameter.
        1479 * @param {string} search The URL query string.
        1480 * @return {?goog.testing.TestCase.Order} The sort order for running tests.
        1481 * @private
        1482 */
        1483goog.testing.TestCase.parseOrder_ = function(search) {
        1484 var order = null;
        1485 var orderMatch = search.match(
        1486 /(?:\?|&)order=(natural|random|sorted)/i);
        1487 if (orderMatch) {
        1488 order = /** @type {goog.testing.TestCase.Order} */ (
        1489 orderMatch[1].toLowerCase());
        1490 }
        1491 return order;
        1492};
        1493
        1494
        1495/**
        1496 * Parses URL query parameters for the 'runTests' parameter.
        1497 * @param {string} search The URL query string.
        1498 * @return {Object<string, boolean>} A set of test names or test indices to be
        1499 * run by the test runner.
        1500 * @private
        1501 */
        1502goog.testing.TestCase.parseRunTests_ = function(search) {
        1503 var testsToRun = null;
        1504 var runTestsMatch = search.match(/(?:\?|&)runTests=([^?&]+)/i);
        1505 if (runTestsMatch) {
        1506 testsToRun = {};
        1507 var arr = runTestsMatch[1].split(',');
        1508 for (var i = 0, len = arr.length; i < len; i++) {
        1509 testsToRun[arr[i]] = true;
        1510 }
        1511 }
        1512 return testsToRun;
        1513};
        1514
        1515
        1516
        1517/**
        1518 * A class representing an error thrown by the test
        1519 * @param {string} source The name of the test which threw the error.
        1520 * @param {string} message The error message.
        1521 * @param {string=} opt_stack A string showing the execution stack.
        1522 * @constructor
        1523 * @final
        1524 */
        1525goog.testing.TestCase.Error = function(source, message, opt_stack) {
        1526 /**
        1527 * The name of the test which threw the error.
        1528 * @type {string}
        1529 */
        1530 this.source = source;
        1531
        1532 /**
        1533 * Reference to the test function.
        1534 * @type {string}
        1535 */
        1536 this.message = message;
        1537
        1538 /**
        1539 * The stack.
        1540 * @type {?string}
        1541 */
        1542 this.stack = null;
        1543
        1544 if (opt_stack) {
        1545 this.stack = opt_stack;
        1546 } else {
        1547 // Attempt to capture a stack trace.
        1548 if (Error.captureStackTrace) {
        1549 // See https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
        1550 Error.captureStackTrace(this, goog.testing.TestCase.Error);
        1551 } else {
        1552 var stack = new Error().stack;
        1553 if (stack) {
        1554 this.stack = stack;
        1555 }
        1556 }
        1557 }
        1558};
        1559
        1560
        1561/**
        1562 * Returns a string representing the error object.
        1563 * @return {string} A string representation of the error.
        1564 * @override
        1565 */
        1566goog.testing.TestCase.Error.prototype.toString = function() {
        1567 return 'ERROR in ' + this.source + '\n' +
        1568 this.message + (this.stack ? '\n' + this.stack : '');
        1569};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/testrunner.js.src.html b/docs/source/lib/goog/testing/testrunner.js.src.html new file mode 100644 index 0000000..8edf546 --- /dev/null +++ b/docs/source/lib/goog/testing/testrunner.js.src.html @@ -0,0 +1 @@ +testrunner.js

        lib/goog/testing/testrunner.js

        1// Copyright 2007 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The test runner is a singleton object that is used to execute
        17 * a goog.testing.TestCases, display the results, and expose the results to
        18 * Selenium for automation. If a TestCase hasn't been registered with the
        19 * runner by the time window.onload occurs, the testRunner will try to auto-
        20 * discover JsUnit style test pages.
        21 *
        22 * The hooks for selenium are (see http://go/selenium-hook-setup):-
        23 * - Boolean G_testRunner.isFinished()
        24 * - Boolean G_testRunner.isSuccess()
        25 * - String G_testRunner.getReport()
        26 * - number G_testRunner.getRunTime()
        27 * - Object<string, Array<string>> G_testRunner.getTestResults()
        28 *
        29 * Testing code should not have dependencies outside of goog.testing so as to
        30 * reduce the chance of masking missing dependencies.
        31 *
        32 */
        33
        34goog.provide('goog.testing.TestRunner');
        35
        36goog.require('goog.dom.TagName');
        37goog.require('goog.testing.TestCase');
        38
        39
        40
        41/**
        42 * Construct a test runner.
        43 *
        44 * NOTE(user): This is currently pretty weird, I'm essentially trying to
        45 * create a wrapper that the Selenium test can hook into to query the state of
        46 * the running test case, while making goog.testing.TestCase general.
        47 *
        48 * @constructor
        49 */
        50goog.testing.TestRunner = function() {
        51 /**
        52 * Errors that occurred in the window.
        53 * @type {Array<string>}
        54 */
        55 this.errors = [];
        56};
        57
        58
        59/**
        60 * Reference to the active test case.
        61 * @type {goog.testing.TestCase?}
        62 */
        63goog.testing.TestRunner.prototype.testCase = null;
        64
        65
        66/**
        67 * Whether the test runner has been initialized yet.
        68 * @type {boolean}
        69 */
        70goog.testing.TestRunner.prototype.initialized = false;
        71
        72
        73/**
        74 * Element created in the document to add test results to.
        75 * @type {Element}
        76 * @private
        77 */
        78goog.testing.TestRunner.prototype.logEl_ = null;
        79
        80
        81/**
        82 * Function to use when filtering errors.
        83 * @type {(function(string))?}
        84 * @private
        85 */
        86goog.testing.TestRunner.prototype.errorFilter_ = null;
        87
        88
        89/**
        90 * Whether an empty test case counts as an error.
        91 * @type {boolean}
        92 * @private
        93 */
        94goog.testing.TestRunner.prototype.strict_ = true;
        95
        96
        97/**
        98 * Initializes the test runner.
        99 * @param {goog.testing.TestCase} testCase The test case to initialize with.
        100 */
        101goog.testing.TestRunner.prototype.initialize = function(testCase) {
        102 if (this.testCase && this.testCase.running) {
        103 throw Error('The test runner is already waiting for a test to complete');
        104 }
        105 this.testCase = testCase;
        106 this.initialized = true;
        107};
        108
        109
        110/**
        111 * By default, the test runner is strict, and fails if it runs an empty
        112 * test case.
        113 * @param {boolean} strict Whether the test runner should fail on an empty
        114 * test case.
        115 */
        116goog.testing.TestRunner.prototype.setStrict = function(strict) {
        117 this.strict_ = strict;
        118};
        119
        120
        121/**
        122 * @return {boolean} Whether the test runner should fail on an empty
        123 * test case.
        124 */
        125goog.testing.TestRunner.prototype.isStrict = function() {
        126 return this.strict_;
        127};
        128
        129
        130/**
        131 * Returns true if the test runner is initialized.
        132 * Used by Selenium Hooks.
        133 * @return {boolean} Whether the test runner is active.
        134 */
        135goog.testing.TestRunner.prototype.isInitialized = function() {
        136 return this.initialized;
        137};
        138
        139
        140/**
        141 * Returns true if the test runner is finished.
        142 * Used by Selenium Hooks.
        143 * @return {boolean} Whether the test runner is active.
        144 */
        145goog.testing.TestRunner.prototype.isFinished = function() {
        146 return this.errors.length > 0 ||
        147 this.initialized && !!this.testCase && this.testCase.started &&
        148 !this.testCase.running;
        149};
        150
        151
        152/**
        153 * Returns true if the test case didn't fail.
        154 * Used by Selenium Hooks.
        155 * @return {boolean} Whether the current test returned successfully.
        156 */
        157goog.testing.TestRunner.prototype.isSuccess = function() {
        158 return !this.hasErrors() && !!this.testCase && this.testCase.isSuccess();
        159};
        160
        161
        162/**
        163 * Returns true if the test case runner has errors that were caught outside of
        164 * the test case.
        165 * @return {boolean} Whether there were JS errors.
        166 */
        167goog.testing.TestRunner.prototype.hasErrors = function() {
        168 return this.errors.length > 0;
        169};
        170
        171
        172/**
        173 * Logs an error that occurred. Used in the case of environment setting up
        174 * an onerror handler.
        175 * @param {string} msg Error message.
        176 */
        177goog.testing.TestRunner.prototype.logError = function(msg) {
        178 if (!this.errorFilter_ || this.errorFilter_.call(null, msg)) {
        179 this.errors.push(msg);
        180 }
        181};
        182
        183
        184/**
        185 * Log failure in current running test.
        186 * @param {Error} ex Exception.
        187 */
        188goog.testing.TestRunner.prototype.logTestFailure = function(ex) {
        189 var testName = /** @type {string} */ (goog.testing.TestCase.currentTestName);
        190 if (this.testCase) {
        191 this.testCase.logError(testName, ex);
        192 } else {
        193 // NOTE: Do not forget to log the original exception raised.
        194 throw new Error('Test runner not initialized with a test case. Original ' +
        195 'exception: ' + ex.message);
        196 }
        197};
        198
        199
        200/**
        201 * Sets a function to use as a filter for errors.
        202 * @param {function(string)} fn Filter function.
        203 */
        204goog.testing.TestRunner.prototype.setErrorFilter = function(fn) {
        205 this.errorFilter_ = fn;
        206};
        207
        208
        209/**
        210 * Returns a report of the test case that ran.
        211 * Used by Selenium Hooks.
        212 * @param {boolean=} opt_verbose If true results will include data about all
        213 * tests, not just what failed.
        214 * @return {string} A report summary of the test.
        215 */
        216goog.testing.TestRunner.prototype.getReport = function(opt_verbose) {
        217 var report = [];
        218 if (this.testCase) {
        219 report.push(this.testCase.getReport(opt_verbose));
        220 }
        221 if (this.errors.length > 0) {
        222 report.push('JavaScript errors detected by test runner:');
        223 report.push.apply(report, this.errors);
        224 report.push('\n');
        225 }
        226 return report.join('\n');
        227};
        228
        229
        230/**
        231 * Returns the amount of time it took for the test to run.
        232 * Used by Selenium Hooks.
        233 * @return {number} The run time, in milliseconds.
        234 */
        235goog.testing.TestRunner.prototype.getRunTime = function() {
        236 return this.testCase ? this.testCase.getRunTime() : 0;
        237};
        238
        239
        240/**
        241 * Returns the number of script files that were loaded in order to run the test.
        242 * @return {number} The number of script files.
        243 */
        244goog.testing.TestRunner.prototype.getNumFilesLoaded = function() {
        245 return this.testCase ? this.testCase.getNumFilesLoaded() : 0;
        246};
        247
        248
        249/**
        250 * Executes a test case and prints the results to the window.
        251 */
        252goog.testing.TestRunner.prototype.execute = function() {
        253 if (!this.testCase) {
        254 throw Error('The test runner must be initialized with a test case ' +
        255 'before execute can be called.');
        256 }
        257
        258 if (this.strict_ && this.testCase.getCount() == 0) {
        259 throw Error(
        260 'No tests found in given test case: ' +
        261 this.testCase.getName() + ' ' +
        262 'By default, the test runner fails if a test case has no tests. ' +
        263 'To modify this behavior, see goog.testing.TestRunner\'s ' +
        264 'setStrict() method, or G_testRunner.setStrict()');
        265 }
        266
        267 this.testCase.setCompletedCallback(goog.bind(this.onComplete_, this));
        268 if (goog.testing.TestRunner.shouldUsePromises_(this.testCase)) {
        269 this.testCase.runTestsReturningPromise();
        270 } else {
        271 this.testCase.runTests();
        272 }
        273};
        274
        275
        276/**
        277 * @param {!goog.testing.TestCase} testCase
        278 * @return {boolean}
        279 * @private
        280 */
        281goog.testing.TestRunner.shouldUsePromises_ = function(testCase) {
        282 return testCase.constructor === goog.testing.TestCase;
        283};
        284
        285
        286/**
        287 * Writes the results to the document when the test case completes.
        288 * @private
        289 */
        290goog.testing.TestRunner.prototype.onComplete_ = function() {
        291 var log = this.testCase.getReport(true);
        292 if (this.errors.length > 0) {
        293 log += '\n' + this.errors.join('\n');
        294 }
        295
        296 if (!this.logEl_) {
        297 var el = document.getElementById('closureTestRunnerLog');
        298 if (el == null) {
        299 el = document.createElement(goog.dom.TagName.DIV);
        300 document.body.appendChild(el);
        301 }
        302 this.logEl_ = el;
        303 }
        304
        305 // Highlight the page to indicate the overall outcome.
        306 this.writeLog(log);
        307
        308 // TODO(chrishenry): Make this work with multiple test cases (b/8603638).
        309 var runAgainLink = document.createElement(goog.dom.TagName.A);
        310 runAgainLink.style.display = 'inline-block';
        311 runAgainLink.style.fontSize = 'small';
        312 runAgainLink.style.marginBottom = '16px';
        313 runAgainLink.href = '';
        314 runAgainLink.onclick = goog.bind(function() {
        315 this.execute();
        316 return false;
        317 }, this);
        318 runAgainLink.innerHTML = 'Run again without reloading';
        319 this.logEl_.appendChild(runAgainLink);
        320};
        321
        322
        323/**
        324 * Writes a nicely formatted log out to the document.
        325 * @param {string} log The string to write.
        326 */
        327goog.testing.TestRunner.prototype.writeLog = function(log) {
        328 var lines = log.split('\n');
        329 for (var i = 0; i < lines.length; i++) {
        330 var line = lines[i];
        331 var color;
        332 var isFailOrError = /FAILED/.test(line) || /ERROR/.test(line);
        333 if (/PASSED/.test(line)) {
        334 color = 'darkgreen';
        335 } else if (isFailOrError) {
        336 color = 'darkred';
        337 } else {
        338 color = '#333';
        339 }
        340 var div = document.createElement(goog.dom.TagName.DIV);
        341 if (line.substr(0, 2) == '> ') {
        342 // The stack trace may contain links so it has to be interpreted as HTML.
        343 div.innerHTML = line;
        344 } else {
        345 div.appendChild(document.createTextNode(line));
        346 }
        347
        348 var testNameMatch =
        349 /(\S+) (\[[^\]]*] )?: (FAILED|ERROR|PASSED)/.exec(line);
        350 if (testNameMatch) {
        351 // Build a URL to run the test individually. If this test was already
        352 // part of another subset test, we need to overwrite the old runTests
        353 // query parameter. We also need to do this without bringing in any
        354 // extra dependencies, otherwise we could mask missing dependency bugs.
        355 var newSearch = 'runTests=' + testNameMatch[1];
        356 var search = window.location.search;
        357 if (search) {
        358 var oldTests = /runTests=([^&]*)/.exec(search);
        359 if (oldTests) {
        360 newSearch = search.substr(0, oldTests.index) +
        361 newSearch +
        362 search.substr(oldTests.index + oldTests[0].length);
        363 } else {
        364 newSearch = search + '&' + newSearch;
        365 }
        366 } else {
        367 newSearch = '?' + newSearch;
        368 }
        369 var href = window.location.href;
        370 var hash = window.location.hash;
        371 if (hash && hash.charAt(0) != '#') {
        372 hash = '#' + hash;
        373 }
        374 href = href.split('#')[0].split('?')[0] + newSearch + hash;
        375
        376 // Add the link.
        377 var a = document.createElement(goog.dom.TagName.A);
        378 a.innerHTML = '(run individually)';
        379 a.style.fontSize = '0.8em';
        380 a.style.color = '#888';
        381 a.href = href;
        382 div.appendChild(document.createTextNode(' '));
        383 div.appendChild(a);
        384 }
        385
        386 div.style.color = color;
        387 div.style.font = 'normal 100% monospace';
        388 div.style.wordWrap = 'break-word';
        389 if (i == 0) {
        390 // Highlight the first line as a header that indicates the test outcome.
        391 div.style.padding = '20px';
        392 div.style.marginBottom = '10px';
        393 if (isFailOrError) {
        394 div.style.border = '5px solid ' + color;
        395 div.style.backgroundColor = '#ffeeee';
        396 } else {
        397 div.style.border = '1px solid black';
        398 div.style.backgroundColor = '#eeffee';
        399 }
        400 }
        401
        402 try {
        403 div.style.whiteSpace = 'pre-wrap';
        404 } catch (e) {
        405 // NOTE(brenneman): IE raises an exception when assigning to pre-wrap.
        406 // Thankfully, it doesn't collapse whitespace when using monospace fonts,
        407 // so it will display correctly if we ignore the exception.
        408 }
        409
        410 if (i < 2) {
        411 div.style.fontWeight = 'bold';
        412 }
        413 this.logEl_.appendChild(div);
        414 }
        415};
        416
        417
        418/**
        419 * Logs a message to the current test case.
        420 * @param {string} s The text to output to the log.
        421 */
        422goog.testing.TestRunner.prototype.log = function(s) {
        423 if (this.testCase) {
        424 this.testCase.log(s);
        425 }
        426};
        427
        428
        429// TODO(nnaze): Properly handle serving test results when multiple test cases
        430// are run.
        431/**
        432 * @return {Object<string, !Array<string>>} A map of test names to a list of
        433 * test failures (if any) to provide formatted data for the test runner.
        434 */
        435goog.testing.TestRunner.prototype.getTestResults = function() {
        436 if (this.testCase) {
        437 return this.testCase.getTestResults();
        438 }
        439 return null;
        440};
        \ No newline at end of file diff --git a/docs/source/lib/goog/testing/watchers.js.src.html b/docs/source/lib/goog/testing/watchers.js.src.html new file mode 100644 index 0000000..036b3a5 --- /dev/null +++ b/docs/source/lib/goog/testing/watchers.js.src.html @@ -0,0 +1 @@ +watchers.js

        lib/goog/testing/watchers.js

        1// Copyright 2013 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Simple notifiers for the Closure testing framework.
        17 *
        18 * @author johnlenz@google.com (John Lenz)
        19 */
        20
        21goog.provide('goog.testing.watchers');
        22
        23
        24/** @private {!Array<function()>} */
        25goog.testing.watchers.resetWatchers_ = [];
        26
        27
        28/**
        29 * Fires clock reset watching functions.
        30 */
        31goog.testing.watchers.signalClockReset = function() {
        32 var watchers = goog.testing.watchers.resetWatchers_;
        33 for (var i = 0; i < watchers.length; i++) {
        34 goog.testing.watchers.resetWatchers_[i]();
        35 }
        36};
        37
        38
        39/**
        40 * Enqueues a function to be called when the clock used for setTimeout is reset.
        41 * @param {function()} fn
        42 */
        43goog.testing.watchers.watchClockReset = function(fn) {
        44 goog.testing.watchers.resetWatchers_.push(fn);
        45};
        46
        \ No newline at end of file diff --git a/docs/source/lib/goog/uri/uri.js.src.html b/docs/source/lib/goog/uri/uri.js.src.html index 99b4e2b..01cc1d6 100644 --- a/docs/source/lib/goog/uri/uri.js.src.html +++ b/docs/source/lib/goog/uri/uri.js.src.html @@ -1 +1 @@ -uri.js

        lib/goog/uri/uri.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Class for parsing and formatting URIs.
        17 *
        18 * Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to
        19 * create a new instance of the goog.Uri object from Uri parts.
        20 *
        21 * e.g: <code>var myUri = new goog.Uri(window.location);</code>
        22 *
        23 * Implements RFC 3986 for parsing/formatting URIs.
        24 * http://www.ietf.org/rfc/rfc3986.txt
        25 *
        26 * Some changes have been made to the interface (more like .NETs), though the
        27 * internal representation is now of un-encoded parts, this will change the
        28 * behavior slightly.
        29 *
        30 */
        31
        32goog.provide('goog.Uri');
        33goog.provide('goog.Uri.QueryData');
        34
        35goog.require('goog.array');
        36goog.require('goog.string');
        37goog.require('goog.structs');
        38goog.require('goog.structs.Map');
        39goog.require('goog.uri.utils');
        40goog.require('goog.uri.utils.ComponentIndex');
        41goog.require('goog.uri.utils.StandardQueryParam');
        42
        43
        44
        45/**
        46 * This class contains setters and getters for the parts of the URI.
        47 * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part
        48 * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the
        49 * decoded path, <code>/foo bar</code>.
        50 *
        51 * The constructor accepts an optional unparsed, raw URI string. The parser
        52 * is relaxed, so special characters that aren't escaped but don't cause
        53 * ambiguities will not cause parse failures.
        54 *
        55 * All setters return <code>this</code> and so may be chained, a la
        56 * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.
        57 *
        58 * @param {*=} opt_uri Optional string URI to parse
        59 * (use goog.Uri.create() to create a URI from parts), or if
        60 * a goog.Uri is passed, a clone is created.
        61 * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore
        62 * the case of the parameter name.
        63 *
        64 * @constructor
        65 */
        66goog.Uri = function(opt_uri, opt_ignoreCase) {
        67 // Parse in the uri string
        68 var m;
        69 if (opt_uri instanceof goog.Uri) {
        70 this.ignoreCase_ = goog.isDef(opt_ignoreCase) ?
        71 opt_ignoreCase : opt_uri.getIgnoreCase();
        72 this.setScheme(opt_uri.getScheme());
        73 this.setUserInfo(opt_uri.getUserInfo());
        74 this.setDomain(opt_uri.getDomain());
        75 this.setPort(opt_uri.getPort());
        76 this.setPath(opt_uri.getPath());
        77 this.setQueryData(opt_uri.getQueryData().clone());
        78 this.setFragment(opt_uri.getFragment());
        79 } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {
        80 this.ignoreCase_ = !!opt_ignoreCase;
        81
        82 // Set the parts -- decoding as we do so.
        83 // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings,
        84 // whereas in other browsers they will be undefined.
        85 this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);
        86 this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);
        87 this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);
        88 this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);
        89 this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);
        90 this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);
        91 this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);
        92
        93 } else {
        94 this.ignoreCase_ = !!opt_ignoreCase;
        95 this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_);
        96 }
        97};
        98
        99
        100/**
        101 * If true, we preserve the type of query parameters set programmatically.
        102 *
        103 * This means that if you set a parameter to a boolean, and then call
        104 * getParameterValue, you will get a boolean back.
        105 *
        106 * If false, we will coerce parameters to strings, just as they would
        107 * appear in real URIs.
        108 *
        109 * TODO(nicksantos): Remove this once people have time to fix all tests.
        110 *
        111 * @type {boolean}
        112 */
        113goog.Uri.preserveParameterTypesCompatibilityFlag = false;
        114
        115
        116/**
        117 * Parameter name added to stop caching.
        118 * @type {string}
        119 */
        120goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
        121
        122
        123/**
        124 * Scheme such as "http".
        125 * @type {string}
        126 * @private
        127 */
        128goog.Uri.prototype.scheme_ = '';
        129
        130
        131/**
        132 * User credentials in the form "username:password".
        133 * @type {string}
        134 * @private
        135 */
        136goog.Uri.prototype.userInfo_ = '';
        137
        138
        139/**
        140 * Domain part, e.g. "www.google.com".
        141 * @type {string}
        142 * @private
        143 */
        144goog.Uri.prototype.domain_ = '';
        145
        146
        147/**
        148 * Port, e.g. 8080.
        149 * @type {?number}
        150 * @private
        151 */
        152goog.Uri.prototype.port_ = null;
        153
        154
        155/**
        156 * Path, e.g. "/tests/img.png".
        157 * @type {string}
        158 * @private
        159 */
        160goog.Uri.prototype.path_ = '';
        161
        162
        163/**
        164 * Object representing query data.
        165 * @type {!goog.Uri.QueryData}
        166 * @private
        167 */
        168goog.Uri.prototype.queryData_;
        169
        170
        171/**
        172 * The fragment without the #.
        173 * @type {string}
        174 * @private
        175 */
        176goog.Uri.prototype.fragment_ = '';
        177
        178
        179/**
        180 * Whether or not this Uri should be treated as Read Only.
        181 * @type {boolean}
        182 * @private
        183 */
        184goog.Uri.prototype.isReadOnly_ = false;
        185
        186
        187/**
        188 * Whether or not to ignore case when comparing query params.
        189 * @type {boolean}
        190 * @private
        191 */
        192goog.Uri.prototype.ignoreCase_ = false;
        193
        194
        195/**
        196 * @return {string} The string form of the url.
        197 * @override
        198 */
        199goog.Uri.prototype.toString = function() {
        200 var out = [];
        201
        202 var scheme = this.getScheme();
        203 if (scheme) {
        204 out.push(goog.Uri.encodeSpecialChars_(
        205 scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_), ':');
        206 }
        207
        208 var domain = this.getDomain();
        209 if (domain) {
        210 out.push('//');
        211
        212 var userInfo = this.getUserInfo();
        213 if (userInfo) {
        214 out.push(goog.Uri.encodeSpecialChars_(
        215 userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_), '@');
        216 }
        217
        218 out.push(goog.string.urlEncode(domain));
        219
        220 var port = this.getPort();
        221 if (port != null) {
        222 out.push(':', String(port));
        223 }
        224 }
        225
        226 var path = this.getPath();
        227 if (path) {
        228 if (this.hasDomain() && path.charAt(0) != '/') {
        229 out.push('/');
        230 }
        231 out.push(goog.Uri.encodeSpecialChars_(
        232 path,
        233 path.charAt(0) == '/' ?
        234 goog.Uri.reDisallowedInAbsolutePath_ :
        235 goog.Uri.reDisallowedInRelativePath_));
        236 }
        237
        238 var query = this.getEncodedQuery();
        239 if (query) {
        240 out.push('?', query);
        241 }
        242
        243 var fragment = this.getFragment();
        244 if (fragment) {
        245 out.push('#', goog.Uri.encodeSpecialChars_(
        246 fragment, goog.Uri.reDisallowedInFragment_));
        247 }
        248 return out.join('');
        249};
        250
        251
        252/**
        253 * Resolves the given relative URI (a goog.Uri object), using the URI
        254 * represented by this instance as the base URI.
        255 *
        256 * There are several kinds of relative URIs:<br>
        257 * 1. foo - replaces the last part of the path, the whole query and fragment<br>
        258 * 2. /foo - replaces the the path, the query and fragment<br>
        259 * 3. //foo - replaces everything from the domain on. foo is a domain name<br>
        260 * 4. ?foo - replace the query and fragment<br>
        261 * 5. #foo - replace the fragment only
        262 *
        263 * Additionally, if relative URI has a non-empty path, all ".." and "."
        264 * segments will be resolved, as described in RFC 3986.
        265 *
        266 * @param {goog.Uri} relativeUri The relative URI to resolve.
        267 * @return {!goog.Uri} The resolved URI.
        268 */
        269goog.Uri.prototype.resolve = function(relativeUri) {
        270
        271 var absoluteUri = this.clone();
        272
        273 // we satisfy these conditions by looking for the first part of relativeUri
        274 // that is not blank and applying defaults to the rest
        275
        276 var overridden = relativeUri.hasScheme();
        277
        278 if (overridden) {
        279 absoluteUri.setScheme(relativeUri.getScheme());
        280 } else {
        281 overridden = relativeUri.hasUserInfo();
        282 }
        283
        284 if (overridden) {
        285 absoluteUri.setUserInfo(relativeUri.getUserInfo());
        286 } else {
        287 overridden = relativeUri.hasDomain();
        288 }
        289
        290 if (overridden) {
        291 absoluteUri.setDomain(relativeUri.getDomain());
        292 } else {
        293 overridden = relativeUri.hasPort();
        294 }
        295
        296 var path = relativeUri.getPath();
        297 if (overridden) {
        298 absoluteUri.setPort(relativeUri.getPort());
        299 } else {
        300 overridden = relativeUri.hasPath();
        301 if (overridden) {
        302 // resolve path properly
        303 if (path.charAt(0) != '/') {
        304 // path is relative
        305 if (this.hasDomain() && !this.hasPath()) {
        306 // RFC 3986, section 5.2.3, case 1
        307 path = '/' + path;
        308 } else {
        309 // RFC 3986, section 5.2.3, case 2
        310 var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');
        311 if (lastSlashIndex != -1) {
        312 path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path;
        313 }
        314 }
        315 }
        316 path = goog.Uri.removeDotSegments(path);
        317 }
        318 }
        319
        320 if (overridden) {
        321 absoluteUri.setPath(path);
        322 } else {
        323 overridden = relativeUri.hasQuery();
        324 }
        325
        326 if (overridden) {
        327 absoluteUri.setQueryData(relativeUri.getDecodedQuery());
        328 } else {
        329 overridden = relativeUri.hasFragment();
        330 }
        331
        332 if (overridden) {
        333 absoluteUri.setFragment(relativeUri.getFragment());
        334 }
        335
        336 return absoluteUri;
        337};
        338
        339
        340/**
        341 * Clones the URI instance.
        342 * @return {!goog.Uri} New instance of the URI objcet.
        343 */
        344goog.Uri.prototype.clone = function() {
        345 return new goog.Uri(this);
        346};
        347
        348
        349/**
        350 * @return {string} The encoded scheme/protocol for the URI.
        351 */
        352goog.Uri.prototype.getScheme = function() {
        353 return this.scheme_;
        354};
        355
        356
        357/**
        358 * Sets the scheme/protocol.
        359 * @param {string} newScheme New scheme value.
        360 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        361 * @return {!goog.Uri} Reference to this URI object.
        362 */
        363goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {
        364 this.enforceReadOnly();
        365 this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme) : newScheme;
        366
        367 // remove an : at the end of the scheme so somebody can pass in
        368 // window.location.protocol
        369 if (this.scheme_) {
        370 this.scheme_ = this.scheme_.replace(/:$/, '');
        371 }
        372 return this;
        373};
        374
        375
        376/**
        377 * @return {boolean} Whether the scheme has been set.
        378 */
        379goog.Uri.prototype.hasScheme = function() {
        380 return !!this.scheme_;
        381};
        382
        383
        384/**
        385 * @return {string} The decoded user info.
        386 */
        387goog.Uri.prototype.getUserInfo = function() {
        388 return this.userInfo_;
        389};
        390
        391
        392/**
        393 * Sets the userInfo.
        394 * @param {string} newUserInfo New userInfo value.
        395 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        396 * @return {!goog.Uri} Reference to this URI object.
        397 */
        398goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
        399 this.enforceReadOnly();
        400 this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) :
        401 newUserInfo;
        402 return this;
        403};
        404
        405
        406/**
        407 * @return {boolean} Whether the user info has been set.
        408 */
        409goog.Uri.prototype.hasUserInfo = function() {
        410 return !!this.userInfo_;
        411};
        412
        413
        414/**
        415 * @return {string} The decoded domain.
        416 */
        417goog.Uri.prototype.getDomain = function() {
        418 return this.domain_;
        419};
        420
        421
        422/**
        423 * Sets the domain.
        424 * @param {string} newDomain New domain value.
        425 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        426 * @return {!goog.Uri} Reference to this URI object.
        427 */
        428goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {
        429 this.enforceReadOnly();
        430 this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain) : newDomain;
        431 return this;
        432};
        433
        434
        435/**
        436 * @return {boolean} Whether the domain has been set.
        437 */
        438goog.Uri.prototype.hasDomain = function() {
        439 return !!this.domain_;
        440};
        441
        442
        443/**
        444 * @return {?number} The port number.
        445 */
        446goog.Uri.prototype.getPort = function() {
        447 return this.port_;
        448};
        449
        450
        451/**
        452 * Sets the port number.
        453 * @param {*} newPort Port number. Will be explicitly casted to a number.
        454 * @return {!goog.Uri} Reference to this URI object.
        455 */
        456goog.Uri.prototype.setPort = function(newPort) {
        457 this.enforceReadOnly();
        458
        459 if (newPort) {
        460 newPort = Number(newPort);
        461 if (isNaN(newPort) || newPort < 0) {
        462 throw Error('Bad port number ' + newPort);
        463 }
        464 this.port_ = newPort;
        465 } else {
        466 this.port_ = null;
        467 }
        468
        469 return this;
        470};
        471
        472
        473/**
        474 * @return {boolean} Whether the port has been set.
        475 */
        476goog.Uri.prototype.hasPort = function() {
        477 return this.port_ != null;
        478};
        479
        480
        481/**
        482 * @return {string} The decoded path.
        483 */
        484goog.Uri.prototype.getPath = function() {
        485 return this.path_;
        486};
        487
        488
        489/**
        490 * Sets the path.
        491 * @param {string} newPath New path value.
        492 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        493 * @return {!goog.Uri} Reference to this URI object.
        494 */
        495goog.Uri.prototype.setPath = function(newPath, opt_decode) {
        496 this.enforceReadOnly();
        497 this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath) : newPath;
        498 return this;
        499};
        500
        501
        502/**
        503 * @return {boolean} Whether the path has been set.
        504 */
        505goog.Uri.prototype.hasPath = function() {
        506 return !!this.path_;
        507};
        508
        509
        510/**
        511 * @return {boolean} Whether the query string has been set.
        512 */
        513goog.Uri.prototype.hasQuery = function() {
        514 return this.queryData_.toString() !== '';
        515};
        516
        517
        518/**
        519 * Sets the query data.
        520 * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.
        521 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        522 * Applies only if queryData is a string.
        523 * @return {!goog.Uri} Reference to this URI object.
        524 */
        525goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {
        526 this.enforceReadOnly();
        527
        528 if (queryData instanceof goog.Uri.QueryData) {
        529 this.queryData_ = queryData;
        530 this.queryData_.setIgnoreCase(this.ignoreCase_);
        531 } else {
        532 if (!opt_decode) {
        533 // QueryData accepts encoded query string, so encode it if
        534 // opt_decode flag is not true.
        535 queryData = goog.Uri.encodeSpecialChars_(queryData,
        536 goog.Uri.reDisallowedInQuery_);
        537 }
        538 this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_);
        539 }
        540
        541 return this;
        542};
        543
        544
        545/**
        546 * Sets the URI query.
        547 * @param {string} newQuery New query value.
        548 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        549 * @return {!goog.Uri} Reference to this URI object.
        550 */
        551goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
        552 return this.setQueryData(newQuery, opt_decode);
        553};
        554
        555
        556/**
        557 * @return {string} The encoded URI query, not including the ?.
        558 */
        559goog.Uri.prototype.getEncodedQuery = function() {
        560 return this.queryData_.toString();
        561};
        562
        563
        564/**
        565 * @return {string} The decoded URI query, not including the ?.
        566 */
        567goog.Uri.prototype.getDecodedQuery = function() {
        568 return this.queryData_.toDecodedString();
        569};
        570
        571
        572/**
        573 * Returns the query data.
        574 * @return {goog.Uri.QueryData} QueryData object.
        575 */
        576goog.Uri.prototype.getQueryData = function() {
        577 return this.queryData_;
        578};
        579
        580
        581/**
        582 * @return {string} The encoded URI query, not including the ?.
        583 *
        584 * Warning: This method, unlike other getter methods, returns encoded
        585 * value, instead of decoded one.
        586 */
        587goog.Uri.prototype.getQuery = function() {
        588 return this.getEncodedQuery();
        589};
        590
        591
        592/**
        593 * Sets the value of the named query parameters, clearing previous values for
        594 * that key.
        595 *
        596 * @param {string} key The parameter to set.
        597 * @param {*} value The new value.
        598 * @return {!goog.Uri} Reference to this URI object.
        599 */
        600goog.Uri.prototype.setParameterValue = function(key, value) {
        601 this.enforceReadOnly();
        602 this.queryData_.set(key, value);
        603 return this;
        604};
        605
        606
        607/**
        608 * Sets the values of the named query parameters, clearing previous values for
        609 * that key. Not new values will currently be moved to the end of the query
        610 * string.
        611 *
        612 * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
        613 * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>
        614 *
        615 * @param {string} key The parameter to set.
        616 * @param {*} values The new values. If values is a single
        617 * string then it will be treated as the sole value.
        618 * @return {!goog.Uri} Reference to this URI object.
        619 */
        620goog.Uri.prototype.setParameterValues = function(key, values) {
        621 this.enforceReadOnly();
        622
        623 if (!goog.isArray(values)) {
        624 values = [String(values)];
        625 }
        626
        627 // TODO(nicksantos): This cast shouldn't be necessary.
        628 this.queryData_.setValues(key, /** @type {Array} */ (values));
        629
        630 return this;
        631};
        632
        633
        634/**
        635 * Returns the value<b>s</b> for a given cgi parameter as a list of decoded
        636 * query parameter values.
        637 * @param {string} name The parameter to get values for.
        638 * @return {Array} The values for a given cgi parameter as a list of
        639 * decoded query parameter values.
        640 */
        641goog.Uri.prototype.getParameterValues = function(name) {
        642 return this.queryData_.getValues(name);
        643};
        644
        645
        646/**
        647 * Returns the first value for a given cgi parameter or undefined if the given
        648 * parameter name does not appear in the query string.
        649 * @param {string} paramName Unescaped parameter name.
        650 * @return {string|undefined} The first value for a given cgi parameter or
        651 * undefined if the given parameter name does not appear in the query
        652 * string.
        653 */
        654goog.Uri.prototype.getParameterValue = function(paramName) {
        655 // NOTE(nicksantos): This type-cast is a lie when
        656 // preserveParameterTypesCompatibilityFlag is set to true.
        657 // But this should only be set to true in tests.
        658 return /** @type {string|undefined} */ (this.queryData_.get(paramName));
        659};
        660
        661
        662/**
        663 * @return {string} The URI fragment, not including the #.
        664 */
        665goog.Uri.prototype.getFragment = function() {
        666 return this.fragment_;
        667};
        668
        669
        670/**
        671 * Sets the URI fragment.
        672 * @param {string} newFragment New fragment value.
        673 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        674 * @return {!goog.Uri} Reference to this URI object.
        675 */
        676goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
        677 this.enforceReadOnly();
        678 this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) :
        679 newFragment;
        680 return this;
        681};
        682
        683
        684/**
        685 * @return {boolean} Whether the URI has a fragment set.
        686 */
        687goog.Uri.prototype.hasFragment = function() {
        688 return !!this.fragment_;
        689};
        690
        691
        692/**
        693 * Returns true if this has the same domain as that of uri2.
        694 * @param {goog.Uri} uri2 The URI object to compare to.
        695 * @return {boolean} true if same domain; false otherwise.
        696 */
        697goog.Uri.prototype.hasSameDomainAs = function(uri2) {
        698 return ((!this.hasDomain() && !uri2.hasDomain()) ||
        699 this.getDomain() == uri2.getDomain()) &&
        700 ((!this.hasPort() && !uri2.hasPort()) ||
        701 this.getPort() == uri2.getPort());
        702};
        703
        704
        705/**
        706 * Adds a random parameter to the Uri.
        707 * @return {!goog.Uri} Reference to this Uri object.
        708 */
        709goog.Uri.prototype.makeUnique = function() {
        710 this.enforceReadOnly();
        711 this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
        712
        713 return this;
        714};
        715
        716
        717/**
        718 * Removes the named query parameter.
        719 *
        720 * @param {string} key The parameter to remove.
        721 * @return {!goog.Uri} Reference to this URI object.
        722 */
        723goog.Uri.prototype.removeParameter = function(key) {
        724 this.enforceReadOnly();
        725 this.queryData_.remove(key);
        726 return this;
        727};
        728
        729
        730/**
        731 * Sets whether Uri is read only. If this goog.Uri is read-only,
        732 * enforceReadOnly_ will be called at the start of any function that may modify
        733 * this Uri.
        734 * @param {boolean} isReadOnly whether this goog.Uri should be read only.
        735 * @return {!goog.Uri} Reference to this Uri object.
        736 */
        737goog.Uri.prototype.setReadOnly = function(isReadOnly) {
        738 this.isReadOnly_ = isReadOnly;
        739 return this;
        740};
        741
        742
        743/**
        744 * @return {boolean} Whether the URI is read only.
        745 */
        746goog.Uri.prototype.isReadOnly = function() {
        747 return this.isReadOnly_;
        748};
        749
        750
        751/**
        752 * Checks if this Uri has been marked as read only, and if so, throws an error.
        753 * This should be called whenever any modifying function is called.
        754 */
        755goog.Uri.prototype.enforceReadOnly = function() {
        756 if (this.isReadOnly_) {
        757 throw Error('Tried to modify a read-only Uri');
        758 }
        759};
        760
        761
        762/**
        763 * Sets whether to ignore case.
        764 * NOTE: If there are already key/value pairs in the QueryData, and
        765 * ignoreCase_ is set to false, the keys will all be lower-cased.
        766 * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
        767 * @return {!goog.Uri} Reference to this Uri object.
        768 */
        769goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
        770 this.ignoreCase_ = ignoreCase;
        771 if (this.queryData_) {
        772 this.queryData_.setIgnoreCase(ignoreCase);
        773 }
        774 return this;
        775};
        776
        777
        778/**
        779 * @return {boolean} Whether to ignore case.
        780 */
        781goog.Uri.prototype.getIgnoreCase = function() {
        782 return this.ignoreCase_;
        783};
        784
        785
        786//==============================================================================
        787// Static members
        788//==============================================================================
        789
        790
        791/**
        792 * Creates a uri from the string form. Basically an alias of new goog.Uri().
        793 * If a Uri object is passed to parse then it will return a clone of the object.
        794 *
        795 * @param {*} uri Raw URI string or instance of Uri
        796 * object.
        797 * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter
        798 * names in #getParameterValue.
        799 * @return {!goog.Uri} The new URI object.
        800 */
        801goog.Uri.parse = function(uri, opt_ignoreCase) {
        802 return uri instanceof goog.Uri ?
        803 uri.clone() : new goog.Uri(uri, opt_ignoreCase);
        804};
        805
        806
        807/**
        808 * Creates a new goog.Uri object from unencoded parts.
        809 *
        810 * @param {?string=} opt_scheme Scheme/protocol or full URI to parse.
        811 * @param {?string=} opt_userInfo username:password.
        812 * @param {?string=} opt_domain www.google.com.
        813 * @param {?number=} opt_port 9830.
        814 * @param {?string=} opt_path /some/path/to/a/file.html.
        815 * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.
        816 * @param {?string=} opt_fragment The fragment without the #.
        817 * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in
        818 * #getParameterValue.
        819 *
        820 * @return {!goog.Uri} The new URI object.
        821 */
        822goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port,
        823 opt_path, opt_query, opt_fragment, opt_ignoreCase) {
        824
        825 var uri = new goog.Uri(null, opt_ignoreCase);
        826
        827 // Only set the parts if they are defined and not empty strings.
        828 opt_scheme && uri.setScheme(opt_scheme);
        829 opt_userInfo && uri.setUserInfo(opt_userInfo);
        830 opt_domain && uri.setDomain(opt_domain);
        831 opt_port && uri.setPort(opt_port);
        832 opt_path && uri.setPath(opt_path);
        833 opt_query && uri.setQueryData(opt_query);
        834 opt_fragment && uri.setFragment(opt_fragment);
        835
        836 return uri;
        837};
        838
        839
        840/**
        841 * Resolves a relative Uri against a base Uri, accepting both strings and
        842 * Uri objects.
        843 *
        844 * @param {*} base Base Uri.
        845 * @param {*} rel Relative Uri.
        846 * @return {!goog.Uri} Resolved uri.
        847 */
        848goog.Uri.resolve = function(base, rel) {
        849 if (!(base instanceof goog.Uri)) {
        850 base = goog.Uri.parse(base);
        851 }
        852
        853 if (!(rel instanceof goog.Uri)) {
        854 rel = goog.Uri.parse(rel);
        855 }
        856
        857 return base.resolve(rel);
        858};
        859
        860
        861/**
        862 * Removes dot segments in given path component, as described in
        863 * RFC 3986, section 5.2.4.
        864 *
        865 * @param {string} path A non-empty path component.
        866 * @return {string} Path component with removed dot segments.
        867 */
        868goog.Uri.removeDotSegments = function(path) {
        869 if (path == '..' || path == '.') {
        870 return '';
        871
        872 } else if (!goog.string.contains(path, './') &&
        873 !goog.string.contains(path, '/.')) {
        874 // This optimization detects uris which do not contain dot-segments,
        875 // and as a consequence do not require any processing.
        876 return path;
        877
        878 } else {
        879 var leadingSlash = goog.string.startsWith(path, '/');
        880 var segments = path.split('/');
        881 var out = [];
        882
        883 for (var pos = 0; pos < segments.length; ) {
        884 var segment = segments[pos++];
        885
        886 if (segment == '.') {
        887 if (leadingSlash && pos == segments.length) {
        888 out.push('');
        889 }
        890 } else if (segment == '..') {
        891 if (out.length > 1 || out.length == 1 && out[0] != '') {
        892 out.pop();
        893 }
        894 if (leadingSlash && pos == segments.length) {
        895 out.push('');
        896 }
        897 } else {
        898 out.push(segment);
        899 leadingSlash = true;
        900 }
        901 }
        902
        903 return out.join('/');
        904 }
        905};
        906
        907
        908/**
        909 * Decodes a value or returns the empty string if it isn't defined or empty.
        910 * @param {string|undefined} val Value to decode.
        911 * @return {string} Decoded value.
        912 * @private
        913 */
        914goog.Uri.decodeOrEmpty_ = function(val) {
        915 // Don't use UrlDecode() here because val is not a query parameter.
        916 return val ? decodeURIComponent(val) : '';
        917};
        918
        919
        920/**
        921 * If unescapedPart is non null, then escapes any characters in it that aren't
        922 * valid characters in a url and also escapes any special characters that
        923 * appear in extra.
        924 *
        925 * @param {*} unescapedPart The string to encode.
        926 * @param {RegExp} extra A character set of characters in [\01-\177].
        927 * @return {?string} null iff unescapedPart == null.
        928 * @private
        929 */
        930goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra) {
        931 if (goog.isString(unescapedPart)) {
        932 return encodeURI(unescapedPart).replace(extra, goog.Uri.encodeChar_);
        933 }
        934 return null;
        935};
        936
        937
        938/**
        939 * Converts a character in [\01-\177] to its unicode character equivalent.
        940 * @param {string} ch One character string.
        941 * @return {string} Encoded string.
        942 * @private
        943 */
        944goog.Uri.encodeChar_ = function(ch) {
        945 var n = ch.charCodeAt(0);
        946 return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
        947};
        948
        949
        950/**
        951 * Regular expression for characters that are disallowed in the scheme or
        952 * userInfo part of the URI.
        953 * @type {RegExp}
        954 * @private
        955 */
        956goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
        957
        958
        959/**
        960 * Regular expression for characters that are disallowed in a relative path.
        961 * @type {RegExp}
        962 * @private
        963 */
        964goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
        965
        966
        967/**
        968 * Regular expression for characters that are disallowed in an absolute path.
        969 * @type {RegExp}
        970 * @private
        971 */
        972goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
        973
        974
        975/**
        976 * Regular expression for characters that are disallowed in the query.
        977 * @type {RegExp}
        978 * @private
        979 */
        980goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
        981
        982
        983/**
        984 * Regular expression for characters that are disallowed in the fragment.
        985 * @type {RegExp}
        986 * @private
        987 */
        988goog.Uri.reDisallowedInFragment_ = /#/g;
        989
        990
        991/**
        992 * Checks whether two URIs have the same domain.
        993 * @param {string} uri1String First URI string.
        994 * @param {string} uri2String Second URI string.
        995 * @return {boolean} true if the two URIs have the same domain; false otherwise.
        996 */
        997goog.Uri.haveSameDomain = function(uri1String, uri2String) {
        998 // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.
        999 // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.
        1000 var pieces1 = goog.uri.utils.split(uri1String);
        1001 var pieces2 = goog.uri.utils.split(uri2String);
        1002 return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
        1003 pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
        1004 pieces1[goog.uri.utils.ComponentIndex.PORT] ==
        1005 pieces2[goog.uri.utils.ComponentIndex.PORT];
        1006};
        1007
        1008
        1009
        1010/**
        1011 * Class used to represent URI query parameters. It is essentially a hash of
        1012 * name-value pairs, though a name can be present more than once.
        1013 *
        1014 * Has the same interface as the collections in goog.structs.
        1015 *
        1016 * @param {?string=} opt_query Optional encoded query string to parse into
        1017 * the object.
        1018 * @param {goog.Uri=} opt_uri Optional uri object that should have its
        1019 * cache invalidated when this object updates. Deprecated -- this
        1020 * is no longer required.
        1021 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1022 * name in #get.
        1023 * @constructor
        1024 */
        1025goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) {
        1026 /**
        1027 * Encoded query string, or null if it requires computing from the key map.
        1028 * @type {?string}
        1029 * @private
        1030 */
        1031 this.encodedQuery_ = opt_query || null;
        1032
        1033 /**
        1034 * If true, ignore the case of the parameter name in #get.
        1035 * @type {boolean}
        1036 * @private
        1037 */
        1038 this.ignoreCase_ = !!opt_ignoreCase;
        1039};
        1040
        1041
        1042/**
        1043 * If the underlying key map is not yet initialized, it parses the
        1044 * query string and fills the map with parsed data.
        1045 * @private
        1046 */
        1047goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
        1048 if (!this.keyMap_) {
        1049 this.keyMap_ = new goog.structs.Map();
        1050 this.count_ = 0;
        1051
        1052 if (this.encodedQuery_) {
        1053 var pairs = this.encodedQuery_.split('&');
        1054 for (var i = 0; i < pairs.length; i++) {
        1055 var indexOfEquals = pairs[i].indexOf('=');
        1056 var name = null;
        1057 var value = null;
        1058 if (indexOfEquals >= 0) {
        1059 name = pairs[i].substring(0, indexOfEquals);
        1060 value = pairs[i].substring(indexOfEquals + 1);
        1061 } else {
        1062 name = pairs[i];
        1063 }
        1064 name = goog.string.urlDecode(name);
        1065 name = this.getKeyName_(name);
        1066 this.add(name, value ? goog.string.urlDecode(value) : '');
        1067 }
        1068 }
        1069 }
        1070};
        1071
        1072
        1073/**
        1074 * Creates a new query data instance from a map of names and values.
        1075 *
        1076 * @param {!goog.structs.Map|!Object} map Map of string parameter
        1077 * names to parameter value. If parameter value is an array, it is
        1078 * treated as if the key maps to each individual value in the
        1079 * array.
        1080 * @param {goog.Uri=} opt_uri URI object that should have its cache
        1081 * invalidated when this object updates.
        1082 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1083 * name in #get.
        1084 * @return {!goog.Uri.QueryData} The populated query data instance.
        1085 */
        1086goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) {
        1087 var keys = goog.structs.getKeys(map);
        1088 if (typeof keys == 'undefined') {
        1089 throw Error('Keys are undefined');
        1090 }
        1091
        1092 var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
        1093 var values = goog.structs.getValues(map);
        1094 for (var i = 0; i < keys.length; i++) {
        1095 var key = keys[i];
        1096 var value = values[i];
        1097 if (!goog.isArray(value)) {
        1098 queryData.add(key, value);
        1099 } else {
        1100 queryData.setValues(key, value);
        1101 }
        1102 }
        1103 return queryData;
        1104};
        1105
        1106
        1107/**
        1108 * Creates a new query data instance from parallel arrays of parameter names
        1109 * and values. Allows for duplicate parameter names. Throws an error if the
        1110 * lengths of the arrays differ.
        1111 *
        1112 * @param {Array.<string>} keys Parameter names.
        1113 * @param {Array} values Parameter values.
        1114 * @param {goog.Uri=} opt_uri URI object that should have its cache
        1115 * invalidated when this object updates.
        1116 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1117 * name in #get.
        1118 * @return {!goog.Uri.QueryData} The populated query data instance.
        1119 */
        1120goog.Uri.QueryData.createFromKeysValues = function(
        1121 keys, values, opt_uri, opt_ignoreCase) {
        1122 if (keys.length != values.length) {
        1123 throw Error('Mismatched lengths for keys/values');
        1124 }
        1125 var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
        1126 for (var i = 0; i < keys.length; i++) {
        1127 queryData.add(keys[i], values[i]);
        1128 }
        1129 return queryData;
        1130};
        1131
        1132
        1133/**
        1134 * The map containing name/value or name/array-of-values pairs.
        1135 * May be null if it requires parsing from the query string.
        1136 *
        1137 * We need to use a Map because we cannot guarantee that the key names will
        1138 * not be problematic for IE.
        1139 *
        1140 * @type {goog.structs.Map}
        1141 * @private
        1142 */
        1143goog.Uri.QueryData.prototype.keyMap_ = null;
        1144
        1145
        1146/**
        1147 * The number of params, or null if it requires computing.
        1148 * @type {?number}
        1149 * @private
        1150 */
        1151goog.Uri.QueryData.prototype.count_ = null;
        1152
        1153
        1154/**
        1155 * @return {?number} The number of parameters.
        1156 */
        1157goog.Uri.QueryData.prototype.getCount = function() {
        1158 this.ensureKeyMapInitialized_();
        1159 return this.count_;
        1160};
        1161
        1162
        1163/**
        1164 * Adds a key value pair.
        1165 * @param {string} key Name.
        1166 * @param {*} value Value.
        1167 * @return {!goog.Uri.QueryData} Instance of this object.
        1168 */
        1169goog.Uri.QueryData.prototype.add = function(key, value) {
        1170 this.ensureKeyMapInitialized_();
        1171 this.invalidateCache_();
        1172
        1173 key = this.getKeyName_(key);
        1174 var values = this.keyMap_.get(key);
        1175 if (!values) {
        1176 this.keyMap_.set(key, (values = []));
        1177 }
        1178 values.push(value);
        1179 this.count_++;
        1180 return this;
        1181};
        1182
        1183
        1184/**
        1185 * Removes all the params with the given key.
        1186 * @param {string} key Name.
        1187 * @return {boolean} Whether any parameter was removed.
        1188 */
        1189goog.Uri.QueryData.prototype.remove = function(key) {
        1190 this.ensureKeyMapInitialized_();
        1191
        1192 key = this.getKeyName_(key);
        1193 if (this.keyMap_.containsKey(key)) {
        1194 this.invalidateCache_();
        1195
        1196 // Decrement parameter count.
        1197 this.count_ -= this.keyMap_.get(key).length;
        1198 return this.keyMap_.remove(key);
        1199 }
        1200 return false;
        1201};
        1202
        1203
        1204/**
        1205 * Clears the parameters.
        1206 */
        1207goog.Uri.QueryData.prototype.clear = function() {
        1208 this.invalidateCache_();
        1209 this.keyMap_ = null;
        1210 this.count_ = 0;
        1211};
        1212
        1213
        1214/**
        1215 * @return {boolean} Whether we have any parameters.
        1216 */
        1217goog.Uri.QueryData.prototype.isEmpty = function() {
        1218 this.ensureKeyMapInitialized_();
        1219 return this.count_ == 0;
        1220};
        1221
        1222
        1223/**
        1224 * Whether there is a parameter with the given name
        1225 * @param {string} key The parameter name to check for.
        1226 * @return {boolean} Whether there is a parameter with the given name.
        1227 */
        1228goog.Uri.QueryData.prototype.containsKey = function(key) {
        1229 this.ensureKeyMapInitialized_();
        1230 key = this.getKeyName_(key);
        1231 return this.keyMap_.containsKey(key);
        1232};
        1233
        1234
        1235/**
        1236 * Whether there is a parameter with the given value.
        1237 * @param {*} value The value to check for.
        1238 * @return {boolean} Whether there is a parameter with the given value.
        1239 */
        1240goog.Uri.QueryData.prototype.containsValue = function(value) {
        1241 // NOTE(arv): This solution goes through all the params even if it was the
        1242 // first param. We can get around this by not reusing code or by switching to
        1243 // iterators.
        1244 var vals = this.getValues();
        1245 return goog.array.contains(vals, value);
        1246};
        1247
        1248
        1249/**
        1250 * Returns all the keys of the parameters. If a key is used multiple times
        1251 * it will be included multiple times in the returned array
        1252 * @return {!Array.<string>} All the keys of the parameters.
        1253 */
        1254goog.Uri.QueryData.prototype.getKeys = function() {
        1255 this.ensureKeyMapInitialized_();
        1256 // We need to get the values to know how many keys to add.
        1257 var vals = /** @type {Array.<Array|*>} */ (this.keyMap_.getValues());
        1258 var keys = this.keyMap_.getKeys();
        1259 var rv = [];
        1260 for (var i = 0; i < keys.length; i++) {
        1261 var val = vals[i];
        1262 for (var j = 0; j < val.length; j++) {
        1263 rv.push(keys[i]);
        1264 }
        1265 }
        1266 return rv;
        1267};
        1268
        1269
        1270/**
        1271 * Returns all the values of the parameters with the given name. If the query
        1272 * data has no such key this will return an empty array. If no key is given
        1273 * all values wil be returned.
        1274 * @param {string=} opt_key The name of the parameter to get the values for.
        1275 * @return {!Array} All the values of the parameters with the given name.
        1276 */
        1277goog.Uri.QueryData.prototype.getValues = function(opt_key) {
        1278 this.ensureKeyMapInitialized_();
        1279 var rv = [];
        1280 if (opt_key) {
        1281 if (this.containsKey(opt_key)) {
        1282 rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key)));
        1283 }
        1284 } else {
        1285 // Return all values.
        1286 var values = /** @type {Array.<Array|*>} */ (this.keyMap_.getValues());
        1287 for (var i = 0; i < values.length; i++) {
        1288 rv = goog.array.concat(rv, values[i]);
        1289 }
        1290 }
        1291 return rv;
        1292};
        1293
        1294
        1295/**
        1296 * Sets a key value pair and removes all other keys with the same value.
        1297 *
        1298 * @param {string} key Name.
        1299 * @param {*} value Value.
        1300 * @return {!goog.Uri.QueryData} Instance of this object.
        1301 */
        1302goog.Uri.QueryData.prototype.set = function(key, value) {
        1303 this.ensureKeyMapInitialized_();
        1304 this.invalidateCache_();
        1305
        1306 // TODO(user): This could be better written as
        1307 // this.remove(key), this.add(key, value), but that would reorder
        1308 // the key (since the key is first removed and then added at the
        1309 // end) and we would have to fix unit tests that depend on key
        1310 // ordering.
        1311 key = this.getKeyName_(key);
        1312 if (this.containsKey(key)) {
        1313 this.count_ -= this.keyMap_.get(key).length;
        1314 }
        1315 this.keyMap_.set(key, [value]);
        1316 this.count_++;
        1317 return this;
        1318};
        1319
        1320
        1321/**
        1322 * Returns the first value associated with the key. If the query data has no
        1323 * such key this will return undefined or the optional default.
        1324 * @param {string} key The name of the parameter to get the value for.
        1325 * @param {*=} opt_default The default value to return if the query data
        1326 * has no such key.
        1327 * @return {*} The first string value associated with the key, or opt_default
        1328 * if there's no value.
        1329 */
        1330goog.Uri.QueryData.prototype.get = function(key, opt_default) {
        1331 var values = key ? this.getValues(key) : [];
        1332 if (goog.Uri.preserveParameterTypesCompatibilityFlag) {
        1333 return values.length > 0 ? values[0] : opt_default;
        1334 } else {
        1335 return values.length > 0 ? String(values[0]) : opt_default;
        1336 }
        1337};
        1338
        1339
        1340/**
        1341 * Sets the values for a key. If the key already exists, this will
        1342 * override all of the existing values that correspond to the key.
        1343 * @param {string} key The key to set values for.
        1344 * @param {Array} values The values to set.
        1345 */
        1346goog.Uri.QueryData.prototype.setValues = function(key, values) {
        1347 this.remove(key);
        1348
        1349 if (values.length > 0) {
        1350 this.invalidateCache_();
        1351 this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));
        1352 this.count_ += values.length;
        1353 }
        1354};
        1355
        1356
        1357/**
        1358 * @return {string} Encoded query string.
        1359 * @override
        1360 */
        1361goog.Uri.QueryData.prototype.toString = function() {
        1362 if (this.encodedQuery_) {
        1363 return this.encodedQuery_;
        1364 }
        1365
        1366 if (!this.keyMap_) {
        1367 return '';
        1368 }
        1369
        1370 var sb = [];
        1371
        1372 // In the past, we use this.getKeys() and this.getVals(), but that
        1373 // generates a lot of allocations as compared to simply iterating
        1374 // over the keys.
        1375 var keys = this.keyMap_.getKeys();
        1376 for (var i = 0; i < keys.length; i++) {
        1377 var key = keys[i];
        1378 var encodedKey = goog.string.urlEncode(key);
        1379 var val = this.getValues(key);
        1380 for (var j = 0; j < val.length; j++) {
        1381 var param = encodedKey;
        1382 // Ensure that null and undefined are encoded into the url as
        1383 // literal strings.
        1384 if (val[j] !== '') {
        1385 param += '=' + goog.string.urlEncode(val[j]);
        1386 }
        1387 sb.push(param);
        1388 }
        1389 }
        1390
        1391 return this.encodedQuery_ = sb.join('&');
        1392};
        1393
        1394
        1395/**
        1396 * @return {string} Decoded query string.
        1397 */
        1398goog.Uri.QueryData.prototype.toDecodedString = function() {
        1399 return goog.Uri.decodeOrEmpty_(this.toString());
        1400};
        1401
        1402
        1403/**
        1404 * Invalidate the cache.
        1405 * @private
        1406 */
        1407goog.Uri.QueryData.prototype.invalidateCache_ = function() {
        1408 this.encodedQuery_ = null;
        1409};
        1410
        1411
        1412/**
        1413 * Removes all keys that are not in the provided list. (Modifies this object.)
        1414 * @param {Array.<string>} keys The desired keys.
        1415 * @return {!goog.Uri.QueryData} a reference to this object.
        1416 */
        1417goog.Uri.QueryData.prototype.filterKeys = function(keys) {
        1418 this.ensureKeyMapInitialized_();
        1419 goog.structs.forEach(this.keyMap_,
        1420 /** @this {goog.Uri.QueryData} */
        1421 function(value, key, map) {
        1422 if (!goog.array.contains(keys, key)) {
        1423 this.remove(key);
        1424 }
        1425 }, this);
        1426 return this;
        1427};
        1428
        1429
        1430/**
        1431 * Clone the query data instance.
        1432 * @return {!goog.Uri.QueryData} New instance of the QueryData object.
        1433 */
        1434goog.Uri.QueryData.prototype.clone = function() {
        1435 var rv = new goog.Uri.QueryData();
        1436 rv.encodedQuery_ = this.encodedQuery_;
        1437 if (this.keyMap_) {
        1438 rv.keyMap_ = this.keyMap_.clone();
        1439 rv.count_ = this.count_;
        1440 }
        1441 return rv;
        1442};
        1443
        1444
        1445/**
        1446 * Helper function to get the key name from a JavaScript object. Converts
        1447 * the object to a string, and to lower case if necessary.
        1448 * @private
        1449 * @param {*} arg The object to get a key name from.
        1450 * @return {string} valid key name which can be looked up in #keyMap_.
        1451 */
        1452goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
        1453 var keyName = String(arg);
        1454 if (this.ignoreCase_) {
        1455 keyName = keyName.toLowerCase();
        1456 }
        1457 return keyName;
        1458};
        1459
        1460
        1461/**
        1462 * Ignore case in parameter names.
        1463 * NOTE: If there are already key/value pairs in the QueryData, and
        1464 * ignoreCase_ is set to false, the keys will all be lower-cased.
        1465 * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
        1466 */
        1467goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {
        1468 var resetKeys = ignoreCase && !this.ignoreCase_;
        1469 if (resetKeys) {
        1470 this.ensureKeyMapInitialized_();
        1471 this.invalidateCache_();
        1472 goog.structs.forEach(this.keyMap_,
        1473 /** @this {goog.Uri.QueryData} */
        1474 function(value, key) {
        1475 var lowerCase = key.toLowerCase();
        1476 if (key != lowerCase) {
        1477 this.remove(key);
        1478 this.setValues(lowerCase, value);
        1479 }
        1480 }, this);
        1481 }
        1482 this.ignoreCase_ = ignoreCase;
        1483};
        1484
        1485
        1486/**
        1487 * Extends a query data object with another query data or map like object. This
        1488 * operates 'in-place', it does not create a new QueryData object.
        1489 *
        1490 * @param {...(goog.Uri.QueryData|goog.structs.Map|Object)} var_args The object
        1491 * from which key value pairs will be copied.
        1492 */
        1493goog.Uri.QueryData.prototype.extend = function(var_args) {
        1494 for (var i = 0; i < arguments.length; i++) {
        1495 var data = arguments[i];
        1496 goog.structs.forEach(data,
        1497 /** @this {goog.Uri.QueryData} */
        1498 function(value, key) {
        1499 this.add(key, value);
        1500 }, this);
        1501 }
        1502};
        \ No newline at end of file +uri.js

        lib/goog/uri/uri.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Class for parsing and formatting URIs.
        17 *
        18 * Use goog.Uri(string) to parse a URI string. Use goog.Uri.create(...) to
        19 * create a new instance of the goog.Uri object from Uri parts.
        20 *
        21 * e.g: <code>var myUri = new goog.Uri(window.location);</code>
        22 *
        23 * Implements RFC 3986 for parsing/formatting URIs.
        24 * http://www.ietf.org/rfc/rfc3986.txt
        25 *
        26 * Some changes have been made to the interface (more like .NETs), though the
        27 * internal representation is now of un-encoded parts, this will change the
        28 * behavior slightly.
        29 *
        30 */
        31
        32goog.provide('goog.Uri');
        33goog.provide('goog.Uri.QueryData');
        34
        35goog.require('goog.array');
        36goog.require('goog.string');
        37goog.require('goog.structs');
        38goog.require('goog.structs.Map');
        39goog.require('goog.uri.utils');
        40goog.require('goog.uri.utils.ComponentIndex');
        41goog.require('goog.uri.utils.StandardQueryParam');
        42
        43
        44
        45/**
        46 * This class contains setters and getters for the parts of the URI.
        47 * The <code>getXyz</code>/<code>setXyz</code> methods return the decoded part
        48 * -- so<code>goog.Uri.parse('/foo%20bar').getPath()</code> will return the
        49 * decoded path, <code>/foo bar</code>.
        50 *
        51 * Reserved characters (see RFC 3986 section 2.2) can be present in
        52 * their percent-encoded form in scheme, domain, and path URI components and
        53 * will not be auto-decoded. For example:
        54 * <code>goog.Uri.parse('rel%61tive/path%2fto/resource').getPath()</code> will
        55 * return <code>relative/path%2fto/resource</code>.
        56 *
        57 * The constructor accepts an optional unparsed, raw URI string. The parser
        58 * is relaxed, so special characters that aren't escaped but don't cause
        59 * ambiguities will not cause parse failures.
        60 *
        61 * All setters return <code>this</code> and so may be chained, a la
        62 * <code>goog.Uri.parse('/foo').setFragment('part').toString()</code>.
        63 *
        64 * @param {*=} opt_uri Optional string URI to parse
        65 * (use goog.Uri.create() to create a URI from parts), or if
        66 * a goog.Uri is passed, a clone is created.
        67 * @param {boolean=} opt_ignoreCase If true, #getParameterValue will ignore
        68 * the case of the parameter name.
        69 *
        70 * @constructor
        71 * @struct
        72 */
        73goog.Uri = function(opt_uri, opt_ignoreCase) {
        74 /**
        75 * Scheme such as "http".
        76 * @private {string}
        77 */
        78 this.scheme_ = '';
        79
        80 /**
        81 * User credentials in the form "username:password".
        82 * @private {string}
        83 */
        84 this.userInfo_ = '';
        85
        86 /**
        87 * Domain part, e.g. "www.google.com".
        88 * @private {string}
        89 */
        90 this.domain_ = '';
        91
        92 /**
        93 * Port, e.g. 8080.
        94 * @private {?number}
        95 */
        96 this.port_ = null;
        97
        98 /**
        99 * Path, e.g. "/tests/img.png".
        100 * @private {string}
        101 */
        102 this.path_ = '';
        103
        104 /**
        105 * The fragment without the #.
        106 * @private {string}
        107 */
        108 this.fragment_ = '';
        109
        110 /**
        111 * Whether or not this Uri should be treated as Read Only.
        112 * @private {boolean}
        113 */
        114 this.isReadOnly_ = false;
        115
        116 /**
        117 * Whether or not to ignore case when comparing query params.
        118 * @private {boolean}
        119 */
        120 this.ignoreCase_ = false;
        121
        122 /**
        123 * Object representing query data.
        124 * @private {!goog.Uri.QueryData}
        125 */
        126 this.queryData_;
        127
        128 // Parse in the uri string
        129 var m;
        130 if (opt_uri instanceof goog.Uri) {
        131 this.ignoreCase_ = goog.isDef(opt_ignoreCase) ?
        132 opt_ignoreCase : opt_uri.getIgnoreCase();
        133 this.setScheme(opt_uri.getScheme());
        134 this.setUserInfo(opt_uri.getUserInfo());
        135 this.setDomain(opt_uri.getDomain());
        136 this.setPort(opt_uri.getPort());
        137 this.setPath(opt_uri.getPath());
        138 this.setQueryData(opt_uri.getQueryData().clone());
        139 this.setFragment(opt_uri.getFragment());
        140 } else if (opt_uri && (m = goog.uri.utils.split(String(opt_uri)))) {
        141 this.ignoreCase_ = !!opt_ignoreCase;
        142
        143 // Set the parts -- decoding as we do so.
        144 // COMPATABILITY NOTE - In IE, unmatched fields may be empty strings,
        145 // whereas in other browsers they will be undefined.
        146 this.setScheme(m[goog.uri.utils.ComponentIndex.SCHEME] || '', true);
        147 this.setUserInfo(m[goog.uri.utils.ComponentIndex.USER_INFO] || '', true);
        148 this.setDomain(m[goog.uri.utils.ComponentIndex.DOMAIN] || '', true);
        149 this.setPort(m[goog.uri.utils.ComponentIndex.PORT]);
        150 this.setPath(m[goog.uri.utils.ComponentIndex.PATH] || '', true);
        151 this.setQueryData(m[goog.uri.utils.ComponentIndex.QUERY_DATA] || '', true);
        152 this.setFragment(m[goog.uri.utils.ComponentIndex.FRAGMENT] || '', true);
        153
        154 } else {
        155 this.ignoreCase_ = !!opt_ignoreCase;
        156 this.queryData_ = new goog.Uri.QueryData(null, null, this.ignoreCase_);
        157 }
        158};
        159
        160
        161/**
        162 * If true, we preserve the type of query parameters set programmatically.
        163 *
        164 * This means that if you set a parameter to a boolean, and then call
        165 * getParameterValue, you will get a boolean back.
        166 *
        167 * If false, we will coerce parameters to strings, just as they would
        168 * appear in real URIs.
        169 *
        170 * TODO(nicksantos): Remove this once people have time to fix all tests.
        171 *
        172 * @type {boolean}
        173 */
        174goog.Uri.preserveParameterTypesCompatibilityFlag = false;
        175
        176
        177/**
        178 * Parameter name added to stop caching.
        179 * @type {string}
        180 */
        181goog.Uri.RANDOM_PARAM = goog.uri.utils.StandardQueryParam.RANDOM;
        182
        183
        184/**
        185 * @return {string} The string form of the url.
        186 * @override
        187 */
        188goog.Uri.prototype.toString = function() {
        189 var out = [];
        190
        191 var scheme = this.getScheme();
        192 if (scheme) {
        193 out.push(goog.Uri.encodeSpecialChars_(
        194 scheme, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), ':');
        195 }
        196
        197 var domain = this.getDomain();
        198 if (domain) {
        199 out.push('//');
        200
        201 var userInfo = this.getUserInfo();
        202 if (userInfo) {
        203 out.push(goog.Uri.encodeSpecialChars_(
        204 userInfo, goog.Uri.reDisallowedInSchemeOrUserInfo_, true), '@');
        205 }
        206
        207 out.push(goog.Uri.removeDoubleEncoding_(goog.string.urlEncode(domain)));
        208
        209 var port = this.getPort();
        210 if (port != null) {
        211 out.push(':', String(port));
        212 }
        213 }
        214
        215 var path = this.getPath();
        216 if (path) {
        217 if (this.hasDomain() && path.charAt(0) != '/') {
        218 out.push('/');
        219 }
        220 out.push(goog.Uri.encodeSpecialChars_(
        221 path,
        222 path.charAt(0) == '/' ?
        223 goog.Uri.reDisallowedInAbsolutePath_ :
        224 goog.Uri.reDisallowedInRelativePath_,
        225 true));
        226 }
        227
        228 var query = this.getEncodedQuery();
        229 if (query) {
        230 out.push('?', query);
        231 }
        232
        233 var fragment = this.getFragment();
        234 if (fragment) {
        235 out.push('#', goog.Uri.encodeSpecialChars_(
        236 fragment, goog.Uri.reDisallowedInFragment_));
        237 }
        238 return out.join('');
        239};
        240
        241
        242/**
        243 * Resolves the given relative URI (a goog.Uri object), using the URI
        244 * represented by this instance as the base URI.
        245 *
        246 * There are several kinds of relative URIs:<br>
        247 * 1. foo - replaces the last part of the path, the whole query and fragment<br>
        248 * 2. /foo - replaces the the path, the query and fragment<br>
        249 * 3. //foo - replaces everything from the domain on. foo is a domain name<br>
        250 * 4. ?foo - replace the query and fragment<br>
        251 * 5. #foo - replace the fragment only
        252 *
        253 * Additionally, if relative URI has a non-empty path, all ".." and "."
        254 * segments will be resolved, as described in RFC 3986.
        255 *
        256 * @param {!goog.Uri} relativeUri The relative URI to resolve.
        257 * @return {!goog.Uri} The resolved URI.
        258 */
        259goog.Uri.prototype.resolve = function(relativeUri) {
        260
        261 var absoluteUri = this.clone();
        262
        263 // we satisfy these conditions by looking for the first part of relativeUri
        264 // that is not blank and applying defaults to the rest
        265
        266 var overridden = relativeUri.hasScheme();
        267
        268 if (overridden) {
        269 absoluteUri.setScheme(relativeUri.getScheme());
        270 } else {
        271 overridden = relativeUri.hasUserInfo();
        272 }
        273
        274 if (overridden) {
        275 absoluteUri.setUserInfo(relativeUri.getUserInfo());
        276 } else {
        277 overridden = relativeUri.hasDomain();
        278 }
        279
        280 if (overridden) {
        281 absoluteUri.setDomain(relativeUri.getDomain());
        282 } else {
        283 overridden = relativeUri.hasPort();
        284 }
        285
        286 var path = relativeUri.getPath();
        287 if (overridden) {
        288 absoluteUri.setPort(relativeUri.getPort());
        289 } else {
        290 overridden = relativeUri.hasPath();
        291 if (overridden) {
        292 // resolve path properly
        293 if (path.charAt(0) != '/') {
        294 // path is relative
        295 if (this.hasDomain() && !this.hasPath()) {
        296 // RFC 3986, section 5.2.3, case 1
        297 path = '/' + path;
        298 } else {
        299 // RFC 3986, section 5.2.3, case 2
        300 var lastSlashIndex = absoluteUri.getPath().lastIndexOf('/');
        301 if (lastSlashIndex != -1) {
        302 path = absoluteUri.getPath().substr(0, lastSlashIndex + 1) + path;
        303 }
        304 }
        305 }
        306 path = goog.Uri.removeDotSegments(path);
        307 }
        308 }
        309
        310 if (overridden) {
        311 absoluteUri.setPath(path);
        312 } else {
        313 overridden = relativeUri.hasQuery();
        314 }
        315
        316 if (overridden) {
        317 absoluteUri.setQueryData(relativeUri.getDecodedQuery());
        318 } else {
        319 overridden = relativeUri.hasFragment();
        320 }
        321
        322 if (overridden) {
        323 absoluteUri.setFragment(relativeUri.getFragment());
        324 }
        325
        326 return absoluteUri;
        327};
        328
        329
        330/**
        331 * Clones the URI instance.
        332 * @return {!goog.Uri} New instance of the URI object.
        333 */
        334goog.Uri.prototype.clone = function() {
        335 return new goog.Uri(this);
        336};
        337
        338
        339/**
        340 * @return {string} The encoded scheme/protocol for the URI.
        341 */
        342goog.Uri.prototype.getScheme = function() {
        343 return this.scheme_;
        344};
        345
        346
        347/**
        348 * Sets the scheme/protocol.
        349 * @param {string} newScheme New scheme value.
        350 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        351 * @return {!goog.Uri} Reference to this URI object.
        352 */
        353goog.Uri.prototype.setScheme = function(newScheme, opt_decode) {
        354 this.enforceReadOnly();
        355 this.scheme_ = opt_decode ? goog.Uri.decodeOrEmpty_(newScheme, true) :
        356 newScheme;
        357
        358 // remove an : at the end of the scheme so somebody can pass in
        359 // window.location.protocol
        360 if (this.scheme_) {
        361 this.scheme_ = this.scheme_.replace(/:$/, '');
        362 }
        363 return this;
        364};
        365
        366
        367/**
        368 * @return {boolean} Whether the scheme has been set.
        369 */
        370goog.Uri.prototype.hasScheme = function() {
        371 return !!this.scheme_;
        372};
        373
        374
        375/**
        376 * @return {string} The decoded user info.
        377 */
        378goog.Uri.prototype.getUserInfo = function() {
        379 return this.userInfo_;
        380};
        381
        382
        383/**
        384 * Sets the userInfo.
        385 * @param {string} newUserInfo New userInfo value.
        386 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        387 * @return {!goog.Uri} Reference to this URI object.
        388 */
        389goog.Uri.prototype.setUserInfo = function(newUserInfo, opt_decode) {
        390 this.enforceReadOnly();
        391 this.userInfo_ = opt_decode ? goog.Uri.decodeOrEmpty_(newUserInfo) :
        392 newUserInfo;
        393 return this;
        394};
        395
        396
        397/**
        398 * @return {boolean} Whether the user info has been set.
        399 */
        400goog.Uri.prototype.hasUserInfo = function() {
        401 return !!this.userInfo_;
        402};
        403
        404
        405/**
        406 * @return {string} The decoded domain.
        407 */
        408goog.Uri.prototype.getDomain = function() {
        409 return this.domain_;
        410};
        411
        412
        413/**
        414 * Sets the domain.
        415 * @param {string} newDomain New domain value.
        416 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        417 * @return {!goog.Uri} Reference to this URI object.
        418 */
        419goog.Uri.prototype.setDomain = function(newDomain, opt_decode) {
        420 this.enforceReadOnly();
        421 this.domain_ = opt_decode ? goog.Uri.decodeOrEmpty_(newDomain, true) :
        422 newDomain;
        423 return this;
        424};
        425
        426
        427/**
        428 * @return {boolean} Whether the domain has been set.
        429 */
        430goog.Uri.prototype.hasDomain = function() {
        431 return !!this.domain_;
        432};
        433
        434
        435/**
        436 * @return {?number} The port number.
        437 */
        438goog.Uri.prototype.getPort = function() {
        439 return this.port_;
        440};
        441
        442
        443/**
        444 * Sets the port number.
        445 * @param {*} newPort Port number. Will be explicitly casted to a number.
        446 * @return {!goog.Uri} Reference to this URI object.
        447 */
        448goog.Uri.prototype.setPort = function(newPort) {
        449 this.enforceReadOnly();
        450
        451 if (newPort) {
        452 newPort = Number(newPort);
        453 if (isNaN(newPort) || newPort < 0) {
        454 throw Error('Bad port number ' + newPort);
        455 }
        456 this.port_ = newPort;
        457 } else {
        458 this.port_ = null;
        459 }
        460
        461 return this;
        462};
        463
        464
        465/**
        466 * @return {boolean} Whether the port has been set.
        467 */
        468goog.Uri.prototype.hasPort = function() {
        469 return this.port_ != null;
        470};
        471
        472
        473/**
        474 * @return {string} The decoded path.
        475 */
        476goog.Uri.prototype.getPath = function() {
        477 return this.path_;
        478};
        479
        480
        481/**
        482 * Sets the path.
        483 * @param {string} newPath New path value.
        484 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        485 * @return {!goog.Uri} Reference to this URI object.
        486 */
        487goog.Uri.prototype.setPath = function(newPath, opt_decode) {
        488 this.enforceReadOnly();
        489 this.path_ = opt_decode ? goog.Uri.decodeOrEmpty_(newPath, true) : newPath;
        490 return this;
        491};
        492
        493
        494/**
        495 * @return {boolean} Whether the path has been set.
        496 */
        497goog.Uri.prototype.hasPath = function() {
        498 return !!this.path_;
        499};
        500
        501
        502/**
        503 * @return {boolean} Whether the query string has been set.
        504 */
        505goog.Uri.prototype.hasQuery = function() {
        506 return this.queryData_.toString() !== '';
        507};
        508
        509
        510/**
        511 * Sets the query data.
        512 * @param {goog.Uri.QueryData|string|undefined} queryData QueryData object.
        513 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        514 * Applies only if queryData is a string.
        515 * @return {!goog.Uri} Reference to this URI object.
        516 */
        517goog.Uri.prototype.setQueryData = function(queryData, opt_decode) {
        518 this.enforceReadOnly();
        519
        520 if (queryData instanceof goog.Uri.QueryData) {
        521 this.queryData_ = queryData;
        522 this.queryData_.setIgnoreCase(this.ignoreCase_);
        523 } else {
        524 if (!opt_decode) {
        525 // QueryData accepts encoded query string, so encode it if
        526 // opt_decode flag is not true.
        527 queryData = goog.Uri.encodeSpecialChars_(queryData,
        528 goog.Uri.reDisallowedInQuery_);
        529 }
        530 this.queryData_ = new goog.Uri.QueryData(queryData, null, this.ignoreCase_);
        531 }
        532
        533 return this;
        534};
        535
        536
        537/**
        538 * Sets the URI query.
        539 * @param {string} newQuery New query value.
        540 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        541 * @return {!goog.Uri} Reference to this URI object.
        542 */
        543goog.Uri.prototype.setQuery = function(newQuery, opt_decode) {
        544 return this.setQueryData(newQuery, opt_decode);
        545};
        546
        547
        548/**
        549 * @return {string} The encoded URI query, not including the ?.
        550 */
        551goog.Uri.prototype.getEncodedQuery = function() {
        552 return this.queryData_.toString();
        553};
        554
        555
        556/**
        557 * @return {string} The decoded URI query, not including the ?.
        558 */
        559goog.Uri.prototype.getDecodedQuery = function() {
        560 return this.queryData_.toDecodedString();
        561};
        562
        563
        564/**
        565 * Returns the query data.
        566 * @return {!goog.Uri.QueryData} QueryData object.
        567 */
        568goog.Uri.prototype.getQueryData = function() {
        569 return this.queryData_;
        570};
        571
        572
        573/**
        574 * @return {string} The encoded URI query, not including the ?.
        575 *
        576 * Warning: This method, unlike other getter methods, returns encoded
        577 * value, instead of decoded one.
        578 */
        579goog.Uri.prototype.getQuery = function() {
        580 return this.getEncodedQuery();
        581};
        582
        583
        584/**
        585 * Sets the value of the named query parameters, clearing previous values for
        586 * that key.
        587 *
        588 * @param {string} key The parameter to set.
        589 * @param {*} value The new value.
        590 * @return {!goog.Uri} Reference to this URI object.
        591 */
        592goog.Uri.prototype.setParameterValue = function(key, value) {
        593 this.enforceReadOnly();
        594 this.queryData_.set(key, value);
        595 return this;
        596};
        597
        598
        599/**
        600 * Sets the values of the named query parameters, clearing previous values for
        601 * that key. Not new values will currently be moved to the end of the query
        602 * string.
        603 *
        604 * So, <code>goog.Uri.parse('foo?a=b&c=d&e=f').setParameterValues('c', ['new'])
        605 * </code> yields <tt>foo?a=b&e=f&c=new</tt>.</p>
        606 *
        607 * @param {string} key The parameter to set.
        608 * @param {*} values The new values. If values is a single
        609 * string then it will be treated as the sole value.
        610 * @return {!goog.Uri} Reference to this URI object.
        611 */
        612goog.Uri.prototype.setParameterValues = function(key, values) {
        613 this.enforceReadOnly();
        614
        615 if (!goog.isArray(values)) {
        616 values = [String(values)];
        617 }
        618
        619 this.queryData_.setValues(key, values);
        620
        621 return this;
        622};
        623
        624
        625/**
        626 * Returns the value<b>s</b> for a given cgi parameter as a list of decoded
        627 * query parameter values.
        628 * @param {string} name The parameter to get values for.
        629 * @return {!Array<?>} The values for a given cgi parameter as a list of
        630 * decoded query parameter values.
        631 */
        632goog.Uri.prototype.getParameterValues = function(name) {
        633 return this.queryData_.getValues(name);
        634};
        635
        636
        637/**
        638 * Returns the first value for a given cgi parameter or undefined if the given
        639 * parameter name does not appear in the query string.
        640 * @param {string} paramName Unescaped parameter name.
        641 * @return {string|undefined} The first value for a given cgi parameter or
        642 * undefined if the given parameter name does not appear in the query
        643 * string.
        644 */
        645goog.Uri.prototype.getParameterValue = function(paramName) {
        646 // NOTE(nicksantos): This type-cast is a lie when
        647 // preserveParameterTypesCompatibilityFlag is set to true.
        648 // But this should only be set to true in tests.
        649 return /** @type {string|undefined} */ (this.queryData_.get(paramName));
        650};
        651
        652
        653/**
        654 * @return {string} The URI fragment, not including the #.
        655 */
        656goog.Uri.prototype.getFragment = function() {
        657 return this.fragment_;
        658};
        659
        660
        661/**
        662 * Sets the URI fragment.
        663 * @param {string} newFragment New fragment value.
        664 * @param {boolean=} opt_decode Optional param for whether to decode new value.
        665 * @return {!goog.Uri} Reference to this URI object.
        666 */
        667goog.Uri.prototype.setFragment = function(newFragment, opt_decode) {
        668 this.enforceReadOnly();
        669 this.fragment_ = opt_decode ? goog.Uri.decodeOrEmpty_(newFragment) :
        670 newFragment;
        671 return this;
        672};
        673
        674
        675/**
        676 * @return {boolean} Whether the URI has a fragment set.
        677 */
        678goog.Uri.prototype.hasFragment = function() {
        679 return !!this.fragment_;
        680};
        681
        682
        683/**
        684 * Returns true if this has the same domain as that of uri2.
        685 * @param {!goog.Uri} uri2 The URI object to compare to.
        686 * @return {boolean} true if same domain; false otherwise.
        687 */
        688goog.Uri.prototype.hasSameDomainAs = function(uri2) {
        689 return ((!this.hasDomain() && !uri2.hasDomain()) ||
        690 this.getDomain() == uri2.getDomain()) &&
        691 ((!this.hasPort() && !uri2.hasPort()) ||
        692 this.getPort() == uri2.getPort());
        693};
        694
        695
        696/**
        697 * Adds a random parameter to the Uri.
        698 * @return {!goog.Uri} Reference to this Uri object.
        699 */
        700goog.Uri.prototype.makeUnique = function() {
        701 this.enforceReadOnly();
        702 this.setParameterValue(goog.Uri.RANDOM_PARAM, goog.string.getRandomString());
        703
        704 return this;
        705};
        706
        707
        708/**
        709 * Removes the named query parameter.
        710 *
        711 * @param {string} key The parameter to remove.
        712 * @return {!goog.Uri} Reference to this URI object.
        713 */
        714goog.Uri.prototype.removeParameter = function(key) {
        715 this.enforceReadOnly();
        716 this.queryData_.remove(key);
        717 return this;
        718};
        719
        720
        721/**
        722 * Sets whether Uri is read only. If this goog.Uri is read-only,
        723 * enforceReadOnly_ will be called at the start of any function that may modify
        724 * this Uri.
        725 * @param {boolean} isReadOnly whether this goog.Uri should be read only.
        726 * @return {!goog.Uri} Reference to this Uri object.
        727 */
        728goog.Uri.prototype.setReadOnly = function(isReadOnly) {
        729 this.isReadOnly_ = isReadOnly;
        730 return this;
        731};
        732
        733
        734/**
        735 * @return {boolean} Whether the URI is read only.
        736 */
        737goog.Uri.prototype.isReadOnly = function() {
        738 return this.isReadOnly_;
        739};
        740
        741
        742/**
        743 * Checks if this Uri has been marked as read only, and if so, throws an error.
        744 * This should be called whenever any modifying function is called.
        745 */
        746goog.Uri.prototype.enforceReadOnly = function() {
        747 if (this.isReadOnly_) {
        748 throw Error('Tried to modify a read-only Uri');
        749 }
        750};
        751
        752
        753/**
        754 * Sets whether to ignore case.
        755 * NOTE: If there are already key/value pairs in the QueryData, and
        756 * ignoreCase_ is set to false, the keys will all be lower-cased.
        757 * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
        758 * @return {!goog.Uri} Reference to this Uri object.
        759 */
        760goog.Uri.prototype.setIgnoreCase = function(ignoreCase) {
        761 this.ignoreCase_ = ignoreCase;
        762 if (this.queryData_) {
        763 this.queryData_.setIgnoreCase(ignoreCase);
        764 }
        765 return this;
        766};
        767
        768
        769/**
        770 * @return {boolean} Whether to ignore case.
        771 */
        772goog.Uri.prototype.getIgnoreCase = function() {
        773 return this.ignoreCase_;
        774};
        775
        776
        777//==============================================================================
        778// Static members
        779//==============================================================================
        780
        781
        782/**
        783 * Creates a uri from the string form. Basically an alias of new goog.Uri().
        784 * If a Uri object is passed to parse then it will return a clone of the object.
        785 *
        786 * @param {*} uri Raw URI string or instance of Uri
        787 * object.
        788 * @param {boolean=} opt_ignoreCase Whether to ignore the case of parameter
        789 * names in #getParameterValue.
        790 * @return {!goog.Uri} The new URI object.
        791 */
        792goog.Uri.parse = function(uri, opt_ignoreCase) {
        793 return uri instanceof goog.Uri ?
        794 uri.clone() : new goog.Uri(uri, opt_ignoreCase);
        795};
        796
        797
        798/**
        799 * Creates a new goog.Uri object from unencoded parts.
        800 *
        801 * @param {?string=} opt_scheme Scheme/protocol or full URI to parse.
        802 * @param {?string=} opt_userInfo username:password.
        803 * @param {?string=} opt_domain www.google.com.
        804 * @param {?number=} opt_port 9830.
        805 * @param {?string=} opt_path /some/path/to/a/file.html.
        806 * @param {string|goog.Uri.QueryData=} opt_query a=1&b=2.
        807 * @param {?string=} opt_fragment The fragment without the #.
        808 * @param {boolean=} opt_ignoreCase Whether to ignore parameter name case in
        809 * #getParameterValue.
        810 *
        811 * @return {!goog.Uri} The new URI object.
        812 */
        813goog.Uri.create = function(opt_scheme, opt_userInfo, opt_domain, opt_port,
        814 opt_path, opt_query, opt_fragment, opt_ignoreCase) {
        815
        816 var uri = new goog.Uri(null, opt_ignoreCase);
        817
        818 // Only set the parts if they are defined and not empty strings.
        819 opt_scheme && uri.setScheme(opt_scheme);
        820 opt_userInfo && uri.setUserInfo(opt_userInfo);
        821 opt_domain && uri.setDomain(opt_domain);
        822 opt_port && uri.setPort(opt_port);
        823 opt_path && uri.setPath(opt_path);
        824 opt_query && uri.setQueryData(opt_query);
        825 opt_fragment && uri.setFragment(opt_fragment);
        826
        827 return uri;
        828};
        829
        830
        831/**
        832 * Resolves a relative Uri against a base Uri, accepting both strings and
        833 * Uri objects.
        834 *
        835 * @param {*} base Base Uri.
        836 * @param {*} rel Relative Uri.
        837 * @return {!goog.Uri} Resolved uri.
        838 */
        839goog.Uri.resolve = function(base, rel) {
        840 if (!(base instanceof goog.Uri)) {
        841 base = goog.Uri.parse(base);
        842 }
        843
        844 if (!(rel instanceof goog.Uri)) {
        845 rel = goog.Uri.parse(rel);
        846 }
        847
        848 return base.resolve(rel);
        849};
        850
        851
        852/**
        853 * Removes dot segments in given path component, as described in
        854 * RFC 3986, section 5.2.4.
        855 *
        856 * @param {string} path A non-empty path component.
        857 * @return {string} Path component with removed dot segments.
        858 */
        859goog.Uri.removeDotSegments = function(path) {
        860 if (path == '..' || path == '.') {
        861 return '';
        862
        863 } else if (!goog.string.contains(path, './') &&
        864 !goog.string.contains(path, '/.')) {
        865 // This optimization detects uris which do not contain dot-segments,
        866 // and as a consequence do not require any processing.
        867 return path;
        868
        869 } else {
        870 var leadingSlash = goog.string.startsWith(path, '/');
        871 var segments = path.split('/');
        872 var out = [];
        873
        874 for (var pos = 0; pos < segments.length; ) {
        875 var segment = segments[pos++];
        876
        877 if (segment == '.') {
        878 if (leadingSlash && pos == segments.length) {
        879 out.push('');
        880 }
        881 } else if (segment == '..') {
        882 if (out.length > 1 || out.length == 1 && out[0] != '') {
        883 out.pop();
        884 }
        885 if (leadingSlash && pos == segments.length) {
        886 out.push('');
        887 }
        888 } else {
        889 out.push(segment);
        890 leadingSlash = true;
        891 }
        892 }
        893
        894 return out.join('/');
        895 }
        896};
        897
        898
        899/**
        900 * Decodes a value or returns the empty string if it isn't defined or empty.
        901 * @param {string|undefined} val Value to decode.
        902 * @param {boolean=} opt_preserveReserved If true, restricted characters will
        903 * not be decoded.
        904 * @return {string} Decoded value.
        905 * @private
        906 */
        907goog.Uri.decodeOrEmpty_ = function(val, opt_preserveReserved) {
        908 // Don't use UrlDecode() here because val is not a query parameter.
        909 if (!val) {
        910 return '';
        911 }
        912
        913 // decodeURI has the same output for '%2f' and '%252f'. We double encode %25
        914 // so that we can distinguish between the 2 inputs. This is later undone by
        915 // removeDoubleEncoding_.
        916 return opt_preserveReserved ?
        917 decodeURI(val.replace(/%25/g, '%2525')) : decodeURIComponent(val);
        918};
        919
        920
        921/**
        922 * If unescapedPart is non null, then escapes any characters in it that aren't
        923 * valid characters in a url and also escapes any special characters that
        924 * appear in extra.
        925 *
        926 * @param {*} unescapedPart The string to encode.
        927 * @param {RegExp} extra A character set of characters in [\01-\177].
        928 * @param {boolean=} opt_removeDoubleEncoding If true, remove double percent
        929 * encoding.
        930 * @return {?string} null iff unescapedPart == null.
        931 * @private
        932 */
        933goog.Uri.encodeSpecialChars_ = function(unescapedPart, extra,
        934 opt_removeDoubleEncoding) {
        935 if (goog.isString(unescapedPart)) {
        936 var encoded = encodeURI(unescapedPart).
        937 replace(extra, goog.Uri.encodeChar_);
        938 if (opt_removeDoubleEncoding) {
        939 // encodeURI double-escapes %XX sequences used to represent restricted
        940 // characters in some URI components, remove the double escaping here.
        941 encoded = goog.Uri.removeDoubleEncoding_(encoded);
        942 }
        943 return encoded;
        944 }
        945 return null;
        946};
        947
        948
        949/**
        950 * Converts a character in [\01-\177] to its unicode character equivalent.
        951 * @param {string} ch One character string.
        952 * @return {string} Encoded string.
        953 * @private
        954 */
        955goog.Uri.encodeChar_ = function(ch) {
        956 var n = ch.charCodeAt(0);
        957 return '%' + ((n >> 4) & 0xf).toString(16) + (n & 0xf).toString(16);
        958};
        959
        960
        961/**
        962 * Removes double percent-encoding from a string.
        963 * @param {string} doubleEncodedString String
        964 * @return {string} String with double encoding removed.
        965 * @private
        966 */
        967goog.Uri.removeDoubleEncoding_ = function(doubleEncodedString) {
        968 return doubleEncodedString.replace(/%25([0-9a-fA-F]{2})/g, '%$1');
        969};
        970
        971
        972/**
        973 * Regular expression for characters that are disallowed in the scheme or
        974 * userInfo part of the URI.
        975 * @type {RegExp}
        976 * @private
        977 */
        978goog.Uri.reDisallowedInSchemeOrUserInfo_ = /[#\/\?@]/g;
        979
        980
        981/**
        982 * Regular expression for characters that are disallowed in a relative path.
        983 * Colon is included due to RFC 3986 3.3.
        984 * @type {RegExp}
        985 * @private
        986 */
        987goog.Uri.reDisallowedInRelativePath_ = /[\#\?:]/g;
        988
        989
        990/**
        991 * Regular expression for characters that are disallowed in an absolute path.
        992 * @type {RegExp}
        993 * @private
        994 */
        995goog.Uri.reDisallowedInAbsolutePath_ = /[\#\?]/g;
        996
        997
        998/**
        999 * Regular expression for characters that are disallowed in the query.
        1000 * @type {RegExp}
        1001 * @private
        1002 */
        1003goog.Uri.reDisallowedInQuery_ = /[\#\?@]/g;
        1004
        1005
        1006/**
        1007 * Regular expression for characters that are disallowed in the fragment.
        1008 * @type {RegExp}
        1009 * @private
        1010 */
        1011goog.Uri.reDisallowedInFragment_ = /#/g;
        1012
        1013
        1014/**
        1015 * Checks whether two URIs have the same domain.
        1016 * @param {string} uri1String First URI string.
        1017 * @param {string} uri2String Second URI string.
        1018 * @return {boolean} true if the two URIs have the same domain; false otherwise.
        1019 */
        1020goog.Uri.haveSameDomain = function(uri1String, uri2String) {
        1021 // Differs from goog.uri.utils.haveSameDomain, since this ignores scheme.
        1022 // TODO(gboyer): Have this just call goog.uri.util.haveSameDomain.
        1023 var pieces1 = goog.uri.utils.split(uri1String);
        1024 var pieces2 = goog.uri.utils.split(uri2String);
        1025 return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
        1026 pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
        1027 pieces1[goog.uri.utils.ComponentIndex.PORT] ==
        1028 pieces2[goog.uri.utils.ComponentIndex.PORT];
        1029};
        1030
        1031
        1032
        1033/**
        1034 * Class used to represent URI query parameters. It is essentially a hash of
        1035 * name-value pairs, though a name can be present more than once.
        1036 *
        1037 * Has the same interface as the collections in goog.structs.
        1038 *
        1039 * @param {?string=} opt_query Optional encoded query string to parse into
        1040 * the object.
        1041 * @param {goog.Uri=} opt_uri Optional uri object that should have its
        1042 * cache invalidated when this object updates. Deprecated -- this
        1043 * is no longer required.
        1044 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1045 * name in #get.
        1046 * @constructor
        1047 * @struct
        1048 * @final
        1049 */
        1050goog.Uri.QueryData = function(opt_query, opt_uri, opt_ignoreCase) {
        1051 /**
        1052 * The map containing name/value or name/array-of-values pairs.
        1053 * May be null if it requires parsing from the query string.
        1054 *
        1055 * We need to use a Map because we cannot guarantee that the key names will
        1056 * not be problematic for IE.
        1057 *
        1058 * @private {goog.structs.Map<string, !Array<*>>}
        1059 */
        1060 this.keyMap_ = null;
        1061
        1062 /**
        1063 * The number of params, or null if it requires computing.
        1064 * @private {?number}
        1065 */
        1066 this.count_ = null;
        1067
        1068 /**
        1069 * Encoded query string, or null if it requires computing from the key map.
        1070 * @private {?string}
        1071 */
        1072 this.encodedQuery_ = opt_query || null;
        1073
        1074 /**
        1075 * If true, ignore the case of the parameter name in #get.
        1076 * @private {boolean}
        1077 */
        1078 this.ignoreCase_ = !!opt_ignoreCase;
        1079};
        1080
        1081
        1082/**
        1083 * If the underlying key map is not yet initialized, it parses the
        1084 * query string and fills the map with parsed data.
        1085 * @private
        1086 */
        1087goog.Uri.QueryData.prototype.ensureKeyMapInitialized_ = function() {
        1088 if (!this.keyMap_) {
        1089 this.keyMap_ = new goog.structs.Map();
        1090 this.count_ = 0;
        1091 if (this.encodedQuery_) {
        1092 var self = this;
        1093 goog.uri.utils.parseQueryData(this.encodedQuery_, function(name, value) {
        1094 self.add(goog.string.urlDecode(name), value);
        1095 });
        1096 }
        1097 }
        1098};
        1099
        1100
        1101/**
        1102 * Creates a new query data instance from a map of names and values.
        1103 *
        1104 * @param {!goog.structs.Map<string, ?>|!Object} map Map of string parameter
        1105 * names to parameter value. If parameter value is an array, it is
        1106 * treated as if the key maps to each individual value in the
        1107 * array.
        1108 * @param {goog.Uri=} opt_uri URI object that should have its cache
        1109 * invalidated when this object updates.
        1110 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1111 * name in #get.
        1112 * @return {!goog.Uri.QueryData} The populated query data instance.
        1113 */
        1114goog.Uri.QueryData.createFromMap = function(map, opt_uri, opt_ignoreCase) {
        1115 var keys = goog.structs.getKeys(map);
        1116 if (typeof keys == 'undefined') {
        1117 throw Error('Keys are undefined');
        1118 }
        1119
        1120 var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
        1121 var values = goog.structs.getValues(map);
        1122 for (var i = 0; i < keys.length; i++) {
        1123 var key = keys[i];
        1124 var value = values[i];
        1125 if (!goog.isArray(value)) {
        1126 queryData.add(key, value);
        1127 } else {
        1128 queryData.setValues(key, value);
        1129 }
        1130 }
        1131 return queryData;
        1132};
        1133
        1134
        1135/**
        1136 * Creates a new query data instance from parallel arrays of parameter names
        1137 * and values. Allows for duplicate parameter names. Throws an error if the
        1138 * lengths of the arrays differ.
        1139 *
        1140 * @param {!Array<string>} keys Parameter names.
        1141 * @param {!Array<?>} values Parameter values.
        1142 * @param {goog.Uri=} opt_uri URI object that should have its cache
        1143 * invalidated when this object updates.
        1144 * @param {boolean=} opt_ignoreCase If true, ignore the case of the parameter
        1145 * name in #get.
        1146 * @return {!goog.Uri.QueryData} The populated query data instance.
        1147 */
        1148goog.Uri.QueryData.createFromKeysValues = function(
        1149 keys, values, opt_uri, opt_ignoreCase) {
        1150 if (keys.length != values.length) {
        1151 throw Error('Mismatched lengths for keys/values');
        1152 }
        1153 var queryData = new goog.Uri.QueryData(null, null, opt_ignoreCase);
        1154 for (var i = 0; i < keys.length; i++) {
        1155 queryData.add(keys[i], values[i]);
        1156 }
        1157 return queryData;
        1158};
        1159
        1160
        1161/**
        1162 * @return {?number} The number of parameters.
        1163 */
        1164goog.Uri.QueryData.prototype.getCount = function() {
        1165 this.ensureKeyMapInitialized_();
        1166 return this.count_;
        1167};
        1168
        1169
        1170/**
        1171 * Adds a key value pair.
        1172 * @param {string} key Name.
        1173 * @param {*} value Value.
        1174 * @return {!goog.Uri.QueryData} Instance of this object.
        1175 */
        1176goog.Uri.QueryData.prototype.add = function(key, value) {
        1177 this.ensureKeyMapInitialized_();
        1178 this.invalidateCache_();
        1179
        1180 key = this.getKeyName_(key);
        1181 var values = this.keyMap_.get(key);
        1182 if (!values) {
        1183 this.keyMap_.set(key, (values = []));
        1184 }
        1185 values.push(value);
        1186 this.count_++;
        1187 return this;
        1188};
        1189
        1190
        1191/**
        1192 * Removes all the params with the given key.
        1193 * @param {string} key Name.
        1194 * @return {boolean} Whether any parameter was removed.
        1195 */
        1196goog.Uri.QueryData.prototype.remove = function(key) {
        1197 this.ensureKeyMapInitialized_();
        1198
        1199 key = this.getKeyName_(key);
        1200 if (this.keyMap_.containsKey(key)) {
        1201 this.invalidateCache_();
        1202
        1203 // Decrement parameter count.
        1204 this.count_ -= this.keyMap_.get(key).length;
        1205 return this.keyMap_.remove(key);
        1206 }
        1207 return false;
        1208};
        1209
        1210
        1211/**
        1212 * Clears the parameters.
        1213 */
        1214goog.Uri.QueryData.prototype.clear = function() {
        1215 this.invalidateCache_();
        1216 this.keyMap_ = null;
        1217 this.count_ = 0;
        1218};
        1219
        1220
        1221/**
        1222 * @return {boolean} Whether we have any parameters.
        1223 */
        1224goog.Uri.QueryData.prototype.isEmpty = function() {
        1225 this.ensureKeyMapInitialized_();
        1226 return this.count_ == 0;
        1227};
        1228
        1229
        1230/**
        1231 * Whether there is a parameter with the given name
        1232 * @param {string} key The parameter name to check for.
        1233 * @return {boolean} Whether there is a parameter with the given name.
        1234 */
        1235goog.Uri.QueryData.prototype.containsKey = function(key) {
        1236 this.ensureKeyMapInitialized_();
        1237 key = this.getKeyName_(key);
        1238 return this.keyMap_.containsKey(key);
        1239};
        1240
        1241
        1242/**
        1243 * Whether there is a parameter with the given value.
        1244 * @param {*} value The value to check for.
        1245 * @return {boolean} Whether there is a parameter with the given value.
        1246 */
        1247goog.Uri.QueryData.prototype.containsValue = function(value) {
        1248 // NOTE(arv): This solution goes through all the params even if it was the
        1249 // first param. We can get around this by not reusing code or by switching to
        1250 // iterators.
        1251 var vals = this.getValues();
        1252 return goog.array.contains(vals, value);
        1253};
        1254
        1255
        1256/**
        1257 * Returns all the keys of the parameters. If a key is used multiple times
        1258 * it will be included multiple times in the returned array
        1259 * @return {!Array<string>} All the keys of the parameters.
        1260 */
        1261goog.Uri.QueryData.prototype.getKeys = function() {
        1262 this.ensureKeyMapInitialized_();
        1263 // We need to get the values to know how many keys to add.
        1264 var vals = /** @type {!Array<*>} */ (this.keyMap_.getValues());
        1265 var keys = this.keyMap_.getKeys();
        1266 var rv = [];
        1267 for (var i = 0; i < keys.length; i++) {
        1268 var val = vals[i];
        1269 for (var j = 0; j < val.length; j++) {
        1270 rv.push(keys[i]);
        1271 }
        1272 }
        1273 return rv;
        1274};
        1275
        1276
        1277/**
        1278 * Returns all the values of the parameters with the given name. If the query
        1279 * data has no such key this will return an empty array. If no key is given
        1280 * all values wil be returned.
        1281 * @param {string=} opt_key The name of the parameter to get the values for.
        1282 * @return {!Array<?>} All the values of the parameters with the given name.
        1283 */
        1284goog.Uri.QueryData.prototype.getValues = function(opt_key) {
        1285 this.ensureKeyMapInitialized_();
        1286 var rv = [];
        1287 if (goog.isString(opt_key)) {
        1288 if (this.containsKey(opt_key)) {
        1289 rv = goog.array.concat(rv, this.keyMap_.get(this.getKeyName_(opt_key)));
        1290 }
        1291 } else {
        1292 // Return all values.
        1293 var values = this.keyMap_.getValues();
        1294 for (var i = 0; i < values.length; i++) {
        1295 rv = goog.array.concat(rv, values[i]);
        1296 }
        1297 }
        1298 return rv;
        1299};
        1300
        1301
        1302/**
        1303 * Sets a key value pair and removes all other keys with the same value.
        1304 *
        1305 * @param {string} key Name.
        1306 * @param {*} value Value.
        1307 * @return {!goog.Uri.QueryData} Instance of this object.
        1308 */
        1309goog.Uri.QueryData.prototype.set = function(key, value) {
        1310 this.ensureKeyMapInitialized_();
        1311 this.invalidateCache_();
        1312
        1313 // TODO(chrishenry): This could be better written as
        1314 // this.remove(key), this.add(key, value), but that would reorder
        1315 // the key (since the key is first removed and then added at the
        1316 // end) and we would have to fix unit tests that depend on key
        1317 // ordering.
        1318 key = this.getKeyName_(key);
        1319 if (this.containsKey(key)) {
        1320 this.count_ -= this.keyMap_.get(key).length;
        1321 }
        1322 this.keyMap_.set(key, [value]);
        1323 this.count_++;
        1324 return this;
        1325};
        1326
        1327
        1328/**
        1329 * Returns the first value associated with the key. If the query data has no
        1330 * such key this will return undefined or the optional default.
        1331 * @param {string} key The name of the parameter to get the value for.
        1332 * @param {*=} opt_default The default value to return if the query data
        1333 * has no such key.
        1334 * @return {*} The first string value associated with the key, or opt_default
        1335 * if there's no value.
        1336 */
        1337goog.Uri.QueryData.prototype.get = function(key, opt_default) {
        1338 var values = key ? this.getValues(key) : [];
        1339 if (goog.Uri.preserveParameterTypesCompatibilityFlag) {
        1340 return values.length > 0 ? values[0] : opt_default;
        1341 } else {
        1342 return values.length > 0 ? String(values[0]) : opt_default;
        1343 }
        1344};
        1345
        1346
        1347/**
        1348 * Sets the values for a key. If the key already exists, this will
        1349 * override all of the existing values that correspond to the key.
        1350 * @param {string} key The key to set values for.
        1351 * @param {!Array<?>} values The values to set.
        1352 */
        1353goog.Uri.QueryData.prototype.setValues = function(key, values) {
        1354 this.remove(key);
        1355
        1356 if (values.length > 0) {
        1357 this.invalidateCache_();
        1358 this.keyMap_.set(this.getKeyName_(key), goog.array.clone(values));
        1359 this.count_ += values.length;
        1360 }
        1361};
        1362
        1363
        1364/**
        1365 * @return {string} Encoded query string.
        1366 * @override
        1367 */
        1368goog.Uri.QueryData.prototype.toString = function() {
        1369 if (this.encodedQuery_) {
        1370 return this.encodedQuery_;
        1371 }
        1372
        1373 if (!this.keyMap_) {
        1374 return '';
        1375 }
        1376
        1377 var sb = [];
        1378
        1379 // In the past, we use this.getKeys() and this.getVals(), but that
        1380 // generates a lot of allocations as compared to simply iterating
        1381 // over the keys.
        1382 var keys = this.keyMap_.getKeys();
        1383 for (var i = 0; i < keys.length; i++) {
        1384 var key = keys[i];
        1385 var encodedKey = goog.string.urlEncode(key);
        1386 var val = this.getValues(key);
        1387 for (var j = 0; j < val.length; j++) {
        1388 var param = encodedKey;
        1389 // Ensure that null and undefined are encoded into the url as
        1390 // literal strings.
        1391 if (val[j] !== '') {
        1392 param += '=' + goog.string.urlEncode(val[j]);
        1393 }
        1394 sb.push(param);
        1395 }
        1396 }
        1397
        1398 return this.encodedQuery_ = sb.join('&');
        1399};
        1400
        1401
        1402/**
        1403 * @return {string} Decoded query string.
        1404 */
        1405goog.Uri.QueryData.prototype.toDecodedString = function() {
        1406 return goog.Uri.decodeOrEmpty_(this.toString());
        1407};
        1408
        1409
        1410/**
        1411 * Invalidate the cache.
        1412 * @private
        1413 */
        1414goog.Uri.QueryData.prototype.invalidateCache_ = function() {
        1415 this.encodedQuery_ = null;
        1416};
        1417
        1418
        1419/**
        1420 * Removes all keys that are not in the provided list. (Modifies this object.)
        1421 * @param {Array<string>} keys The desired keys.
        1422 * @return {!goog.Uri.QueryData} a reference to this object.
        1423 */
        1424goog.Uri.QueryData.prototype.filterKeys = function(keys) {
        1425 this.ensureKeyMapInitialized_();
        1426 this.keyMap_.forEach(
        1427 function(value, key) {
        1428 if (!goog.array.contains(keys, key)) {
        1429 this.remove(key);
        1430 }
        1431 }, this);
        1432 return this;
        1433};
        1434
        1435
        1436/**
        1437 * Clone the query data instance.
        1438 * @return {!goog.Uri.QueryData} New instance of the QueryData object.
        1439 */
        1440goog.Uri.QueryData.prototype.clone = function() {
        1441 var rv = new goog.Uri.QueryData();
        1442 rv.encodedQuery_ = this.encodedQuery_;
        1443 if (this.keyMap_) {
        1444 rv.keyMap_ = this.keyMap_.clone();
        1445 rv.count_ = this.count_;
        1446 }
        1447 return rv;
        1448};
        1449
        1450
        1451/**
        1452 * Helper function to get the key name from a JavaScript object. Converts
        1453 * the object to a string, and to lower case if necessary.
        1454 * @private
        1455 * @param {*} arg The object to get a key name from.
        1456 * @return {string} valid key name which can be looked up in #keyMap_.
        1457 */
        1458goog.Uri.QueryData.prototype.getKeyName_ = function(arg) {
        1459 var keyName = String(arg);
        1460 if (this.ignoreCase_) {
        1461 keyName = keyName.toLowerCase();
        1462 }
        1463 return keyName;
        1464};
        1465
        1466
        1467/**
        1468 * Ignore case in parameter names.
        1469 * NOTE: If there are already key/value pairs in the QueryData, and
        1470 * ignoreCase_ is set to false, the keys will all be lower-cased.
        1471 * @param {boolean} ignoreCase whether this goog.Uri should ignore case.
        1472 */
        1473goog.Uri.QueryData.prototype.setIgnoreCase = function(ignoreCase) {
        1474 var resetKeys = ignoreCase && !this.ignoreCase_;
        1475 if (resetKeys) {
        1476 this.ensureKeyMapInitialized_();
        1477 this.invalidateCache_();
        1478 this.keyMap_.forEach(
        1479 function(value, key) {
        1480 var lowerCase = key.toLowerCase();
        1481 if (key != lowerCase) {
        1482 this.remove(key);
        1483 this.setValues(lowerCase, value);
        1484 }
        1485 }, this);
        1486 }
        1487 this.ignoreCase_ = ignoreCase;
        1488};
        1489
        1490
        1491/**
        1492 * Extends a query data object with another query data or map like object. This
        1493 * operates 'in-place', it does not create a new QueryData object.
        1494 *
        1495 * @param {...(goog.Uri.QueryData|goog.structs.Map<?, ?>|Object)} var_args
        1496 * The object from which key value pairs will be copied.
        1497 */
        1498goog.Uri.QueryData.prototype.extend = function(var_args) {
        1499 for (var i = 0; i < arguments.length; i++) {
        1500 var data = arguments[i];
        1501 goog.structs.forEach(data,
        1502 /** @this {goog.Uri.QueryData} */
        1503 function(value, key) {
        1504 this.add(key, value);
        1505 }, this);
        1506 }
        1507};
        \ No newline at end of file diff --git a/docs/source/lib/goog/uri/utils.js.src.html b/docs/source/lib/goog/uri/utils.js.src.html index 0313591..5328e76 100644 --- a/docs/source/lib/goog/uri/utils.js.src.html +++ b/docs/source/lib/goog/uri/utils.js.src.html @@ -1 +1 @@ -utils.js

        lib/goog/uri/utils.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Simple utilities for dealing with URI strings.
        17 *
        18 * This is intended to be a lightweight alternative to constructing goog.Uri
        19 * objects. Whereas goog.Uri adds several kilobytes to the binary regardless
        20 * of how much of its functionality you use, this is designed to be a set of
        21 * mostly-independent utilities so that the compiler includes only what is
        22 * necessary for the task. Estimated savings of porting is 5k pre-gzip and
        23 * 1.5k post-gzip. To ensure the savings remain, future developers should
        24 * avoid adding new functionality to existing functions, but instead create
        25 * new ones and factor out shared code.
        26 *
        27 * Many of these utilities have limited functionality, tailored to common
        28 * cases. The query parameter utilities assume that the parameter keys are
        29 * already encoded, since most keys are compile-time alphanumeric strings. The
        30 * query parameter mutation utilities also do not tolerate fragment identifiers.
        31 *
        32 * By design, these functions can be slower than goog.Uri equivalents.
        33 * Repeated calls to some of functions may be quadratic in behavior for IE,
        34 * although the effect is somewhat limited given the 2kb limit.
        35 *
        36 * One advantage of the limited functionality here is that this approach is
        37 * less sensitive to differences in URI encodings than goog.Uri, since these
        38 * functions modify the strings in place, rather than decoding and
        39 * re-encoding.
        40 *
        41 * Uses features of RFC 3986 for parsing/formatting URIs:
        42 * http://www.ietf.org/rfc/rfc3986.txt
        43 *
        44 * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
        45 * @author msamuel@google.com (Mike Samuel) - Domain knowledge and regexes.
        46 */
        47
        48goog.provide('goog.uri.utils');
        49goog.provide('goog.uri.utils.ComponentIndex');
        50goog.provide('goog.uri.utils.QueryArray');
        51goog.provide('goog.uri.utils.QueryValue');
        52goog.provide('goog.uri.utils.StandardQueryParam');
        53
        54goog.require('goog.asserts');
        55goog.require('goog.string');
        56goog.require('goog.userAgent');
        57
        58
        59/**
        60 * Character codes inlined to avoid object allocations due to charCode.
        61 * @enum {number}
        62 * @private
        63 */
        64goog.uri.utils.CharCode_ = {
        65 AMPERSAND: 38,
        66 EQUAL: 61,
        67 HASH: 35,
        68 QUESTION: 63
        69};
        70
        71
        72/**
        73 * Builds a URI string from already-encoded parts.
        74 *
        75 * No encoding is performed. Any component may be omitted as either null or
        76 * undefined.
        77 *
        78 * @param {?string=} opt_scheme The scheme such as 'http'.
        79 * @param {?string=} opt_userInfo The user name before the '@'.
        80 * @param {?string=} opt_domain The domain such as 'www.google.com', already
        81 * URI-encoded.
        82 * @param {(string|number|null)=} opt_port The port number.
        83 * @param {?string=} opt_path The path, already URI-encoded. If it is not
        84 * empty, it must begin with a slash.
        85 * @param {?string=} opt_queryData The URI-encoded query data.
        86 * @param {?string=} opt_fragment The URI-encoded fragment identifier.
        87 * @return {string} The fully combined URI.
        88 */
        89goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo,
        90 opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) {
        91 var out = '';
        92
        93 if (opt_scheme) {
        94 out += opt_scheme + ':';
        95 }
        96
        97 if (opt_domain) {
        98 out += '//';
        99
        100 if (opt_userInfo) {
        101 out += opt_userInfo + '@';
        102 }
        103
        104 out += opt_domain;
        105
        106 if (opt_port) {
        107 out += ':' + opt_port;
        108 }
        109 }
        110
        111 if (opt_path) {
        112 out += opt_path;
        113 }
        114
        115 if (opt_queryData) {
        116 out += '?' + opt_queryData;
        117 }
        118
        119 if (opt_fragment) {
        120 out += '#' + opt_fragment;
        121 }
        122
        123 return out;
        124};
        125
        126
        127/**
        128 * A regular expression for breaking a URI into its component parts.
        129 *
        130 * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
        131 * As the "first-match-wins" algorithm is identical to the "greedy"
        132 * disambiguation method used by POSIX regular expressions, it is natural and
        133 * commonplace to use a regular expression for parsing the potential five
        134 * components of a URI reference.
        135 *
        136 * The following line is the regular expression for breaking-down a
        137 * well-formed URI reference into its components.
        138 *
        139 * <pre>
        140 * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
        141 * 12 3 4 5 6 7 8 9
        142 * </pre>
        143 *
        144 * The numbers in the second line above are only to assist readability; they
        145 * indicate the reference points for each subexpression (i.e., each paired
        146 * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
        147 * For example, matching the above expression to
        148 * <pre>
        149 * http://www.ics.uci.edu/pub/ietf/uri/#Related
        150 * </pre>
        151 * results in the following subexpression matches:
        152 * <pre>
        153 * $1 = http:
        154 * $2 = http
        155 * $3 = //www.ics.uci.edu
        156 * $4 = www.ics.uci.edu
        157 * $5 = /pub/ietf/uri/
        158 * $6 = <undefined>
        159 * $7 = <undefined>
        160 * $8 = #Related
        161 * $9 = Related
        162 * </pre>
        163 * where <undefined> indicates that the component is not present, as is the
        164 * case for the query component in the above example. Therefore, we can
        165 * determine the value of the five components as
        166 * <pre>
        167 * scheme = $2
        168 * authority = $4
        169 * path = $5
        170 * query = $7
        171 * fragment = $9
        172 * </pre>
        173 *
        174 * The regular expression has been modified slightly to expose the
        175 * userInfo, domain, and port separately from the authority.
        176 * The modified version yields
        177 * <pre>
        178 * $1 = http scheme
        179 * $2 = <undefined> userInfo -\
        180 * $3 = www.ics.uci.edu domain | authority
        181 * $4 = <undefined> port -/
        182 * $5 = /pub/ietf/uri/ path
        183 * $6 = <undefined> query without ?
        184 * $7 = Related fragment without #
        185 * </pre>
        186 * @type {!RegExp}
        187 * @private
        188 */
        189goog.uri.utils.splitRe_ = new RegExp(
        190 '^' +
        191 '(?:' +
        192 '([^:/?#.]+)' + // scheme - ignore special characters
        193 // used by other URL parts such as :,
        194 // ?, /, #, and .
        195 ':)?' +
        196 '(?://' +
        197 '(?:([^/?#]*)@)?' + // userInfo
        198 '([^/#?]*?)' + // domain
        199 '(?::([0-9]+))?' + // port
        200 '(?=[/#?]|$)' + // authority-terminating character
        201 ')?' +
        202 '([^?#]+)?' + // path
        203 '(?:\\?([^#]*))?' + // query
        204 '(?:#(.*))?' + // fragment
        205 '$');
        206
        207
        208/**
        209 * The index of each URI component in the return value of goog.uri.utils.split.
        210 * @enum {number}
        211 */
        212goog.uri.utils.ComponentIndex = {
        213 SCHEME: 1,
        214 USER_INFO: 2,
        215 DOMAIN: 3,
        216 PORT: 4,
        217 PATH: 5,
        218 QUERY_DATA: 6,
        219 FRAGMENT: 7
        220};
        221
        222
        223/**
        224 * Splits a URI into its component parts.
        225 *
        226 * Each component can be accessed via the component indices; for example:
        227 * <pre>
        228 * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
        229 * </pre>
        230 *
        231 * @param {string} uri The URI string to examine.
        232 * @return {!Array.<string|undefined>} Each component still URI-encoded.
        233 * Each component that is present will contain the encoded value, whereas
        234 * components that are not present will be undefined or empty, depending
        235 * on the browser's regular expression implementation. Never null, since
        236 * arbitrary strings may still look like path names.
        237 */
        238goog.uri.utils.split = function(uri) {
        239 goog.uri.utils.phishingProtection_();
        240
        241 // See @return comment -- never null.
        242 return /** @type {!Array.<string|undefined>} */ (
        243 uri.match(goog.uri.utils.splitRe_));
        244};
        245
        246
        247/**
        248 * Safari has a nasty bug where if you have an http URL with a username, e.g.,
        249 * http://evil.com%2F@google.com/
        250 * Safari will report that window.location.href is
        251 * http://evil.com/google.com/
        252 * so that anyone who tries to parse the domain of that URL will get
        253 * the wrong domain. We've seen exploits where people use this to trick
        254 * Safari into loading resources from evil domains.
        255 *
        256 * To work around this, we run a little "Safari phishing check", and throw
        257 * an exception if we see this happening.
        258 *
        259 * There is no convenient place to put this check. We apply it to
        260 * anyone doing URI parsing on Webkit. We're not happy about this, but
        261 * it fixes the problem.
        262 *
        263 * This should be removed once Safari fixes their bug.
        264 *
        265 * Exploit reported by Masato Kinugawa.
        266 *
        267 * @type {boolean}
        268 * @private
        269 */
        270goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT;
        271
        272
        273/**
        274 * Check to see if the user is being phished.
        275 * @private
        276 */
        277goog.uri.utils.phishingProtection_ = function() {
        278 if (goog.uri.utils.needsPhishingProtection_) {
        279 // Turn protection off, so that we don't recurse.
        280 goog.uri.utils.needsPhishingProtection_ = false;
        281
        282 // Use quoted access, just in case the user isn't using location externs.
        283 var location = goog.global['location'];
        284 if (location) {
        285 var href = location['href'];
        286 if (href) {
        287 var domain = goog.uri.utils.getDomain(href);
        288 if (domain && domain != location['hostname']) {
        289 // Phishing attack
        290 goog.uri.utils.needsPhishingProtection_ = true;
        291 throw Error();
        292 }
        293 }
        294 }
        295 }
        296};
        297
        298
        299/**
        300 * @param {?string} uri A possibly null string.
        301 * @return {?string} The string URI-decoded, or null if uri is null.
        302 * @private
        303 */
        304goog.uri.utils.decodeIfPossible_ = function(uri) {
        305 return uri && decodeURIComponent(uri);
        306};
        307
        308
        309/**
        310 * Gets a URI component by index.
        311 *
        312 * It is preferred to use the getPathEncoded() variety of functions ahead,
        313 * since they are more readable.
        314 *
        315 * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
        316 * @param {string} uri The URI to examine.
        317 * @return {?string} The still-encoded component, or null if the component
        318 * is not present.
        319 * @private
        320 */
        321goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
        322 // Convert undefined, null, and empty string into null.
        323 return goog.uri.utils.split(uri)[componentIndex] || null;
        324};
        325
        326
        327/**
        328 * @param {string} uri The URI to examine.
        329 * @return {?string} The protocol or scheme, or null if none. Does not
        330 * include trailing colons or slashes.
        331 */
        332goog.uri.utils.getScheme = function(uri) {
        333 return goog.uri.utils.getComponentByIndex_(
        334 goog.uri.utils.ComponentIndex.SCHEME, uri);
        335};
        336
        337
        338/**
        339 * Gets the effective scheme for the URL. If the URL is relative then the
        340 * scheme is derived from the page's location.
        341 * @param {string} uri The URI to examine.
        342 * @return {string} The protocol or scheme, always lower case.
        343 */
        344goog.uri.utils.getEffectiveScheme = function(uri) {
        345 var scheme = goog.uri.utils.getScheme(uri);
        346 if (!scheme && self.location) {
        347 var protocol = self.location.protocol;
        348 scheme = protocol.substr(0, protocol.length - 1);
        349 }
        350 // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
        351 // All other browsers with web workers support self.location from the worker.
        352 return scheme ? scheme.toLowerCase() : '';
        353};
        354
        355
        356/**
        357 * @param {string} uri The URI to examine.
        358 * @return {?string} The user name still encoded, or null if none.
        359 */
        360goog.uri.utils.getUserInfoEncoded = function(uri) {
        361 return goog.uri.utils.getComponentByIndex_(
        362 goog.uri.utils.ComponentIndex.USER_INFO, uri);
        363};
        364
        365
        366/**
        367 * @param {string} uri The URI to examine.
        368 * @return {?string} The decoded user info, or null if none.
        369 */
        370goog.uri.utils.getUserInfo = function(uri) {
        371 return goog.uri.utils.decodeIfPossible_(
        372 goog.uri.utils.getUserInfoEncoded(uri));
        373};
        374
        375
        376/**
        377 * @param {string} uri The URI to examine.
        378 * @return {?string} The domain name still encoded, or null if none.
        379 */
        380goog.uri.utils.getDomainEncoded = function(uri) {
        381 return goog.uri.utils.getComponentByIndex_(
        382 goog.uri.utils.ComponentIndex.DOMAIN, uri);
        383};
        384
        385
        386/**
        387 * @param {string} uri The URI to examine.
        388 * @return {?string} The decoded domain, or null if none.
        389 */
        390goog.uri.utils.getDomain = function(uri) {
        391 return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getDomainEncoded(uri));
        392};
        393
        394
        395/**
        396 * @param {string} uri The URI to examine.
        397 * @return {?number} The port number, or null if none.
        398 */
        399goog.uri.utils.getPort = function(uri) {
        400 // Coerce to a number. If the result of getComponentByIndex_ is null or
        401 // non-numeric, the number coersion yields NaN. This will then return
        402 // null for all non-numeric cases (though also zero, which isn't a relevant
        403 // port number).
        404 return Number(goog.uri.utils.getComponentByIndex_(
        405 goog.uri.utils.ComponentIndex.PORT, uri)) || null;
        406};
        407
        408
        409/**
        410 * @param {string} uri The URI to examine.
        411 * @return {?string} The path still encoded, or null if none. Includes the
        412 * leading slash, if any.
        413 */
        414goog.uri.utils.getPathEncoded = function(uri) {
        415 return goog.uri.utils.getComponentByIndex_(
        416 goog.uri.utils.ComponentIndex.PATH, uri);
        417};
        418
        419
        420/**
        421 * @param {string} uri The URI to examine.
        422 * @return {?string} The decoded path, or null if none. Includes the leading
        423 * slash, if any.
        424 */
        425goog.uri.utils.getPath = function(uri) {
        426 return goog.uri.utils.decodeIfPossible_(goog.uri.utils.getPathEncoded(uri));
        427};
        428
        429
        430/**
        431 * @param {string} uri The URI to examine.
        432 * @return {?string} The query data still encoded, or null if none. Does not
        433 * include the question mark itself.
        434 */
        435goog.uri.utils.getQueryData = function(uri) {
        436 return goog.uri.utils.getComponentByIndex_(
        437 goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
        438};
        439
        440
        441/**
        442 * @param {string} uri The URI to examine.
        443 * @return {?string} The fragment identifier, or null if none. Does not
        444 * include the hash mark itself.
        445 */
        446goog.uri.utils.getFragmentEncoded = function(uri) {
        447 // The hash mark may not appear in any other part of the URL.
        448 var hashIndex = uri.indexOf('#');
        449 return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
        450};
        451
        452
        453/**
        454 * @param {string} uri The URI to examine.
        455 * @param {?string} fragment The encoded fragment identifier, or null if none.
        456 * Does not include the hash mark itself.
        457 * @return {string} The URI with the fragment set.
        458 */
        459goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
        460 return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
        461};
        462
        463
        464/**
        465 * @param {string} uri The URI to examine.
        466 * @return {?string} The decoded fragment identifier, or null if none. Does
        467 * not include the hash mark.
        468 */
        469goog.uri.utils.getFragment = function(uri) {
        470 return goog.uri.utils.decodeIfPossible_(
        471 goog.uri.utils.getFragmentEncoded(uri));
        472};
        473
        474
        475/**
        476 * Extracts everything up to the port of the URI.
        477 * @param {string} uri The URI string.
        478 * @return {string} Everything up to and including the port.
        479 */
        480goog.uri.utils.getHost = function(uri) {
        481 var pieces = goog.uri.utils.split(uri);
        482 return goog.uri.utils.buildFromEncodedParts(
        483 pieces[goog.uri.utils.ComponentIndex.SCHEME],
        484 pieces[goog.uri.utils.ComponentIndex.USER_INFO],
        485 pieces[goog.uri.utils.ComponentIndex.DOMAIN],
        486 pieces[goog.uri.utils.ComponentIndex.PORT]);
        487};
        488
        489
        490/**
        491 * Extracts the path of the URL and everything after.
        492 * @param {string} uri The URI string.
        493 * @return {string} The URI, starting at the path and including the query
        494 * parameters and fragment identifier.
        495 */
        496goog.uri.utils.getPathAndAfter = function(uri) {
        497 var pieces = goog.uri.utils.split(uri);
        498 return goog.uri.utils.buildFromEncodedParts(null, null, null, null,
        499 pieces[goog.uri.utils.ComponentIndex.PATH],
        500 pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
        501 pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
        502};
        503
        504
        505/**
        506 * Gets the URI with the fragment identifier removed.
        507 * @param {string} uri The URI to examine.
        508 * @return {string} Everything preceding the hash mark.
        509 */
        510goog.uri.utils.removeFragment = function(uri) {
        511 // The hash mark may not appear in any other part of the URL.
        512 var hashIndex = uri.indexOf('#');
        513 return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
        514};
        515
        516
        517/**
        518 * Ensures that two URI's have the exact same domain, scheme, and port.
        519 *
        520 * Unlike the version in goog.Uri, this checks protocol, and therefore is
        521 * suitable for checking against the browser's same-origin policy.
        522 *
        523 * @param {string} uri1 The first URI.
        524 * @param {string} uri2 The second URI.
        525 * @return {boolean} Whether they have the same domain and port.
        526 */
        527goog.uri.utils.haveSameDomain = function(uri1, uri2) {
        528 var pieces1 = goog.uri.utils.split(uri1);
        529 var pieces2 = goog.uri.utils.split(uri2);
        530 return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
        531 pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
        532 pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
        533 pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
        534 pieces1[goog.uri.utils.ComponentIndex.PORT] ==
        535 pieces2[goog.uri.utils.ComponentIndex.PORT];
        536};
        537
        538
        539/**
        540 * Asserts that there are no fragment or query identifiers, only in uncompiled
        541 * mode.
        542 * @param {string} uri The URI to examine.
        543 * @private
        544 */
        545goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
        546 // NOTE: would use goog.asserts here, but jscompiler doesn't know that
        547 // indexOf has no side effects.
        548 if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) {
        549 throw Error('goog.uri.utils: Fragment or query identifiers are not ' +
        550 'supported: [' + uri + ']');
        551 }
        552};
        553
        554
        555/**
        556 * Supported query parameter values by the parameter serializing utilities.
        557 *
        558 * If a value is null or undefined, the key-value pair is skipped, as an easy
        559 * way to omit parameters conditionally. Non-array parameters are converted
        560 * to a string and URI encoded. Array values are expanded into multiple
        561 * &key=value pairs, with each element stringized and URI-encoded.
        562 *
        563 * @typedef {*}
        564 */
        565goog.uri.utils.QueryValue;
        566
        567
        568/**
        569 * An array representing a set of query parameters with alternating keys
        570 * and values.
        571 *
        572 * Keys are assumed to be URI encoded already and live at even indices. See
        573 * goog.uri.utils.QueryValue for details on how parameter values are encoded.
        574 *
        575 * Example:
        576 * <pre>
        577 * var data = [
        578 * // Simple param: ?name=BobBarker
        579 * 'name', 'BobBarker',
        580 * // Conditional param -- may be omitted entirely.
        581 * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
        582 * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
        583 * 'house', ['LosAngeles', 'NewYork', null]
        584 * ];
        585 * </pre>
        586 *
        587 * @typedef {!Array.<string|goog.uri.utils.QueryValue>}
        588 */
        589goog.uri.utils.QueryArray;
        590
        591
        592/**
        593 * Appends a URI and query data in a string buffer with special preconditions.
        594 *
        595 * Internal implementation utility, performing very few object allocations.
        596 *
        597 * @param {!Array.<string|undefined>} buffer A string buffer. The first element
        598 * must be the base URI, and may have a fragment identifier. If the array
        599 * contains more than one element, the second element must be an ampersand,
        600 * and may be overwritten, depending on the base URI. Undefined elements
        601 * are treated as empty-string.
        602 * @return {string} The concatenated URI and query data.
        603 * @private
        604 */
        605goog.uri.utils.appendQueryData_ = function(buffer) {
        606 if (buffer[1]) {
        607 // At least one query parameter was added. We need to check the
        608 // punctuation mark, which is currently an ampersand, and also make sure
        609 // there aren't any interfering fragment identifiers.
        610 var baseUri = /** @type {string} */ (buffer[0]);
        611 var hashIndex = baseUri.indexOf('#');
        612 if (hashIndex >= 0) {
        613 // Move the fragment off the base part of the URI into the end.
        614 buffer.push(baseUri.substr(hashIndex));
        615 buffer[0] = baseUri = baseUri.substr(0, hashIndex);
        616 }
        617 var questionIndex = baseUri.indexOf('?');
        618 if (questionIndex < 0) {
        619 // No question mark, so we need a question mark instead of an ampersand.
        620 buffer[1] = '?';
        621 } else if (questionIndex == baseUri.length - 1) {
        622 // Question mark is the very last character of the existing URI, so don't
        623 // append an additional delimiter.
        624 buffer[1] = undefined;
        625 }
        626 }
        627
        628 return buffer.join('');
        629};
        630
        631
        632/**
        633 * Appends key=value pairs to an array, supporting multi-valued objects.
        634 * @param {string} key The key prefix.
        635 * @param {goog.uri.utils.QueryValue} value The value to serialize.
        636 * @param {!Array.<string>} pairs The array to which the 'key=value' strings
        637 * should be appended.
        638 * @private
        639 */
        640goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
        641 if (goog.isArray(value)) {
        642 // Convince the compiler it's an array.
        643 goog.asserts.assertArray(value);
        644 for (var j = 0; j < value.length; j++) {
        645 // Convert to string explicitly, to short circuit the null and array
        646 // logic in this function -- this ensures that null and undefined get
        647 // written as literal 'null' and 'undefined', and arrays don't get
        648 // expanded out but instead encoded in the default way.
        649 goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
        650 }
        651 } else if (value != null) {
        652 // Skip a top-level null or undefined entirely.
        653 pairs.push('&', key,
        654 // Check for empty string. Zero gets encoded into the url as literal
        655 // strings. For empty string, skip the equal sign, to be consistent
        656 // with UriBuilder.java.
        657 value === '' ? '' : '=',
        658 goog.string.urlEncode(value));
        659 }
        660};
        661
        662
        663/**
        664 * Builds a buffer of query data from a sequence of alternating keys and values.
        665 *
        666 * @param {!Array.<string|undefined>} buffer A string buffer to append to. The
        667 * first element appended will be an '&', and may be replaced by the caller.
        668 * @param {goog.uri.utils.QueryArray|Arguments} keysAndValues An array with
        669 * alternating keys and values -- see the typedef.
        670 * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
        671 * @return {!Array.<string|undefined>} The buffer argument.
        672 * @private
        673 */
        674goog.uri.utils.buildQueryDataBuffer_ = function(
        675 buffer, keysAndValues, opt_startIndex) {
        676 goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0),
        677 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.');
        678
        679 for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
        680 goog.uri.utils.appendKeyValuePairs_(
        681 keysAndValues[i], keysAndValues[i + 1], buffer);
        682 }
        683
        684 return buffer;
        685};
        686
        687
        688/**
        689 * Builds a query data string from a sequence of alternating keys and values.
        690 * Currently generates "&key&" for empty args.
        691 *
        692 * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and
        693 * values. See the typedef.
        694 * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
        695 * @return {string} The encoded query string, in the for 'a=1&b=2'.
        696 */
        697goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
        698 var buffer = goog.uri.utils.buildQueryDataBuffer_(
        699 [], keysAndValues, opt_startIndex);
        700 buffer[0] = ''; // Remove the leading ampersand.
        701 return buffer.join('');
        702};
        703
        704
        705/**
        706 * Builds a buffer of query data from a map.
        707 *
        708 * @param {!Array.<string|undefined>} buffer A string buffer to append to. The
        709 * first element appended will be an '&', and may be replaced by the caller.
        710 * @param {Object.<goog.uri.utils.QueryValue>} map An object where keys are
        711 * URI-encoded parameter keys, and the values conform to the contract
        712 * specified in the goog.uri.utils.QueryValue typedef.
        713 * @return {!Array.<string|undefined>} The buffer argument.
        714 * @private
        715 */
        716goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
        717 for (var key in map) {
        718 goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
        719 }
        720
        721 return buffer;
        722};
        723
        724
        725/**
        726 * Builds a query data string from a map.
        727 * Currently generates "&key&" for empty args.
        728 *
        729 * @param {Object} map An object where keys are URI-encoded parameter keys,
        730 * and the values are arbitrary types or arrays. Keys with a null value
        731 * are dropped.
        732 * @return {string} The encoded query string, in the for 'a=1&b=2'.
        733 */
        734goog.uri.utils.buildQueryDataFromMap = function(map) {
        735 var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
        736 buffer[0] = '';
        737 return buffer.join('');
        738};
        739
        740
        741/**
        742 * Appends URI parameters to an existing URI.
        743 *
        744 * The variable arguments may contain alternating keys and values. Keys are
        745 * assumed to be already URI encoded. The values should not be URI-encoded,
        746 * and will instead be encoded by this function.
        747 * <pre>
        748 * appendParams('http://www.foo.com?existing=true',
        749 * 'key1', 'value1',
        750 * 'key2', 'value?willBeEncoded',
        751 * 'key3', ['valueA', 'valueB', 'valueC'],
        752 * 'key4', null);
        753 * result: 'http://www.foo.com?existing=true&' +
        754 * 'key1=value1&' +
        755 * 'key2=value%3FwillBeEncoded&' +
        756 * 'key3=valueA&key3=valueB&key3=valueC'
        757 * </pre>
        758 *
        759 * A single call to this function will not exhibit quadratic behavior in IE,
        760 * whereas multiple repeated calls may, although the effect is limited by
        761 * fact that URL's generally can't exceed 2kb.
        762 *
        763 * @param {string} uri The original URI, which may already have query data.
        764 * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args
        765 * An array or argument list conforming to goog.uri.utils.QueryArray.
        766 * @return {string} The URI with all query parameters added.
        767 */
        768goog.uri.utils.appendParams = function(uri, var_args) {
        769 return goog.uri.utils.appendQueryData_(
        770 arguments.length == 2 ?
        771 goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) :
        772 goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1));
        773};
        774
        775
        776/**
        777 * Appends query parameters from a map.
        778 *
        779 * @param {string} uri The original URI, which may already have query data.
        780 * @param {Object} map An object where keys are URI-encoded parameter keys,
        781 * and the values are arbitrary types or arrays. Keys with a null value
        782 * are dropped.
        783 * @return {string} The new parameters.
        784 */
        785goog.uri.utils.appendParamsFromMap = function(uri, map) {
        786 return goog.uri.utils.appendQueryData_(
        787 goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
        788};
        789
        790
        791/**
        792 * Appends a single URI parameter.
        793 *
        794 * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
        795 * way string append works, though it should be limited given the 2kb limit.
        796 *
        797 * @param {string} uri The original URI, which may already have query data.
        798 * @param {string} key The key, which must already be URI encoded.
        799 * @param {*=} opt_value The value, which will be stringized and encoded
        800 * (assumed not already to be encoded). If omitted, undefined, or null, the
        801 * key will be added as a valueless parameter.
        802 * @return {string} The URI with the query parameter added.
        803 */
        804goog.uri.utils.appendParam = function(uri, key, opt_value) {
        805 var paramArr = [uri, '&', key];
        806 if (goog.isDefAndNotNull(opt_value)) {
        807 paramArr.push('=', goog.string.urlEncode(opt_value));
        808 }
        809 return goog.uri.utils.appendQueryData_(paramArr);
        810};
        811
        812
        813/**
        814 * Finds the next instance of a query parameter with the specified name.
        815 *
        816 * Does not instantiate any objects.
        817 *
        818 * @param {string} uri The URI to search. May contain a fragment identifier
        819 * if opt_hashIndex is specified.
        820 * @param {number} startIndex The index to begin searching for the key at. A
        821 * match may be found even if this is one character after the ampersand.
        822 * @param {string} keyEncoded The URI-encoded key.
        823 * @param {number} hashOrEndIndex Index to stop looking at. If a hash
        824 * mark is present, it should be its index, otherwise it should be the
        825 * length of the string.
        826 * @return {number} The position of the first character in the key's name,
        827 * immediately after either a question mark or a dot.
        828 * @private
        829 */
        830goog.uri.utils.findParam_ = function(
        831 uri, startIndex, keyEncoded, hashOrEndIndex) {
        832 var index = startIndex;
        833 var keyLength = keyEncoded.length;
        834
        835 // Search for the key itself and post-filter for surronuding punctuation,
        836 // rather than expensively building a regexp.
        837 while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
        838 index < hashOrEndIndex) {
        839 var precedingChar = uri.charCodeAt(index - 1);
        840 // Ensure that the preceding character is '&' or '?'.
        841 if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
        842 precedingChar == goog.uri.utils.CharCode_.QUESTION) {
        843 // Ensure the following character is '&', '=', '#', or NaN
        844 // (end of string).
        845 var followingChar = uri.charCodeAt(index + keyLength);
        846 if (!followingChar ||
        847 followingChar == goog.uri.utils.CharCode_.EQUAL ||
        848 followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
        849 followingChar == goog.uri.utils.CharCode_.HASH) {
        850 return index;
        851 }
        852 }
        853 index += keyLength + 1;
        854 }
        855
        856 return -1;
        857};
        858
        859
        860/**
        861 * Regular expression for finding a hash mark or end of string.
        862 * @type {RegExp}
        863 * @private
        864 */
        865goog.uri.utils.hashOrEndRe_ = /#|$/;
        866
        867
        868/**
        869 * Determines if the URI contains a specific key.
        870 *
        871 * Performs no object instantiations.
        872 *
        873 * @param {string} uri The URI to process. May contain a fragment
        874 * identifier.
        875 * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
        876 * @return {boolean} Whether the key is present.
        877 */
        878goog.uri.utils.hasParam = function(uri, keyEncoded) {
        879 return goog.uri.utils.findParam_(uri, 0, keyEncoded,
        880 uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
        881};
        882
        883
        884/**
        885 * Gets the first value of a query parameter.
        886 * @param {string} uri The URI to process. May contain a fragment.
        887 * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
        888 * @return {?string} The first value of the parameter (URI-decoded), or null
        889 * if the parameter is not found.
        890 */
        891goog.uri.utils.getParamValue = function(uri, keyEncoded) {
        892 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        893 var foundIndex = goog.uri.utils.findParam_(
        894 uri, 0, keyEncoded, hashOrEndIndex);
        895
        896 if (foundIndex < 0) {
        897 return null;
        898 } else {
        899 var endPosition = uri.indexOf('&', foundIndex);
        900 if (endPosition < 0 || endPosition > hashOrEndIndex) {
        901 endPosition = hashOrEndIndex;
        902 }
        903 // Progress forth to the end of the "key=" or "key&" substring.
        904 foundIndex += keyEncoded.length + 1;
        905 // Use substr, because it (unlike substring) will return empty string
        906 // if foundIndex > endPosition.
        907 return goog.string.urlDecode(
        908 uri.substr(foundIndex, endPosition - foundIndex));
        909 }
        910};
        911
        912
        913/**
        914 * Gets all values of a query parameter.
        915 * @param {string} uri The URI to process. May contain a framgnet.
        916 * @param {string} keyEncoded The URI-encoded key. Case-snsitive.
        917 * @return {!Array.<string>} All URI-decoded values with the given key.
        918 * If the key is not found, this will have length 0, but never be null.
        919 */
        920goog.uri.utils.getParamValues = function(uri, keyEncoded) {
        921 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        922 var position = 0;
        923 var foundIndex;
        924 var result = [];
        925
        926 while ((foundIndex = goog.uri.utils.findParam_(
        927 uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
        928 // Find where this parameter ends, either the '&' or the end of the
        929 // query parameters.
        930 position = uri.indexOf('&', foundIndex);
        931 if (position < 0 || position > hashOrEndIndex) {
        932 position = hashOrEndIndex;
        933 }
        934
        935 // Progress forth to the end of the "key=" or "key&" substring.
        936 foundIndex += keyEncoded.length + 1;
        937 // Use substr, because it (unlike substring) will return empty string
        938 // if foundIndex > position.
        939 result.push(goog.string.urlDecode(uri.substr(
        940 foundIndex, position - foundIndex)));
        941 }
        942
        943 return result;
        944};
        945
        946
        947/**
        948 * Regexp to find trailing question marks and ampersands.
        949 * @type {RegExp}
        950 * @private
        951 */
        952goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
        953
        954
        955/**
        956 * Removes all instances of a query parameter.
        957 * @param {string} uri The URI to process. Must not contain a fragment.
        958 * @param {string} keyEncoded The URI-encoded key.
        959 * @return {string} The URI with all instances of the parameter removed.
        960 */
        961goog.uri.utils.removeParam = function(uri, keyEncoded) {
        962 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        963 var position = 0;
        964 var foundIndex;
        965 var buffer = [];
        966
        967 // Look for a query parameter.
        968 while ((foundIndex = goog.uri.utils.findParam_(
        969 uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
        970 // Get the portion of the query string up to, but not including, the ?
        971 // or & starting the parameter.
        972 buffer.push(uri.substring(position, foundIndex));
        973 // Progress to immediately after the '&'. If not found, go to the end.
        974 // Avoid including the hash mark.
        975 position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex,
        976 hashOrEndIndex);
        977 }
        978
        979 // Append everything that is remaining.
        980 buffer.push(uri.substr(position));
        981
        982 // Join the buffer, and remove trailing punctuation that remains.
        983 return buffer.join('').replace(
        984 goog.uri.utils.trailingQueryPunctuationRe_, '$1');
        985};
        986
        987
        988/**
        989 * Replaces all existing definitions of a parameter with a single definition.
        990 *
        991 * Repeated calls to this can exhibit quadratic behavior due to the need to
        992 * find existing instances and reconstruct the string, though it should be
        993 * limited given the 2kb limit. Consider using appendParams to append multiple
        994 * parameters in bulk.
        995 *
        996 * @param {string} uri The original URI, which may already have query data.
        997 * @param {string} keyEncoded The key, which must already be URI encoded.
        998 * @param {*} value The value, which will be stringized and encoded (assumed
        999 * not already to be encoded).
        1000 * @return {string} The URI with the query parameter added.
        1001 */
        1002goog.uri.utils.setParam = function(uri, keyEncoded, value) {
        1003 return goog.uri.utils.appendParam(
        1004 goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
        1005};
        1006
        1007
        1008/**
        1009 * Generates a URI path using a given URI and a path with checks to
        1010 * prevent consecutive "//". The baseUri passed in must not contain
        1011 * query or fragment identifiers. The path to append may not contain query or
        1012 * fragment identifiers.
        1013 *
        1014 * @param {string} baseUri URI to use as the base.
        1015 * @param {string} path Path to append.
        1016 * @return {string} Updated URI.
        1017 */
        1018goog.uri.utils.appendPath = function(baseUri, path) {
        1019 goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
        1020
        1021 // Remove any trailing '/'
        1022 if (goog.string.endsWith(baseUri, '/')) {
        1023 baseUri = baseUri.substr(0, baseUri.length - 1);
        1024 }
        1025 // Remove any leading '/'
        1026 if (goog.string.startsWith(path, '/')) {
        1027 path = path.substr(1);
        1028 }
        1029 return goog.string.buildString(baseUri, '/', path);
        1030};
        1031
        1032
        1033/**
        1034 * Standard supported query parameters.
        1035 * @enum {string}
        1036 */
        1037goog.uri.utils.StandardQueryParam = {
        1038
        1039 /** Unused parameter for unique-ifying. */
        1040 RANDOM: 'zx'
        1041};
        1042
        1043
        1044/**
        1045 * Sets the zx parameter of a URI to a random value.
        1046 * @param {string} uri Any URI.
        1047 * @return {string} That URI with the "zx" parameter added or replaced to
        1048 * contain a random string.
        1049 */
        1050goog.uri.utils.makeUnique = function(uri) {
        1051 return goog.uri.utils.setParam(uri,
        1052 goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
        1053};
        \ No newline at end of file +utils.js

        lib/goog/uri/utils.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Simple utilities for dealing with URI strings.
        17 *
        18 * This is intended to be a lightweight alternative to constructing goog.Uri
        19 * objects. Whereas goog.Uri adds several kilobytes to the binary regardless
        20 * of how much of its functionality you use, this is designed to be a set of
        21 * mostly-independent utilities so that the compiler includes only what is
        22 * necessary for the task. Estimated savings of porting is 5k pre-gzip and
        23 * 1.5k post-gzip. To ensure the savings remain, future developers should
        24 * avoid adding new functionality to existing functions, but instead create
        25 * new ones and factor out shared code.
        26 *
        27 * Many of these utilities have limited functionality, tailored to common
        28 * cases. The query parameter utilities assume that the parameter keys are
        29 * already encoded, since most keys are compile-time alphanumeric strings. The
        30 * query parameter mutation utilities also do not tolerate fragment identifiers.
        31 *
        32 * By design, these functions can be slower than goog.Uri equivalents.
        33 * Repeated calls to some of functions may be quadratic in behavior for IE,
        34 * although the effect is somewhat limited given the 2kb limit.
        35 *
        36 * One advantage of the limited functionality here is that this approach is
        37 * less sensitive to differences in URI encodings than goog.Uri, since these
        38 * functions operate on strings directly, rather than decoding them and
        39 * then re-encoding.
        40 *
        41 * Uses features of RFC 3986 for parsing/formatting URIs:
        42 * http://www.ietf.org/rfc/rfc3986.txt
        43 *
        44 * @author gboyer@google.com (Garrett Boyer) - The "lightened" design.
        45 */
        46
        47goog.provide('goog.uri.utils');
        48goog.provide('goog.uri.utils.ComponentIndex');
        49goog.provide('goog.uri.utils.QueryArray');
        50goog.provide('goog.uri.utils.QueryValue');
        51goog.provide('goog.uri.utils.StandardQueryParam');
        52
        53goog.require('goog.asserts');
        54goog.require('goog.string');
        55goog.require('goog.userAgent');
        56
        57
        58/**
        59 * Character codes inlined to avoid object allocations due to charCode.
        60 * @enum {number}
        61 * @private
        62 */
        63goog.uri.utils.CharCode_ = {
        64 AMPERSAND: 38,
        65 EQUAL: 61,
        66 HASH: 35,
        67 QUESTION: 63
        68};
        69
        70
        71/**
        72 * Builds a URI string from already-encoded parts.
        73 *
        74 * No encoding is performed. Any component may be omitted as either null or
        75 * undefined.
        76 *
        77 * @param {?string=} opt_scheme The scheme such as 'http'.
        78 * @param {?string=} opt_userInfo The user name before the '@'.
        79 * @param {?string=} opt_domain The domain such as 'www.google.com', already
        80 * URI-encoded.
        81 * @param {(string|number|null)=} opt_port The port number.
        82 * @param {?string=} opt_path The path, already URI-encoded. If it is not
        83 * empty, it must begin with a slash.
        84 * @param {?string=} opt_queryData The URI-encoded query data.
        85 * @param {?string=} opt_fragment The URI-encoded fragment identifier.
        86 * @return {string} The fully combined URI.
        87 */
        88goog.uri.utils.buildFromEncodedParts = function(opt_scheme, opt_userInfo,
        89 opt_domain, opt_port, opt_path, opt_queryData, opt_fragment) {
        90 var out = '';
        91
        92 if (opt_scheme) {
        93 out += opt_scheme + ':';
        94 }
        95
        96 if (opt_domain) {
        97 out += '//';
        98
        99 if (opt_userInfo) {
        100 out += opt_userInfo + '@';
        101 }
        102
        103 out += opt_domain;
        104
        105 if (opt_port) {
        106 out += ':' + opt_port;
        107 }
        108 }
        109
        110 if (opt_path) {
        111 out += opt_path;
        112 }
        113
        114 if (opt_queryData) {
        115 out += '?' + opt_queryData;
        116 }
        117
        118 if (opt_fragment) {
        119 out += '#' + opt_fragment;
        120 }
        121
        122 return out;
        123};
        124
        125
        126/**
        127 * A regular expression for breaking a URI into its component parts.
        128 *
        129 * {@link http://www.ietf.org/rfc/rfc3986.txt} says in Appendix B
        130 * As the "first-match-wins" algorithm is identical to the "greedy"
        131 * disambiguation method used by POSIX regular expressions, it is natural and
        132 * commonplace to use a regular expression for parsing the potential five
        133 * components of a URI reference.
        134 *
        135 * The following line is the regular expression for breaking-down a
        136 * well-formed URI reference into its components.
        137 *
        138 * <pre>
        139 * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
        140 * 12 3 4 5 6 7 8 9
        141 * </pre>
        142 *
        143 * The numbers in the second line above are only to assist readability; they
        144 * indicate the reference points for each subexpression (i.e., each paired
        145 * parenthesis). We refer to the value matched for subexpression <n> as $<n>.
        146 * For example, matching the above expression to
        147 * <pre>
        148 * http://www.ics.uci.edu/pub/ietf/uri/#Related
        149 * </pre>
        150 * results in the following subexpression matches:
        151 * <pre>
        152 * $1 = http:
        153 * $2 = http
        154 * $3 = //www.ics.uci.edu
        155 * $4 = www.ics.uci.edu
        156 * $5 = /pub/ietf/uri/
        157 * $6 = <undefined>
        158 * $7 = <undefined>
        159 * $8 = #Related
        160 * $9 = Related
        161 * </pre>
        162 * where <undefined> indicates that the component is not present, as is the
        163 * case for the query component in the above example. Therefore, we can
        164 * determine the value of the five components as
        165 * <pre>
        166 * scheme = $2
        167 * authority = $4
        168 * path = $5
        169 * query = $7
        170 * fragment = $9
        171 * </pre>
        172 *
        173 * The regular expression has been modified slightly to expose the
        174 * userInfo, domain, and port separately from the authority.
        175 * The modified version yields
        176 * <pre>
        177 * $1 = http scheme
        178 * $2 = <undefined> userInfo -\
        179 * $3 = www.ics.uci.edu domain | authority
        180 * $4 = <undefined> port -/
        181 * $5 = /pub/ietf/uri/ path
        182 * $6 = <undefined> query without ?
        183 * $7 = Related fragment without #
        184 * </pre>
        185 * @type {!RegExp}
        186 * @private
        187 */
        188goog.uri.utils.splitRe_ = new RegExp(
        189 '^' +
        190 '(?:' +
        191 '([^:/?#.]+)' + // scheme - ignore special characters
        192 // used by other URL parts such as :,
        193 // ?, /, #, and .
        194 ':)?' +
        195 '(?://' +
        196 '(?:([^/?#]*)@)?' + // userInfo
        197 '([^/#?]*?)' + // domain
        198 '(?::([0-9]+))?' + // port
        199 '(?=[/#?]|$)' + // authority-terminating character
        200 ')?' +
        201 '([^?#]+)?' + // path
        202 '(?:\\?([^#]*))?' + // query
        203 '(?:#(.*))?' + // fragment
        204 '$');
        205
        206
        207/**
        208 * The index of each URI component in the return value of goog.uri.utils.split.
        209 * @enum {number}
        210 */
        211goog.uri.utils.ComponentIndex = {
        212 SCHEME: 1,
        213 USER_INFO: 2,
        214 DOMAIN: 3,
        215 PORT: 4,
        216 PATH: 5,
        217 QUERY_DATA: 6,
        218 FRAGMENT: 7
        219};
        220
        221
        222/**
        223 * Splits a URI into its component parts.
        224 *
        225 * Each component can be accessed via the component indices; for example:
        226 * <pre>
        227 * goog.uri.utils.split(someStr)[goog.uri.utils.CompontentIndex.QUERY_DATA];
        228 * </pre>
        229 *
        230 * @param {string} uri The URI string to examine.
        231 * @return {!Array<string|undefined>} Each component still URI-encoded.
        232 * Each component that is present will contain the encoded value, whereas
        233 * components that are not present will be undefined or empty, depending
        234 * on the browser's regular expression implementation. Never null, since
        235 * arbitrary strings may still look like path names.
        236 */
        237goog.uri.utils.split = function(uri) {
        238 goog.uri.utils.phishingProtection_();
        239
        240 // See @return comment -- never null.
        241 return /** @type {!Array<string|undefined>} */ (
        242 uri.match(goog.uri.utils.splitRe_));
        243};
        244
        245
        246/**
        247 * Safari has a nasty bug where if you have an http URL with a username, e.g.,
        248 * http://evil.com%2F@google.com/
        249 * Safari will report that window.location.href is
        250 * http://evil.com/google.com/
        251 * so that anyone who tries to parse the domain of that URL will get
        252 * the wrong domain. We've seen exploits where people use this to trick
        253 * Safari into loading resources from evil domains.
        254 *
        255 * To work around this, we run a little "Safari phishing check", and throw
        256 * an exception if we see this happening.
        257 *
        258 * There is no convenient place to put this check. We apply it to
        259 * anyone doing URI parsing on Webkit. We're not happy about this, but
        260 * it fixes the problem.
        261 *
        262 * This should be removed once Safari fixes their bug.
        263 *
        264 * Exploit reported by Masato Kinugawa.
        265 *
        266 * @type {boolean}
        267 * @private
        268 */
        269goog.uri.utils.needsPhishingProtection_ = goog.userAgent.WEBKIT;
        270
        271
        272/**
        273 * Check to see if the user is being phished.
        274 * @private
        275 */
        276goog.uri.utils.phishingProtection_ = function() {
        277 if (goog.uri.utils.needsPhishingProtection_) {
        278 // Turn protection off, so that we don't recurse.
        279 goog.uri.utils.needsPhishingProtection_ = false;
        280
        281 // Use quoted access, just in case the user isn't using location externs.
        282 var location = goog.global['location'];
        283 if (location) {
        284 var href = location['href'];
        285 if (href) {
        286 var domain = goog.uri.utils.getDomain(href);
        287 if (domain && domain != location['hostname']) {
        288 // Phishing attack
        289 goog.uri.utils.needsPhishingProtection_ = true;
        290 throw Error();
        291 }
        292 }
        293 }
        294 }
        295};
        296
        297
        298/**
        299 * @param {?string} uri A possibly null string.
        300 * @param {boolean=} opt_preserveReserved If true, percent-encoding of RFC-3986
        301 * reserved characters will not be removed.
        302 * @return {?string} The string URI-decoded, or null if uri is null.
        303 * @private
        304 */
        305goog.uri.utils.decodeIfPossible_ = function(uri, opt_preserveReserved) {
        306 if (!uri) {
        307 return uri;
        308 }
        309
        310 return opt_preserveReserved ? decodeURI(uri) : decodeURIComponent(uri);
        311};
        312
        313
        314/**
        315 * Gets a URI component by index.
        316 *
        317 * It is preferred to use the getPathEncoded() variety of functions ahead,
        318 * since they are more readable.
        319 *
        320 * @param {goog.uri.utils.ComponentIndex} componentIndex The component index.
        321 * @param {string} uri The URI to examine.
        322 * @return {?string} The still-encoded component, or null if the component
        323 * is not present.
        324 * @private
        325 */
        326goog.uri.utils.getComponentByIndex_ = function(componentIndex, uri) {
        327 // Convert undefined, null, and empty string into null.
        328 return goog.uri.utils.split(uri)[componentIndex] || null;
        329};
        330
        331
        332/**
        333 * @param {string} uri The URI to examine.
        334 * @return {?string} The protocol or scheme, or null if none. Does not
        335 * include trailing colons or slashes.
        336 */
        337goog.uri.utils.getScheme = function(uri) {
        338 return goog.uri.utils.getComponentByIndex_(
        339 goog.uri.utils.ComponentIndex.SCHEME, uri);
        340};
        341
        342
        343/**
        344 * Gets the effective scheme for the URL. If the URL is relative then the
        345 * scheme is derived from the page's location.
        346 * @param {string} uri The URI to examine.
        347 * @return {string} The protocol or scheme, always lower case.
        348 */
        349goog.uri.utils.getEffectiveScheme = function(uri) {
        350 var scheme = goog.uri.utils.getScheme(uri);
        351 if (!scheme && self.location) {
        352 var protocol = self.location.protocol;
        353 scheme = protocol.substr(0, protocol.length - 1);
        354 }
        355 // NOTE: When called from a web worker in Firefox 3.5, location maybe null.
        356 // All other browsers with web workers support self.location from the worker.
        357 return scheme ? scheme.toLowerCase() : '';
        358};
        359
        360
        361/**
        362 * @param {string} uri The URI to examine.
        363 * @return {?string} The user name still encoded, or null if none.
        364 */
        365goog.uri.utils.getUserInfoEncoded = function(uri) {
        366 return goog.uri.utils.getComponentByIndex_(
        367 goog.uri.utils.ComponentIndex.USER_INFO, uri);
        368};
        369
        370
        371/**
        372 * @param {string} uri The URI to examine.
        373 * @return {?string} The decoded user info, or null if none.
        374 */
        375goog.uri.utils.getUserInfo = function(uri) {
        376 return goog.uri.utils.decodeIfPossible_(
        377 goog.uri.utils.getUserInfoEncoded(uri));
        378};
        379
        380
        381/**
        382 * @param {string} uri The URI to examine.
        383 * @return {?string} The domain name still encoded, or null if none.
        384 */
        385goog.uri.utils.getDomainEncoded = function(uri) {
        386 return goog.uri.utils.getComponentByIndex_(
        387 goog.uri.utils.ComponentIndex.DOMAIN, uri);
        388};
        389
        390
        391/**
        392 * @param {string} uri The URI to examine.
        393 * @return {?string} The decoded domain, or null if none.
        394 */
        395goog.uri.utils.getDomain = function(uri) {
        396 return goog.uri.utils.decodeIfPossible_(
        397 goog.uri.utils.getDomainEncoded(uri), true /* opt_preserveReserved */);
        398};
        399
        400
        401/**
        402 * @param {string} uri The URI to examine.
        403 * @return {?number} The port number, or null if none.
        404 */
        405goog.uri.utils.getPort = function(uri) {
        406 // Coerce to a number. If the result of getComponentByIndex_ is null or
        407 // non-numeric, the number coersion yields NaN. This will then return
        408 // null for all non-numeric cases (though also zero, which isn't a relevant
        409 // port number).
        410 return Number(goog.uri.utils.getComponentByIndex_(
        411 goog.uri.utils.ComponentIndex.PORT, uri)) || null;
        412};
        413
        414
        415/**
        416 * @param {string} uri The URI to examine.
        417 * @return {?string} The path still encoded, or null if none. Includes the
        418 * leading slash, if any.
        419 */
        420goog.uri.utils.getPathEncoded = function(uri) {
        421 return goog.uri.utils.getComponentByIndex_(
        422 goog.uri.utils.ComponentIndex.PATH, uri);
        423};
        424
        425
        426/**
        427 * @param {string} uri The URI to examine.
        428 * @return {?string} The decoded path, or null if none. Includes the leading
        429 * slash, if any.
        430 */
        431goog.uri.utils.getPath = function(uri) {
        432 return goog.uri.utils.decodeIfPossible_(
        433 goog.uri.utils.getPathEncoded(uri), true /* opt_preserveReserved */);
        434};
        435
        436
        437/**
        438 * @param {string} uri The URI to examine.
        439 * @return {?string} The query data still encoded, or null if none. Does not
        440 * include the question mark itself.
        441 */
        442goog.uri.utils.getQueryData = function(uri) {
        443 return goog.uri.utils.getComponentByIndex_(
        444 goog.uri.utils.ComponentIndex.QUERY_DATA, uri);
        445};
        446
        447
        448/**
        449 * @param {string} uri The URI to examine.
        450 * @return {?string} The fragment identifier, or null if none. Does not
        451 * include the hash mark itself.
        452 */
        453goog.uri.utils.getFragmentEncoded = function(uri) {
        454 // The hash mark may not appear in any other part of the URL.
        455 var hashIndex = uri.indexOf('#');
        456 return hashIndex < 0 ? null : uri.substr(hashIndex + 1);
        457};
        458
        459
        460/**
        461 * @param {string} uri The URI to examine.
        462 * @param {?string} fragment The encoded fragment identifier, or null if none.
        463 * Does not include the hash mark itself.
        464 * @return {string} The URI with the fragment set.
        465 */
        466goog.uri.utils.setFragmentEncoded = function(uri, fragment) {
        467 return goog.uri.utils.removeFragment(uri) + (fragment ? '#' + fragment : '');
        468};
        469
        470
        471/**
        472 * @param {string} uri The URI to examine.
        473 * @return {?string} The decoded fragment identifier, or null if none. Does
        474 * not include the hash mark.
        475 */
        476goog.uri.utils.getFragment = function(uri) {
        477 return goog.uri.utils.decodeIfPossible_(
        478 goog.uri.utils.getFragmentEncoded(uri));
        479};
        480
        481
        482/**
        483 * Extracts everything up to the port of the URI.
        484 * @param {string} uri The URI string.
        485 * @return {string} Everything up to and including the port.
        486 */
        487goog.uri.utils.getHost = function(uri) {
        488 var pieces = goog.uri.utils.split(uri);
        489 return goog.uri.utils.buildFromEncodedParts(
        490 pieces[goog.uri.utils.ComponentIndex.SCHEME],
        491 pieces[goog.uri.utils.ComponentIndex.USER_INFO],
        492 pieces[goog.uri.utils.ComponentIndex.DOMAIN],
        493 pieces[goog.uri.utils.ComponentIndex.PORT]);
        494};
        495
        496
        497/**
        498 * Extracts the path of the URL and everything after.
        499 * @param {string} uri The URI string.
        500 * @return {string} The URI, starting at the path and including the query
        501 * parameters and fragment identifier.
        502 */
        503goog.uri.utils.getPathAndAfter = function(uri) {
        504 var pieces = goog.uri.utils.split(uri);
        505 return goog.uri.utils.buildFromEncodedParts(null, null, null, null,
        506 pieces[goog.uri.utils.ComponentIndex.PATH],
        507 pieces[goog.uri.utils.ComponentIndex.QUERY_DATA],
        508 pieces[goog.uri.utils.ComponentIndex.FRAGMENT]);
        509};
        510
        511
        512/**
        513 * Gets the URI with the fragment identifier removed.
        514 * @param {string} uri The URI to examine.
        515 * @return {string} Everything preceding the hash mark.
        516 */
        517goog.uri.utils.removeFragment = function(uri) {
        518 // The hash mark may not appear in any other part of the URL.
        519 var hashIndex = uri.indexOf('#');
        520 return hashIndex < 0 ? uri : uri.substr(0, hashIndex);
        521};
        522
        523
        524/**
        525 * Ensures that two URI's have the exact same domain, scheme, and port.
        526 *
        527 * Unlike the version in goog.Uri, this checks protocol, and therefore is
        528 * suitable for checking against the browser's same-origin policy.
        529 *
        530 * @param {string} uri1 The first URI.
        531 * @param {string} uri2 The second URI.
        532 * @return {boolean} Whether they have the same scheme, domain and port.
        533 */
        534goog.uri.utils.haveSameDomain = function(uri1, uri2) {
        535 var pieces1 = goog.uri.utils.split(uri1);
        536 var pieces2 = goog.uri.utils.split(uri2);
        537 return pieces1[goog.uri.utils.ComponentIndex.DOMAIN] ==
        538 pieces2[goog.uri.utils.ComponentIndex.DOMAIN] &&
        539 pieces1[goog.uri.utils.ComponentIndex.SCHEME] ==
        540 pieces2[goog.uri.utils.ComponentIndex.SCHEME] &&
        541 pieces1[goog.uri.utils.ComponentIndex.PORT] ==
        542 pieces2[goog.uri.utils.ComponentIndex.PORT];
        543};
        544
        545
        546/**
        547 * Asserts that there are no fragment or query identifiers, only in uncompiled
        548 * mode.
        549 * @param {string} uri The URI to examine.
        550 * @private
        551 */
        552goog.uri.utils.assertNoFragmentsOrQueries_ = function(uri) {
        553 // NOTE: would use goog.asserts here, but jscompiler doesn't know that
        554 // indexOf has no side effects.
        555 if (goog.DEBUG && (uri.indexOf('#') >= 0 || uri.indexOf('?') >= 0)) {
        556 throw Error('goog.uri.utils: Fragment or query identifiers are not ' +
        557 'supported: [' + uri + ']');
        558 }
        559};
        560
        561
        562/**
        563 * Supported query parameter values by the parameter serializing utilities.
        564 *
        565 * If a value is null or undefined, the key-value pair is skipped, as an easy
        566 * way to omit parameters conditionally. Non-array parameters are converted
        567 * to a string and URI encoded. Array values are expanded into multiple
        568 * &key=value pairs, with each element stringized and URI-encoded.
        569 *
        570 * @typedef {*}
        571 */
        572goog.uri.utils.QueryValue;
        573
        574
        575/**
        576 * An array representing a set of query parameters with alternating keys
        577 * and values.
        578 *
        579 * Keys are assumed to be URI encoded already and live at even indices. See
        580 * goog.uri.utils.QueryValue for details on how parameter values are encoded.
        581 *
        582 * Example:
        583 * <pre>
        584 * var data = [
        585 * // Simple param: ?name=BobBarker
        586 * 'name', 'BobBarker',
        587 * // Conditional param -- may be omitted entirely.
        588 * 'specialDietaryNeeds', hasDietaryNeeds() ? getDietaryNeeds() : null,
        589 * // Multi-valued param: &house=LosAngeles&house=NewYork&house=null
        590 * 'house', ['LosAngeles', 'NewYork', null]
        591 * ];
        592 * </pre>
        593 *
        594 * @typedef {!Array<string|goog.uri.utils.QueryValue>}
        595 */
        596goog.uri.utils.QueryArray;
        597
        598
        599/**
        600 * Parses encoded query parameters and calls callback function for every
        601 * parameter found in the string.
        602 *
        603 * Missing value of parameter (e.g. “…&key&…”) is treated as if the value was an
        604 * empty string. Keys may be empty strings (e.g. “…&=value&…”) which also means
        605 * that “…&=&…” and “…&&…” will result in an empty key and value.
        606 *
        607 * @param {string} encodedQuery Encoded query string excluding question mark at
        608 * the beginning.
        609 * @param {function(string, string)} callback Function called for every
        610 * parameter found in query string. The first argument (name) will not be
        611 * urldecoded (so the function is consistent with buildQueryData), but the
        612 * second will. If the parameter has no value (i.e. “=” was not present)
        613 * the second argument (value) will be an empty string.
        614 */
        615goog.uri.utils.parseQueryData = function(encodedQuery, callback) {
        616 var pairs = encodedQuery.split('&');
        617 for (var i = 0; i < pairs.length; i++) {
        618 var indexOfEquals = pairs[i].indexOf('=');
        619 var name = null;
        620 var value = null;
        621 if (indexOfEquals >= 0) {
        622 name = pairs[i].substring(0, indexOfEquals);
        623 value = pairs[i].substring(indexOfEquals + 1);
        624 } else {
        625 name = pairs[i];
        626 }
        627 callback(name, value ? goog.string.urlDecode(value) : '');
        628 }
        629};
        630
        631
        632/**
        633 * Appends a URI and query data in a string buffer with special preconditions.
        634 *
        635 * Internal implementation utility, performing very few object allocations.
        636 *
        637 * @param {!Array<string|undefined>} buffer A string buffer. The first element
        638 * must be the base URI, and may have a fragment identifier. If the array
        639 * contains more than one element, the second element must be an ampersand,
        640 * and may be overwritten, depending on the base URI. Undefined elements
        641 * are treated as empty-string.
        642 * @return {string} The concatenated URI and query data.
        643 * @private
        644 */
        645goog.uri.utils.appendQueryData_ = function(buffer) {
        646 if (buffer[1]) {
        647 // At least one query parameter was added. We need to check the
        648 // punctuation mark, which is currently an ampersand, and also make sure
        649 // there aren't any interfering fragment identifiers.
        650 var baseUri = /** @type {string} */ (buffer[0]);
        651 var hashIndex = baseUri.indexOf('#');
        652 if (hashIndex >= 0) {
        653 // Move the fragment off the base part of the URI into the end.
        654 buffer.push(baseUri.substr(hashIndex));
        655 buffer[0] = baseUri = baseUri.substr(0, hashIndex);
        656 }
        657 var questionIndex = baseUri.indexOf('?');
        658 if (questionIndex < 0) {
        659 // No question mark, so we need a question mark instead of an ampersand.
        660 buffer[1] = '?';
        661 } else if (questionIndex == baseUri.length - 1) {
        662 // Question mark is the very last character of the existing URI, so don't
        663 // append an additional delimiter.
        664 buffer[1] = undefined;
        665 }
        666 }
        667
        668 return buffer.join('');
        669};
        670
        671
        672/**
        673 * Appends key=value pairs to an array, supporting multi-valued objects.
        674 * @param {string} key The key prefix.
        675 * @param {goog.uri.utils.QueryValue} value The value to serialize.
        676 * @param {!Array<string>} pairs The array to which the 'key=value' strings
        677 * should be appended.
        678 * @private
        679 */
        680goog.uri.utils.appendKeyValuePairs_ = function(key, value, pairs) {
        681 if (goog.isArray(value)) {
        682 // Convince the compiler it's an array.
        683 goog.asserts.assertArray(value);
        684 for (var j = 0; j < value.length; j++) {
        685 // Convert to string explicitly, to short circuit the null and array
        686 // logic in this function -- this ensures that null and undefined get
        687 // written as literal 'null' and 'undefined', and arrays don't get
        688 // expanded out but instead encoded in the default way.
        689 goog.uri.utils.appendKeyValuePairs_(key, String(value[j]), pairs);
        690 }
        691 } else if (value != null) {
        692 // Skip a top-level null or undefined entirely.
        693 pairs.push('&', key,
        694 // Check for empty string. Zero gets encoded into the url as literal
        695 // strings. For empty string, skip the equal sign, to be consistent
        696 // with UriBuilder.java.
        697 value === '' ? '' : '=',
        698 goog.string.urlEncode(value));
        699 }
        700};
        701
        702
        703/**
        704 * Builds a buffer of query data from a sequence of alternating keys and values.
        705 *
        706 * @param {!Array<string|undefined>} buffer A string buffer to append to. The
        707 * first element appended will be an '&', and may be replaced by the caller.
        708 * @param {!goog.uri.utils.QueryArray|!Arguments} keysAndValues An array with
        709 * alternating keys and values -- see the typedef.
        710 * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
        711 * @return {!Array<string|undefined>} The buffer argument.
        712 * @private
        713 */
        714goog.uri.utils.buildQueryDataBuffer_ = function(
        715 buffer, keysAndValues, opt_startIndex) {
        716 goog.asserts.assert(Math.max(keysAndValues.length - (opt_startIndex || 0),
        717 0) % 2 == 0, 'goog.uri.utils: Key/value lists must be even in length.');
        718
        719 for (var i = opt_startIndex || 0; i < keysAndValues.length; i += 2) {
        720 goog.uri.utils.appendKeyValuePairs_(
        721 keysAndValues[i], keysAndValues[i + 1], buffer);
        722 }
        723
        724 return buffer;
        725};
        726
        727
        728/**
        729 * Builds a query data string from a sequence of alternating keys and values.
        730 * Currently generates "&key&" for empty args.
        731 *
        732 * @param {goog.uri.utils.QueryArray} keysAndValues Alternating keys and
        733 * values. See the typedef.
        734 * @param {number=} opt_startIndex A start offset into the arary, defaults to 0.
        735 * @return {string} The encoded query string, in the form 'a=1&b=2'.
        736 */
        737goog.uri.utils.buildQueryData = function(keysAndValues, opt_startIndex) {
        738 var buffer = goog.uri.utils.buildQueryDataBuffer_(
        739 [], keysAndValues, opt_startIndex);
        740 buffer[0] = ''; // Remove the leading ampersand.
        741 return buffer.join('');
        742};
        743
        744
        745/**
        746 * Builds a buffer of query data from a map.
        747 *
        748 * @param {!Array<string|undefined>} buffer A string buffer to append to. The
        749 * first element appended will be an '&', and may be replaced by the caller.
        750 * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys
        751 * are URI-encoded parameter keys, and the values conform to the contract
        752 * specified in the goog.uri.utils.QueryValue typedef.
        753 * @return {!Array<string|undefined>} The buffer argument.
        754 * @private
        755 */
        756goog.uri.utils.buildQueryDataBufferFromMap_ = function(buffer, map) {
        757 for (var key in map) {
        758 goog.uri.utils.appendKeyValuePairs_(key, map[key], buffer);
        759 }
        760
        761 return buffer;
        762};
        763
        764
        765/**
        766 * Builds a query data string from a map.
        767 * Currently generates "&key&" for empty args.
        768 *
        769 * @param {!Object<string, goog.uri.utils.QueryValue>} map An object where keys
        770 * are URI-encoded parameter keys, and the values are arbitrary types
        771 * or arrays. Keys with a null value are dropped.
        772 * @return {string} The encoded query string, in the form 'a=1&b=2'.
        773 */
        774goog.uri.utils.buildQueryDataFromMap = function(map) {
        775 var buffer = goog.uri.utils.buildQueryDataBufferFromMap_([], map);
        776 buffer[0] = '';
        777 return buffer.join('');
        778};
        779
        780
        781/**
        782 * Appends URI parameters to an existing URI.
        783 *
        784 * The variable arguments may contain alternating keys and values. Keys are
        785 * assumed to be already URI encoded. The values should not be URI-encoded,
        786 * and will instead be encoded by this function.
        787 * <pre>
        788 * appendParams('http://www.foo.com?existing=true',
        789 * 'key1', 'value1',
        790 * 'key2', 'value?willBeEncoded',
        791 * 'key3', ['valueA', 'valueB', 'valueC'],
        792 * 'key4', null);
        793 * result: 'http://www.foo.com?existing=true&' +
        794 * 'key1=value1&' +
        795 * 'key2=value%3FwillBeEncoded&' +
        796 * 'key3=valueA&key3=valueB&key3=valueC'
        797 * </pre>
        798 *
        799 * A single call to this function will not exhibit quadratic behavior in IE,
        800 * whereas multiple repeated calls may, although the effect is limited by
        801 * fact that URL's generally can't exceed 2kb.
        802 *
        803 * @param {string} uri The original URI, which may already have query data.
        804 * @param {...(goog.uri.utils.QueryArray|string|goog.uri.utils.QueryValue)} var_args
        805 * An array or argument list conforming to goog.uri.utils.QueryArray.
        806 * @return {string} The URI with all query parameters added.
        807 */
        808goog.uri.utils.appendParams = function(uri, var_args) {
        809 return goog.uri.utils.appendQueryData_(
        810 arguments.length == 2 ?
        811 goog.uri.utils.buildQueryDataBuffer_([uri], arguments[1], 0) :
        812 goog.uri.utils.buildQueryDataBuffer_([uri], arguments, 1));
        813};
        814
        815
        816/**
        817 * Appends query parameters from a map.
        818 *
        819 * @param {string} uri The original URI, which may already have query data.
        820 * @param {!Object<goog.uri.utils.QueryValue>} map An object where keys are
        821 * URI-encoded parameter keys, and the values are arbitrary types or arrays.
        822 * Keys with a null value are dropped.
        823 * @return {string} The new parameters.
        824 */
        825goog.uri.utils.appendParamsFromMap = function(uri, map) {
        826 return goog.uri.utils.appendQueryData_(
        827 goog.uri.utils.buildQueryDataBufferFromMap_([uri], map));
        828};
        829
        830
        831/**
        832 * Appends a single URI parameter.
        833 *
        834 * Repeated calls to this can exhibit quadratic behavior in IE6 due to the
        835 * way string append works, though it should be limited given the 2kb limit.
        836 *
        837 * @param {string} uri The original URI, which may already have query data.
        838 * @param {string} key The key, which must already be URI encoded.
        839 * @param {*=} opt_value The value, which will be stringized and encoded
        840 * (assumed not already to be encoded). If omitted, undefined, or null, the
        841 * key will be added as a valueless parameter.
        842 * @return {string} The URI with the query parameter added.
        843 */
        844goog.uri.utils.appendParam = function(uri, key, opt_value) {
        845 var paramArr = [uri, '&', key];
        846 if (goog.isDefAndNotNull(opt_value)) {
        847 paramArr.push('=', goog.string.urlEncode(opt_value));
        848 }
        849 return goog.uri.utils.appendQueryData_(paramArr);
        850};
        851
        852
        853/**
        854 * Finds the next instance of a query parameter with the specified name.
        855 *
        856 * Does not instantiate any objects.
        857 *
        858 * @param {string} uri The URI to search. May contain a fragment identifier
        859 * if opt_hashIndex is specified.
        860 * @param {number} startIndex The index to begin searching for the key at. A
        861 * match may be found even if this is one character after the ampersand.
        862 * @param {string} keyEncoded The URI-encoded key.
        863 * @param {number} hashOrEndIndex Index to stop looking at. If a hash
        864 * mark is present, it should be its index, otherwise it should be the
        865 * length of the string.
        866 * @return {number} The position of the first character in the key's name,
        867 * immediately after either a question mark or a dot.
        868 * @private
        869 */
        870goog.uri.utils.findParam_ = function(
        871 uri, startIndex, keyEncoded, hashOrEndIndex) {
        872 var index = startIndex;
        873 var keyLength = keyEncoded.length;
        874
        875 // Search for the key itself and post-filter for surronuding punctuation,
        876 // rather than expensively building a regexp.
        877 while ((index = uri.indexOf(keyEncoded, index)) >= 0 &&
        878 index < hashOrEndIndex) {
        879 var precedingChar = uri.charCodeAt(index - 1);
        880 // Ensure that the preceding character is '&' or '?'.
        881 if (precedingChar == goog.uri.utils.CharCode_.AMPERSAND ||
        882 precedingChar == goog.uri.utils.CharCode_.QUESTION) {
        883 // Ensure the following character is '&', '=', '#', or NaN
        884 // (end of string).
        885 var followingChar = uri.charCodeAt(index + keyLength);
        886 if (!followingChar ||
        887 followingChar == goog.uri.utils.CharCode_.EQUAL ||
        888 followingChar == goog.uri.utils.CharCode_.AMPERSAND ||
        889 followingChar == goog.uri.utils.CharCode_.HASH) {
        890 return index;
        891 }
        892 }
        893 index += keyLength + 1;
        894 }
        895
        896 return -1;
        897};
        898
        899
        900/**
        901 * Regular expression for finding a hash mark or end of string.
        902 * @type {RegExp}
        903 * @private
        904 */
        905goog.uri.utils.hashOrEndRe_ = /#|$/;
        906
        907
        908/**
        909 * Determines if the URI contains a specific key.
        910 *
        911 * Performs no object instantiations.
        912 *
        913 * @param {string} uri The URI to process. May contain a fragment
        914 * identifier.
        915 * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
        916 * @return {boolean} Whether the key is present.
        917 */
        918goog.uri.utils.hasParam = function(uri, keyEncoded) {
        919 return goog.uri.utils.findParam_(uri, 0, keyEncoded,
        920 uri.search(goog.uri.utils.hashOrEndRe_)) >= 0;
        921};
        922
        923
        924/**
        925 * Gets the first value of a query parameter.
        926 * @param {string} uri The URI to process. May contain a fragment.
        927 * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
        928 * @return {?string} The first value of the parameter (URI-decoded), or null
        929 * if the parameter is not found.
        930 */
        931goog.uri.utils.getParamValue = function(uri, keyEncoded) {
        932 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        933 var foundIndex = goog.uri.utils.findParam_(
        934 uri, 0, keyEncoded, hashOrEndIndex);
        935
        936 if (foundIndex < 0) {
        937 return null;
        938 } else {
        939 var endPosition = uri.indexOf('&', foundIndex);
        940 if (endPosition < 0 || endPosition > hashOrEndIndex) {
        941 endPosition = hashOrEndIndex;
        942 }
        943 // Progress forth to the end of the "key=" or "key&" substring.
        944 foundIndex += keyEncoded.length + 1;
        945 // Use substr, because it (unlike substring) will return empty string
        946 // if foundIndex > endPosition.
        947 return goog.string.urlDecode(
        948 uri.substr(foundIndex, endPosition - foundIndex));
        949 }
        950};
        951
        952
        953/**
        954 * Gets all values of a query parameter.
        955 * @param {string} uri The URI to process. May contain a fragment.
        956 * @param {string} keyEncoded The URI-encoded key. Case-sensitive.
        957 * @return {!Array<string>} All URI-decoded values with the given key.
        958 * If the key is not found, this will have length 0, but never be null.
        959 */
        960goog.uri.utils.getParamValues = function(uri, keyEncoded) {
        961 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        962 var position = 0;
        963 var foundIndex;
        964 var result = [];
        965
        966 while ((foundIndex = goog.uri.utils.findParam_(
        967 uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
        968 // Find where this parameter ends, either the '&' or the end of the
        969 // query parameters.
        970 position = uri.indexOf('&', foundIndex);
        971 if (position < 0 || position > hashOrEndIndex) {
        972 position = hashOrEndIndex;
        973 }
        974
        975 // Progress forth to the end of the "key=" or "key&" substring.
        976 foundIndex += keyEncoded.length + 1;
        977 // Use substr, because it (unlike substring) will return empty string
        978 // if foundIndex > position.
        979 result.push(goog.string.urlDecode(uri.substr(
        980 foundIndex, position - foundIndex)));
        981 }
        982
        983 return result;
        984};
        985
        986
        987/**
        988 * Regexp to find trailing question marks and ampersands.
        989 * @type {RegExp}
        990 * @private
        991 */
        992goog.uri.utils.trailingQueryPunctuationRe_ = /[?&]($|#)/;
        993
        994
        995/**
        996 * Removes all instances of a query parameter.
        997 * @param {string} uri The URI to process. Must not contain a fragment.
        998 * @param {string} keyEncoded The URI-encoded key.
        999 * @return {string} The URI with all instances of the parameter removed.
        1000 */
        1001goog.uri.utils.removeParam = function(uri, keyEncoded) {
        1002 var hashOrEndIndex = uri.search(goog.uri.utils.hashOrEndRe_);
        1003 var position = 0;
        1004 var foundIndex;
        1005 var buffer = [];
        1006
        1007 // Look for a query parameter.
        1008 while ((foundIndex = goog.uri.utils.findParam_(
        1009 uri, position, keyEncoded, hashOrEndIndex)) >= 0) {
        1010 // Get the portion of the query string up to, but not including, the ?
        1011 // or & starting the parameter.
        1012 buffer.push(uri.substring(position, foundIndex));
        1013 // Progress to immediately after the '&'. If not found, go to the end.
        1014 // Avoid including the hash mark.
        1015 position = Math.min((uri.indexOf('&', foundIndex) + 1) || hashOrEndIndex,
        1016 hashOrEndIndex);
        1017 }
        1018
        1019 // Append everything that is remaining.
        1020 buffer.push(uri.substr(position));
        1021
        1022 // Join the buffer, and remove trailing punctuation that remains.
        1023 return buffer.join('').replace(
        1024 goog.uri.utils.trailingQueryPunctuationRe_, '$1');
        1025};
        1026
        1027
        1028/**
        1029 * Replaces all existing definitions of a parameter with a single definition.
        1030 *
        1031 * Repeated calls to this can exhibit quadratic behavior due to the need to
        1032 * find existing instances and reconstruct the string, though it should be
        1033 * limited given the 2kb limit. Consider using appendParams to append multiple
        1034 * parameters in bulk.
        1035 *
        1036 * @param {string} uri The original URI, which may already have query data.
        1037 * @param {string} keyEncoded The key, which must already be URI encoded.
        1038 * @param {*} value The value, which will be stringized and encoded (assumed
        1039 * not already to be encoded).
        1040 * @return {string} The URI with the query parameter added.
        1041 */
        1042goog.uri.utils.setParam = function(uri, keyEncoded, value) {
        1043 return goog.uri.utils.appendParam(
        1044 goog.uri.utils.removeParam(uri, keyEncoded), keyEncoded, value);
        1045};
        1046
        1047
        1048/**
        1049 * Generates a URI path using a given URI and a path with checks to
        1050 * prevent consecutive "//". The baseUri passed in must not contain
        1051 * query or fragment identifiers. The path to append may not contain query or
        1052 * fragment identifiers.
        1053 *
        1054 * @param {string} baseUri URI to use as the base.
        1055 * @param {string} path Path to append.
        1056 * @return {string} Updated URI.
        1057 */
        1058goog.uri.utils.appendPath = function(baseUri, path) {
        1059 goog.uri.utils.assertNoFragmentsOrQueries_(baseUri);
        1060
        1061 // Remove any trailing '/'
        1062 if (goog.string.endsWith(baseUri, '/')) {
        1063 baseUri = baseUri.substr(0, baseUri.length - 1);
        1064 }
        1065 // Remove any leading '/'
        1066 if (goog.string.startsWith(path, '/')) {
        1067 path = path.substr(1);
        1068 }
        1069 return goog.string.buildString(baseUri, '/', path);
        1070};
        1071
        1072
        1073/**
        1074 * Replaces the path.
        1075 * @param {string} uri URI to use as the base.
        1076 * @param {string} path New path.
        1077 * @return {string} Updated URI.
        1078 */
        1079goog.uri.utils.setPath = function(uri, path) {
        1080 // Add any missing '/'.
        1081 if (!goog.string.startsWith(path, '/')) {
        1082 path = '/' + path;
        1083 }
        1084 var parts = goog.uri.utils.split(uri);
        1085 return goog.uri.utils.buildFromEncodedParts(
        1086 parts[goog.uri.utils.ComponentIndex.SCHEME],
        1087 parts[goog.uri.utils.ComponentIndex.USER_INFO],
        1088 parts[goog.uri.utils.ComponentIndex.DOMAIN],
        1089 parts[goog.uri.utils.ComponentIndex.PORT],
        1090 path,
        1091 parts[goog.uri.utils.ComponentIndex.QUERY_DATA],
        1092 parts[goog.uri.utils.ComponentIndex.FRAGMENT]);
        1093};
        1094
        1095
        1096/**
        1097 * Standard supported query parameters.
        1098 * @enum {string}
        1099 */
        1100goog.uri.utils.StandardQueryParam = {
        1101
        1102 /** Unused parameter for unique-ifying. */
        1103 RANDOM: 'zx'
        1104};
        1105
        1106
        1107/**
        1108 * Sets the zx parameter of a URI to a random value.
        1109 * @param {string} uri Any URI.
        1110 * @return {string} That URI with the "zx" parameter added or replaced to
        1111 * contain a random string.
        1112 */
        1113goog.uri.utils.makeUnique = function(uri) {
        1114 return goog.uri.utils.setParam(uri,
        1115 goog.uri.utils.StandardQueryParam.RANDOM, goog.string.getRandomString());
        1116};
        \ No newline at end of file diff --git a/docs/source/lib/goog/useragent/product.js.src.html b/docs/source/lib/goog/useragent/product.js.src.html index 1ff8b1c..b7b12f0 100644 --- a/docs/source/lib/goog/useragent/product.js.src.html +++ b/docs/source/lib/goog/useragent/product.js.src.html @@ -1 +1 @@ -product.js

        lib/goog/useragent/product.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Detects the specific browser and not just the rendering engine.
        17 *
        18 */
        19
        20goog.provide('goog.userAgent.product');
        21
        22goog.require('goog.userAgent');
        23
        24
        25/**
        26 * @define {boolean} Whether the code is running on the Firefox web browser.
        27 */
        28goog.define('goog.userAgent.product.ASSUME_FIREFOX', false);
        29
        30
        31/**
        32 * @define {boolean} Whether the code is running on the Camino web browser.
        33 */
        34goog.define('goog.userAgent.product.ASSUME_CAMINO', false);
        35
        36
        37/**
        38 * @define {boolean} Whether we know at compile-time that the product is an
        39 * iPhone.
        40 */
        41goog.define('goog.userAgent.product.ASSUME_IPHONE', false);
        42
        43
        44/**
        45 * @define {boolean} Whether we know at compile-time that the product is an
        46 * iPad.
        47 */
        48goog.define('goog.userAgent.product.ASSUME_IPAD', false);
        49
        50
        51/**
        52 * @define {boolean} Whether we know at compile-time that the product is an
        53 * Android phone.
        54 */
        55goog.define('goog.userAgent.product.ASSUME_ANDROID', false);
        56
        57
        58/**
        59 * @define {boolean} Whether the code is running on the Chrome web browser.
        60 */
        61goog.define('goog.userAgent.product.ASSUME_CHROME', false);
        62
        63
        64/**
        65 * @define {boolean} Whether the code is running on the Safari web browser.
        66 */
        67goog.define('goog.userAgent.product.ASSUME_SAFARI', false);
        68
        69
        70/**
        71 * Whether we know the product type at compile-time.
        72 * @type {boolean}
        73 * @private
        74 */
        75goog.userAgent.product.PRODUCT_KNOWN_ =
        76 goog.userAgent.ASSUME_IE ||
        77 goog.userAgent.ASSUME_OPERA ||
        78 goog.userAgent.product.ASSUME_FIREFOX ||
        79 goog.userAgent.product.ASSUME_CAMINO ||
        80 goog.userAgent.product.ASSUME_IPHONE ||
        81 goog.userAgent.product.ASSUME_IPAD ||
        82 goog.userAgent.product.ASSUME_ANDROID ||
        83 goog.userAgent.product.ASSUME_CHROME ||
        84 goog.userAgent.product.ASSUME_SAFARI;
        85
        86
        87/**
        88 * Right now we just focus on Tier 1-3 browsers at:
        89 * http://wiki/Nonconf/ProductPlatformGuidelines
        90 * As well as the YUI grade A browsers at:
        91 * http://developer.yahoo.com/yui/articles/gbs/
        92 *
        93 * @private
        94 */
        95goog.userAgent.product.init_ = function() {
        96
        97 /**
        98 * Whether the code is running on the Firefox web browser.
        99 * @type {boolean}
        100 * @private
        101 */
        102 goog.userAgent.product.detectedFirefox_ = false;
        103
        104 /**
        105 * Whether the code is running on the Camino web browser.
        106 * @type {boolean}
        107 * @private
        108 */
        109 goog.userAgent.product.detectedCamino_ = false;
        110
        111 /**
        112 * Whether the code is running on an iPhone or iPod touch.
        113 * @type {boolean}
        114 * @private
        115 */
        116 goog.userAgent.product.detectedIphone_ = false;
        117
        118 /**
        119 * Whether the code is running on an iPad
        120 * @type {boolean}
        121 * @private
        122 */
        123 goog.userAgent.product.detectedIpad_ = false;
        124
        125 /**
        126 * Whether the code is running on the default browser on an Android phone.
        127 * @type {boolean}
        128 * @private
        129 */
        130 goog.userAgent.product.detectedAndroid_ = false;
        131
        132 /**
        133 * Whether the code is running on the Chrome web browser.
        134 * @type {boolean}
        135 * @private
        136 */
        137 goog.userAgent.product.detectedChrome_ = false;
        138
        139 /**
        140 * Whether the code is running on the Safari web browser.
        141 * @type {boolean}
        142 * @private
        143 */
        144 goog.userAgent.product.detectedSafari_ = false;
        145
        146 var ua = goog.userAgent.getUserAgentString();
        147 if (!ua) {
        148 return;
        149 }
        150
        151 // The order of the if-statements in the following code is important.
        152 // For example, in the WebKit section, we put Chrome in front of Safari
        153 // because the string 'Safari' is present on both of those browsers'
        154 // userAgent strings as well as the string we are looking for.
        155 // The idea is to prevent accidental detection of more than one client.
        156
        157 if (ua.indexOf('Firefox') != -1) {
        158 goog.userAgent.product.detectedFirefox_ = true;
        159 } else if (ua.indexOf('Camino') != -1) {
        160 goog.userAgent.product.detectedCamino_ = true;
        161 } else if (ua.indexOf('iPhone') != -1 || ua.indexOf('iPod') != -1) {
        162 goog.userAgent.product.detectedIphone_ = true;
        163 } else if (ua.indexOf('iPad') != -1) {
        164 goog.userAgent.product.detectedIpad_ = true;
        165 } else if (ua.indexOf('Android') != -1) {
        166 goog.userAgent.product.detectedAndroid_ = true;
        167 } else if (ua.indexOf('Chrome') != -1) {
        168 goog.userAgent.product.detectedChrome_ = true;
        169 } else if (ua.indexOf('Safari') != -1) {
        170 goog.userAgent.product.detectedSafari_ = true;
        171 }
        172};
        173
        174if (!goog.userAgent.product.PRODUCT_KNOWN_) {
        175 goog.userAgent.product.init_();
        176}
        177
        178
        179/**
        180 * Whether the code is running on the Opera web browser.
        181 * @type {boolean}
        182 */
        183goog.userAgent.product.OPERA = goog.userAgent.OPERA;
        184
        185
        186/**
        187 * Whether the code is running on an IE web browser.
        188 * @type {boolean}
        189 */
        190goog.userAgent.product.IE = goog.userAgent.IE;
        191
        192
        193/**
        194 * Whether the code is running on the Firefox web browser.
        195 * @type {boolean}
        196 */
        197goog.userAgent.product.FIREFOX = goog.userAgent.product.PRODUCT_KNOWN_ ?
        198 goog.userAgent.product.ASSUME_FIREFOX :
        199 goog.userAgent.product.detectedFirefox_;
        200
        201
        202/**
        203 * Whether the code is running on the Camino web browser.
        204 * @type {boolean}
        205 */
        206goog.userAgent.product.CAMINO = goog.userAgent.product.PRODUCT_KNOWN_ ?
        207 goog.userAgent.product.ASSUME_CAMINO :
        208 goog.userAgent.product.detectedCamino_;
        209
        210
        211/**
        212 * Whether the code is running on an iPhone or iPod touch.
        213 * @type {boolean}
        214 */
        215goog.userAgent.product.IPHONE = goog.userAgent.product.PRODUCT_KNOWN_ ?
        216 goog.userAgent.product.ASSUME_IPHONE :
        217 goog.userAgent.product.detectedIphone_;
        218
        219
        220/**
        221 * Whether the code is running on an iPad.
        222 * @type {boolean}
        223 */
        224goog.userAgent.product.IPAD = goog.userAgent.product.PRODUCT_KNOWN_ ?
        225 goog.userAgent.product.ASSUME_IPAD :
        226 goog.userAgent.product.detectedIpad_;
        227
        228
        229/**
        230 * Whether the code is running on the default browser on an Android phone.
        231 * @type {boolean}
        232 */
        233goog.userAgent.product.ANDROID = goog.userAgent.product.PRODUCT_KNOWN_ ?
        234 goog.userAgent.product.ASSUME_ANDROID :
        235 goog.userAgent.product.detectedAndroid_;
        236
        237
        238/**
        239 * Whether the code is running on the Chrome web browser.
        240 * @type {boolean}
        241 */
        242goog.userAgent.product.CHROME = goog.userAgent.product.PRODUCT_KNOWN_ ?
        243 goog.userAgent.product.ASSUME_CHROME :
        244 goog.userAgent.product.detectedChrome_;
        245
        246
        247/**
        248 * Whether the code is running on the Safari web browser.
        249 * @type {boolean}
        250 */
        251goog.userAgent.product.SAFARI = goog.userAgent.product.PRODUCT_KNOWN_ ?
        252 goog.userAgent.product.ASSUME_SAFARI :
        253 goog.userAgent.product.detectedSafari_;
        \ No newline at end of file +product.js

        lib/goog/useragent/product.js

        1// Copyright 2008 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Detects the specific browser and not just the rendering engine.
        17 *
        18 */
        19
        20goog.provide('goog.userAgent.product');
        21
        22goog.require('goog.labs.userAgent.browser');
        23goog.require('goog.labs.userAgent.platform');
        24goog.require('goog.userAgent');
        25
        26
        27/**
        28 * @define {boolean} Whether the code is running on the Firefox web browser.
        29 */
        30goog.define('goog.userAgent.product.ASSUME_FIREFOX', false);
        31
        32
        33/**
        34 * @define {boolean} Whether we know at compile-time that the product is an
        35 * iPhone.
        36 */
        37goog.define('goog.userAgent.product.ASSUME_IPHONE', false);
        38
        39
        40/**
        41 * @define {boolean} Whether we know at compile-time that the product is an
        42 * iPad.
        43 */
        44goog.define('goog.userAgent.product.ASSUME_IPAD', false);
        45
        46
        47/**
        48 * @define {boolean} Whether we know at compile-time that the product is an
        49 * AOSP browser or WebView inside a pre KitKat Android phone or tablet.
        50 */
        51goog.define('goog.userAgent.product.ASSUME_ANDROID', false);
        52
        53
        54/**
        55 * @define {boolean} Whether the code is running on the Chrome web browser on
        56 * any platform or AOSP browser or WebView in a KitKat+ Android phone or tablet.
        57 */
        58goog.define('goog.userAgent.product.ASSUME_CHROME', false);
        59
        60
        61/**
        62 * @define {boolean} Whether the code is running on the Safari web browser.
        63 */
        64goog.define('goog.userAgent.product.ASSUME_SAFARI', false);
        65
        66
        67/**
        68 * Whether we know the product type at compile-time.
        69 * @type {boolean}
        70 * @private
        71 */
        72goog.userAgent.product.PRODUCT_KNOWN_ =
        73 goog.userAgent.ASSUME_IE ||
        74 goog.userAgent.ASSUME_OPERA ||
        75 goog.userAgent.product.ASSUME_FIREFOX ||
        76 goog.userAgent.product.ASSUME_IPHONE ||
        77 goog.userAgent.product.ASSUME_IPAD ||
        78 goog.userAgent.product.ASSUME_ANDROID ||
        79 goog.userAgent.product.ASSUME_CHROME ||
        80 goog.userAgent.product.ASSUME_SAFARI;
        81
        82
        83/**
        84 * Whether the code is running on the Opera web browser.
        85 * @type {boolean}
        86 */
        87goog.userAgent.product.OPERA = goog.userAgent.OPERA;
        88
        89
        90/**
        91 * Whether the code is running on an IE web browser.
        92 * @type {boolean}
        93 */
        94goog.userAgent.product.IE = goog.userAgent.IE;
        95
        96
        97/**
        98 * Whether the code is running on the Firefox web browser.
        99 * @type {boolean}
        100 */
        101goog.userAgent.product.FIREFOX = goog.userAgent.product.PRODUCT_KNOWN_ ?
        102 goog.userAgent.product.ASSUME_FIREFOX :
        103 goog.labs.userAgent.browser.isFirefox();
        104
        105
        106/**
        107 * Whether the user agent is an iPhone or iPod (as in iPod touch).
        108 * @return {boolean}
        109 * @private
        110 */
        111goog.userAgent.product.isIphoneOrIpod_ = function() {
        112 return goog.labs.userAgent.platform.isIphone() ||
        113 goog.labs.userAgent.platform.isIpod();
        114};
        115
        116
        117/**
        118 * Whether the code is running on an iPhone or iPod touch.
        119 *
        120 * iPod touch is considered an iPhone for legacy reasons.
        121 * @type {boolean}
        122 */
        123goog.userAgent.product.IPHONE = goog.userAgent.product.PRODUCT_KNOWN_ ?
        124 goog.userAgent.product.ASSUME_IPHONE :
        125 goog.userAgent.product.isIphoneOrIpod_();
        126
        127
        128/**
        129 * Whether the code is running on an iPad.
        130 * @type {boolean}
        131 */
        132goog.userAgent.product.IPAD = goog.userAgent.product.PRODUCT_KNOWN_ ?
        133 goog.userAgent.product.ASSUME_IPAD :
        134 goog.labs.userAgent.platform.isIpad();
        135
        136
        137/**
        138 * Whether the code is running on AOSP browser or WebView inside
        139 * a pre KitKat Android phone or tablet.
        140 * @type {boolean}
        141 */
        142goog.userAgent.product.ANDROID = goog.userAgent.product.PRODUCT_KNOWN_ ?
        143 goog.userAgent.product.ASSUME_ANDROID :
        144 goog.labs.userAgent.browser.isAndroidBrowser();
        145
        146
        147/**
        148 * Whether the code is running on the Chrome web browser on any platform
        149 * or AOSP browser or WebView in a KitKat+ Android phone or tablet.
        150 * @type {boolean}
        151 */
        152goog.userAgent.product.CHROME = goog.userAgent.product.PRODUCT_KNOWN_ ?
        153 goog.userAgent.product.ASSUME_CHROME :
        154 goog.labs.userAgent.browser.isChrome();
        155
        156
        157/**
        158 * @return {boolean} Whether the browser is Safari on desktop.
        159 * @private
        160 */
        161goog.userAgent.product.isSafariDesktop_ = function() {
        162 return goog.labs.userAgent.browser.isSafari() &&
        163 !goog.labs.userAgent.platform.isIos();
        164};
        165
        166
        167/**
        168 * Whether the code is running on the desktop Safari web browser.
        169 * Note: the legacy behavior here is only true for Safari not running
        170 * on iOS.
        171 * @type {boolean}
        172 */
        173goog.userAgent.product.SAFARI = goog.userAgent.product.PRODUCT_KNOWN_ ?
        174 goog.userAgent.product.ASSUME_SAFARI :
        175 goog.userAgent.product.isSafariDesktop_();
        \ No newline at end of file diff --git a/docs/source/lib/goog/useragent/product_isversion.js.src.html b/docs/source/lib/goog/useragent/product_isversion.js.src.html index 4f99683..adcfceb 100644 --- a/docs/source/lib/goog/useragent/product_isversion.js.src.html +++ b/docs/source/lib/goog/useragent/product_isversion.js.src.html @@ -1 +1 @@ -product_isversion.js

        lib/goog/useragent/product_isversion.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Functions for understanding the version of the browser.
        17 * This is pulled out of product.js to ensure that only builds that need
        18 * this functionality actually get it, without having to rely on the compiler
        19 * to strip out unneeded pieces.
        20 *
        21 * TODO(nnaze): Move to more appropriate filename/namespace.
        22 *
        23 */
        24
        25
        26goog.provide('goog.userAgent.product.isVersion');
        27
        28
        29goog.require('goog.userAgent.product');
        30
        31
        32/**
        33 * @return {string} The string that describes the version number of the user
        34 * agent product. This is a string rather than a number because it may
        35 * contain 'b', 'a', and so on.
        36 * @private
        37 */
        38goog.userAgent.product.determineVersion_ = function() {
        39 // All browsers have different ways to detect the version and they all have
        40 // different naming schemes.
        41
        42 if (goog.userAgent.product.FIREFOX) {
        43 // Firefox/2.0.0.1 or Firefox/3.5.3
        44 return goog.userAgent.product.getFirstRegExpGroup_(/Firefox\/([0-9.]+)/);
        45 }
        46
        47 if (goog.userAgent.product.IE || goog.userAgent.product.OPERA) {
        48 return goog.userAgent.VERSION;
        49 }
        50
        51 if (goog.userAgent.product.CHROME) {
        52 // Chrome/4.0.223.1
        53 return goog.userAgent.product.getFirstRegExpGroup_(/Chrome\/([0-9.]+)/);
        54 }
        55
        56 if (goog.userAgent.product.SAFARI) {
        57 // Version/5.0.3
        58 //
        59 // NOTE: Before version 3, Safari did not report a product version number.
        60 // The product version number for these browsers will be the empty string.
        61 // They may be differentiated by WebKit version number in goog.userAgent.
        62 return goog.userAgent.product.getFirstRegExpGroup_(/Version\/([0-9.]+)/);
        63 }
        64
        65 if (goog.userAgent.product.IPHONE || goog.userAgent.product.IPAD) {
        66 // Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1
        67 // (KHTML, like Gecko) Version/3.0 Mobile/3A100a Safari/419.3
        68 // Version is the browser version, Mobile is the build number. We combine
        69 // the version string with the build number: 3.0.3A100a for the example.
        70 var arr = goog.userAgent.product.execRegExp_(
        71 /Version\/(\S+).*Mobile\/(\S+)/);
        72 if (arr) {
        73 return arr[1] + '.' + arr[2];
        74 }
        75 } else if (goog.userAgent.product.ANDROID) {
        76 // Mozilla/5.0 (Linux; U; Android 0.5; en-us) AppleWebKit/522+
        77 // (KHTML, like Gecko) Safari/419.3
        78 //
        79 // Mozilla/5.0 (Linux; U; Android 1.0; en-us; dream) AppleWebKit/525.10+
        80 // (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2
        81 //
        82 // Prefer Version number if present, else make do with the OS number
        83 var version = goog.userAgent.product.getFirstRegExpGroup_(
        84 /Android\s+([0-9.]+)/);
        85 if (version) {
        86 return version;
        87 }
        88
        89 return goog.userAgent.product.getFirstRegExpGroup_(/Version\/([0-9.]+)/);
        90 } else if (goog.userAgent.product.CAMINO) {
        91 return goog.userAgent.product.getFirstRegExpGroup_(/Camino\/([0-9.]+)/);
        92 }
        93
        94 return '';
        95};
        96
        97
        98/**
        99 * Return the first group of the given regex.
        100 * @param {!RegExp} re Regular expression with at least one group.
        101 * @return {string} Contents of the first group or an empty string if no match.
        102 * @private
        103 */
        104goog.userAgent.product.getFirstRegExpGroup_ = function(re) {
        105 var arr = goog.userAgent.product.execRegExp_(re);
        106 return arr ? arr[1] : '';
        107};
        108
        109
        110/**
        111 * Run regexp's exec() on the userAgent string.
        112 * @param {!RegExp} re Regular expression.
        113 * @return {Array} A result array, or null for no match.
        114 * @private
        115 */
        116goog.userAgent.product.execRegExp_ = function(re) {
        117 return re.exec(goog.userAgent.getUserAgentString());
        118};
        119
        120
        121/**
        122 * The version of the user agent. This is a string because it might contain
        123 * 'b' (as in beta) as well as multiple dots.
        124 * @type {string}
        125 */
        126goog.userAgent.product.VERSION = goog.userAgent.product.determineVersion_();
        127
        128
        129/**
        130 * Whether the user agent product version is higher or the same as the given
        131 * version.
        132 *
        133 * @param {string|number} version The version to check.
        134 * @return {boolean} Whether the user agent product version is higher or the
        135 * same as the given version.
        136 */
        137goog.userAgent.product.isVersion = function(version) {
        138 return goog.string.compareVersions(
        139 goog.userAgent.product.VERSION, version) >= 0;
        140};
        \ No newline at end of file +product_isversion.js

        lib/goog/useragent/product_isversion.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Functions for understanding the version of the browser.
        17 * This is pulled out of product.js to ensure that only builds that need
        18 * this functionality actually get it, without having to rely on the compiler
        19 * to strip out unneeded pieces.
        20 *
        21 * TODO(nnaze): Move to more appropriate filename/namespace.
        22 *
        23 */
        24
        25
        26goog.provide('goog.userAgent.product.isVersion');
        27
        28
        29goog.require('goog.labs.userAgent.platform');
        30goog.require('goog.string');
        31goog.require('goog.userAgent');
        32goog.require('goog.userAgent.product');
        33
        34
        35/**
        36 * @return {string} The string that describes the version number of the user
        37 * agent product. This is a string rather than a number because it may
        38 * contain 'b', 'a', and so on.
        39 * @private
        40 */
        41goog.userAgent.product.determineVersion_ = function() {
        42 // All browsers have different ways to detect the version and they all have
        43 // different naming schemes.
        44
        45 if (goog.userAgent.product.FIREFOX) {
        46 // Firefox/2.0.0.1 or Firefox/3.5.3
        47 return goog.userAgent.product.getFirstRegExpGroup_(/Firefox\/([0-9.]+)/);
        48 }
        49
        50 if (goog.userAgent.product.IE || goog.userAgent.product.OPERA) {
        51 return goog.userAgent.VERSION;
        52 }
        53
        54 if (goog.userAgent.product.CHROME) {
        55 // Chrome/4.0.223.1
        56 return goog.userAgent.product.getFirstRegExpGroup_(/Chrome\/([0-9.]+)/);
        57 }
        58
        59 // This replicates legacy logic, which considered Safari and iOS to be
        60 // different products.
        61 if (goog.userAgent.product.SAFARI && !goog.labs.userAgent.platform.isIos()) {
        62 // Version/5.0.3
        63 //
        64 // NOTE: Before version 3, Safari did not report a product version number.
        65 // The product version number for these browsers will be the empty string.
        66 // They may be differentiated by WebKit version number in goog.userAgent.
        67 return goog.userAgent.product.getFirstRegExpGroup_(/Version\/([0-9.]+)/);
        68 }
        69
        70 if (goog.userAgent.product.IPHONE || goog.userAgent.product.IPAD) {
        71 // Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1
        72 // (KHTML, like Gecko) Version/3.0 Mobile/3A100a Safari/419.3
        73 // Version is the browser version, Mobile is the build number. We combine
        74 // the version string with the build number: 3.0.3A100a for the example.
        75 var arr = goog.userAgent.product.execRegExp_(
        76 /Version\/(\S+).*Mobile\/(\S+)/);
        77 if (arr) {
        78 return arr[1] + '.' + arr[2];
        79 }
        80 } else if (goog.userAgent.product.ANDROID) {
        81 // Mozilla/5.0 (Linux; U; Android 0.5; en-us) AppleWebKit/522+
        82 // (KHTML, like Gecko) Safari/419.3
        83 //
        84 // Mozilla/5.0 (Linux; U; Android 1.0; en-us; dream) AppleWebKit/525.10+
        85 // (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2
        86 //
        87 // Prefer Version number if present, else make do with the OS number
        88 var version = goog.userAgent.product.getFirstRegExpGroup_(
        89 /Android\s+([0-9.]+)/);
        90 if (version) {
        91 return version;
        92 }
        93
        94 return goog.userAgent.product.getFirstRegExpGroup_(/Version\/([0-9.]+)/);
        95 }
        96
        97 return '';
        98};
        99
        100
        101/**
        102 * Return the first group of the given regex.
        103 * @param {!RegExp} re Regular expression with at least one group.
        104 * @return {string} Contents of the first group or an empty string if no match.
        105 * @private
        106 */
        107goog.userAgent.product.getFirstRegExpGroup_ = function(re) {
        108 var arr = goog.userAgent.product.execRegExp_(re);
        109 return arr ? arr[1] : '';
        110};
        111
        112
        113/**
        114 * Run regexp's exec() on the userAgent string.
        115 * @param {!RegExp} re Regular expression.
        116 * @return {Array<?>} A result array, or null for no match.
        117 * @private
        118 */
        119goog.userAgent.product.execRegExp_ = function(re) {
        120 return re.exec(goog.userAgent.getUserAgentString());
        121};
        122
        123
        124/**
        125 * The version of the user agent. This is a string because it might contain
        126 * 'b' (as in beta) as well as multiple dots.
        127 * @type {string}
        128 */
        129goog.userAgent.product.VERSION = goog.userAgent.product.determineVersion_();
        130
        131
        132/**
        133 * Whether the user agent product version is higher or the same as the given
        134 * version.
        135 *
        136 * @param {string|number} version The version to check.
        137 * @return {boolean} Whether the user agent product version is higher or the
        138 * same as the given version.
        139 */
        140goog.userAgent.product.isVersion = function(version) {
        141 return goog.string.compareVersions(
        142 goog.userAgent.product.VERSION, version) >= 0;
        143};
        \ No newline at end of file diff --git a/docs/source/lib/goog/useragent/useragent.js.src.html b/docs/source/lib/goog/useragent/useragent.js.src.html index 3f33e48..f01d9d4 100644 --- a/docs/source/lib/goog/useragent/useragent.js.src.html +++ b/docs/source/lib/goog/useragent/useragent.js.src.html @@ -1 +1 @@ -useragent.js

        lib/goog/useragent/useragent.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Rendering engine detection.
        17 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
        18 * For information on the browser brand (such as Safari versus Chrome), see
        19 * goog.userAgent.product.
        20 * @see ../demos/useragent.html
        21 */
        22
        23goog.provide('goog.userAgent');
        24
        25goog.require('goog.string');
        26
        27
        28/**
        29 * @define {boolean} Whether we know at compile-time that the browser is IE.
        30 */
        31goog.define('goog.userAgent.ASSUME_IE', false);
        32
        33
        34/**
        35 * @define {boolean} Whether we know at compile-time that the browser is GECKO.
        36 */
        37goog.define('goog.userAgent.ASSUME_GECKO', false);
        38
        39
        40/**
        41 * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
        42 */
        43goog.define('goog.userAgent.ASSUME_WEBKIT', false);
        44
        45
        46/**
        47 * @define {boolean} Whether we know at compile-time that the browser is a
        48 * mobile device running WebKit e.g. iPhone or Android.
        49 */
        50goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
        51
        52
        53/**
        54 * @define {boolean} Whether we know at compile-time that the browser is OPERA.
        55 */
        56goog.define('goog.userAgent.ASSUME_OPERA', false);
        57
        58
        59/**
        60 * @define {boolean} Whether the
        61 * {@code goog.userAgent.isVersionOrHigher}
        62 * function will return true for any version.
        63 */
        64goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
        65
        66
        67/**
        68 * Whether we know the browser engine at compile-time.
        69 * @type {boolean}
        70 * @private
        71 */
        72goog.userAgent.BROWSER_KNOWN_ =
        73 goog.userAgent.ASSUME_IE ||
        74 goog.userAgent.ASSUME_GECKO ||
        75 goog.userAgent.ASSUME_MOBILE_WEBKIT ||
        76 goog.userAgent.ASSUME_WEBKIT ||
        77 goog.userAgent.ASSUME_OPERA;
        78
        79
        80/**
        81 * Returns the userAgent string for the current browser.
        82 * Some user agents (I'm thinking of you, Gears WorkerPool) do not expose a
        83 * navigator object off the global scope. In that case we return null.
        84 *
        85 * @return {?string} The userAgent string or null if there is none.
        86 */
        87goog.userAgent.getUserAgentString = function() {
        88 return goog.global['navigator'] ? goog.global['navigator'].userAgent : null;
        89};
        90
        91
        92/**
        93 * @return {Object} The native navigator object.
        94 */
        95goog.userAgent.getNavigator = function() {
        96 // Need a local navigator reference instead of using the global one,
        97 // to avoid the rare case where they reference different objects.
        98 // (in a WorkerPool, for example).
        99 return goog.global['navigator'];
        100};
        101
        102
        103/**
        104 * Initializer for goog.userAgent.
        105 *
        106 * This is a named function so that it can be stripped via the jscompiler
        107 * option for stripping types.
        108 * @private
        109 */
        110goog.userAgent.init_ = function() {
        111 /**
        112 * Whether the user agent string denotes Opera.
        113 * @type {boolean}
        114 * @private
        115 */
        116 goog.userAgent.detectedOpera_ = false;
        117
        118 /**
        119 * Whether the user agent string denotes Internet Explorer. This includes
        120 * other browsers using Trident as its rendering engine. For example AOL
        121 * and Netscape 8
        122 * @type {boolean}
        123 * @private
        124 */
        125 goog.userAgent.detectedIe_ = false;
        126
        127 /**
        128 * Whether the user agent string denotes WebKit. WebKit is the rendering
        129 * engine that Safari, Android and others use.
        130 * @type {boolean}
        131 * @private
        132 */
        133 goog.userAgent.detectedWebkit_ = false;
        134
        135 /**
        136 * Whether the user agent string denotes a mobile device.
        137 * @type {boolean}
        138 * @private
        139 */
        140 goog.userAgent.detectedMobile_ = false;
        141
        142 /**
        143 * Whether the user agent string denotes Gecko. Gecko is the rendering
        144 * engine used by Mozilla, Mozilla Firefox, Camino and many more.
        145 * @type {boolean}
        146 * @private
        147 */
        148 goog.userAgent.detectedGecko_ = false;
        149
        150 var ua;
        151 if (!goog.userAgent.BROWSER_KNOWN_ &&
        152 (ua = goog.userAgent.getUserAgentString())) {
        153 var navigator = goog.userAgent.getNavigator();
        154 goog.userAgent.detectedOpera_ = ua.indexOf('Opera') == 0;
        155 goog.userAgent.detectedIe_ = !goog.userAgent.detectedOpera_ &&
        156 ua.indexOf('MSIE') != -1;
        157 goog.userAgent.detectedWebkit_ = !goog.userAgent.detectedOpera_ &&
        158 ua.indexOf('WebKit') != -1;
        159 // WebKit also gives navigator.product string equal to 'Gecko'.
        160 goog.userAgent.detectedMobile_ = goog.userAgent.detectedWebkit_ &&
        161 ua.indexOf('Mobile') != -1;
        162 goog.userAgent.detectedGecko_ = !goog.userAgent.detectedOpera_ &&
        163 !goog.userAgent.detectedWebkit_ && navigator.product == 'Gecko';
        164 }
        165};
        166
        167
        168if (!goog.userAgent.BROWSER_KNOWN_) {
        169 goog.userAgent.init_();
        170}
        171
        172
        173/**
        174 * Whether the user agent is Opera.
        175 * @type {boolean}
        176 */
        177goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
        178 goog.userAgent.ASSUME_OPERA : goog.userAgent.detectedOpera_;
        179
        180
        181/**
        182 * Whether the user agent is Internet Explorer. This includes other browsers
        183 * using Trident as its rendering engine. For example AOL and Netscape 8
        184 * @type {boolean}
        185 */
        186goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
        187 goog.userAgent.ASSUME_IE : goog.userAgent.detectedIe_;
        188
        189
        190/**
        191 * Whether the user agent is Gecko. Gecko is the rendering engine used by
        192 * Mozilla, Mozilla Firefox, Camino and many more.
        193 * @type {boolean}
        194 */
        195goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
        196 goog.userAgent.ASSUME_GECKO :
        197 goog.userAgent.detectedGecko_;
        198
        199
        200/**
        201 * Whether the user agent is WebKit. WebKit is the rendering engine that
        202 * Safari, Android and others use.
        203 * @type {boolean}
        204 */
        205goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
        206 goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
        207 goog.userAgent.detectedWebkit_;
        208
        209
        210/**
        211 * Whether the user agent is running on a mobile device.
        212 * @type {boolean}
        213 */
        214goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
        215 goog.userAgent.detectedMobile_;
        216
        217
        218/**
        219 * Used while transitioning code to use WEBKIT instead.
        220 * @type {boolean}
        221 * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
        222 * TODO(nicksantos): Delete this from goog.userAgent.
        223 */
        224goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
        225
        226
        227/**
        228 * @return {string} the platform (operating system) the user agent is running
        229 * on. Default to empty string because navigator.platform may not be defined
        230 * (on Rhino, for example).
        231 * @private
        232 */
        233goog.userAgent.determinePlatform_ = function() {
        234 var navigator = goog.userAgent.getNavigator();
        235 return navigator && navigator.platform || '';
        236};
        237
        238
        239/**
        240 * The platform (operating system) the user agent is running on. Default to
        241 * empty string because navigator.platform may not be defined (on Rhino, for
        242 * example).
        243 * @type {string}
        244 */
        245goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
        246
        247
        248/**
        249 * @define {boolean} Whether the user agent is running on a Macintosh operating
        250 * system.
        251 */
        252goog.define('goog.userAgent.ASSUME_MAC', false);
        253
        254
        255/**
        256 * @define {boolean} Whether the user agent is running on a Windows operating
        257 * system.
        258 */
        259goog.define('goog.userAgent.ASSUME_WINDOWS', false);
        260
        261
        262/**
        263 * @define {boolean} Whether the user agent is running on a Linux operating
        264 * system.
        265 */
        266goog.define('goog.userAgent.ASSUME_LINUX', false);
        267
        268
        269/**
        270 * @define {boolean} Whether the user agent is running on a X11 windowing
        271 * system.
        272 */
        273goog.define('goog.userAgent.ASSUME_X11', false);
        274
        275
        276/**
        277 * @define {boolean} Whether the user agent is running on Android.
        278 */
        279goog.define('goog.userAgent.ASSUME_ANDROID', false);
        280
        281
        282/**
        283 * @define {boolean} Whether the user agent is running on an iPhone.
        284 */
        285goog.define('goog.userAgent.ASSUME_IPHONE', false);
        286
        287
        288/**
        289 * @define {boolean} Whether the user agent is running on an iPad.
        290 */
        291goog.define('goog.userAgent.ASSUME_IPAD', false);
        292
        293
        294/**
        295 * @type {boolean}
        296 * @private
        297 */
        298goog.userAgent.PLATFORM_KNOWN_ =
        299 goog.userAgent.ASSUME_MAC ||
        300 goog.userAgent.ASSUME_WINDOWS ||
        301 goog.userAgent.ASSUME_LINUX ||
        302 goog.userAgent.ASSUME_X11 ||
        303 goog.userAgent.ASSUME_ANDROID ||
        304 goog.userAgent.ASSUME_IPHONE ||
        305 goog.userAgent.ASSUME_IPAD;
        306
        307
        308/**
        309 * Initialize the goog.userAgent constants that define which platform the user
        310 * agent is running on.
        311 * @private
        312 */
        313goog.userAgent.initPlatform_ = function() {
        314 /**
        315 * Whether the user agent is running on a Macintosh operating system.
        316 * @type {boolean}
        317 * @private
        318 */
        319 goog.userAgent.detectedMac_ = goog.string.contains(goog.userAgent.PLATFORM,
        320 'Mac');
        321
        322 /**
        323 * Whether the user agent is running on a Windows operating system.
        324 * @type {boolean}
        325 * @private
        326 */
        327 goog.userAgent.detectedWindows_ = goog.string.contains(
        328 goog.userAgent.PLATFORM, 'Win');
        329
        330 /**
        331 * Whether the user agent is running on a Linux operating system.
        332 * @type {boolean}
        333 * @private
        334 */
        335 goog.userAgent.detectedLinux_ = goog.string.contains(goog.userAgent.PLATFORM,
        336 'Linux');
        337
        338 /**
        339 * Whether the user agent is running on a X11 windowing system.
        340 * @type {boolean}
        341 * @private
        342 */
        343 goog.userAgent.detectedX11_ = !!goog.userAgent.getNavigator() &&
        344 goog.string.contains(goog.userAgent.getNavigator()['appVersion'] || '',
        345 'X11');
        346
        347 // Need user agent string for Android/IOS detection
        348 var ua = goog.userAgent.getUserAgentString();
        349
        350 /**
        351 * Whether the user agent is running on Android.
        352 * @type {boolean}
        353 * @private
        354 */
        355 goog.userAgent.detectedAndroid_ = !!ua && ua.indexOf('Android') >= 0;
        356
        357 /**
        358 * Whether the user agent is running on an iPhone.
        359 * @type {boolean}
        360 * @private
        361 */
        362 goog.userAgent.detectedIPhone_ = !!ua && ua.indexOf('iPhone') >= 0;
        363
        364 /**
        365 * Whether the user agent is running on an iPad.
        366 * @type {boolean}
        367 * @private
        368 */
        369 goog.userAgent.detectedIPad_ = !!ua && ua.indexOf('iPad') >= 0;
        370};
        371
        372
        373if (!goog.userAgent.PLATFORM_KNOWN_) {
        374 goog.userAgent.initPlatform_();
        375}
        376
        377
        378/**
        379 * Whether the user agent is running on a Macintosh operating system.
        380 * @type {boolean}
        381 */
        382goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
        383 goog.userAgent.ASSUME_MAC : goog.userAgent.detectedMac_;
        384
        385
        386/**
        387 * Whether the user agent is running on a Windows operating system.
        388 * @type {boolean}
        389 */
        390goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
        391 goog.userAgent.ASSUME_WINDOWS : goog.userAgent.detectedWindows_;
        392
        393
        394/**
        395 * Whether the user agent is running on a Linux operating system.
        396 * @type {boolean}
        397 */
        398goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
        399 goog.userAgent.ASSUME_LINUX : goog.userAgent.detectedLinux_;
        400
        401
        402/**
        403 * Whether the user agent is running on a X11 windowing system.
        404 * @type {boolean}
        405 */
        406goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
        407 goog.userAgent.ASSUME_X11 : goog.userAgent.detectedX11_;
        408
        409
        410/**
        411 * Whether the user agent is running on Android.
        412 * @type {boolean}
        413 */
        414goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
        415 goog.userAgent.ASSUME_ANDROID : goog.userAgent.detectedAndroid_;
        416
        417
        418/**
        419 * Whether the user agent is running on an iPhone.
        420 * @type {boolean}
        421 */
        422goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
        423 goog.userAgent.ASSUME_IPHONE : goog.userAgent.detectedIPhone_;
        424
        425
        426/**
        427 * Whether the user agent is running on an iPad.
        428 * @type {boolean}
        429 */
        430goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
        431 goog.userAgent.ASSUME_IPAD : goog.userAgent.detectedIPad_;
        432
        433
        434/**
        435 * @return {string} The string that describes the version number of the user
        436 * agent.
        437 * @private
        438 */
        439goog.userAgent.determineVersion_ = function() {
        440 // All browsers have different ways to detect the version and they all have
        441 // different naming schemes.
        442
        443 // version is a string rather than a number because it may contain 'b', 'a',
        444 // and so on.
        445 var version = '', re;
        446
        447 if (goog.userAgent.OPERA && goog.global['opera']) {
        448 var operaVersion = goog.global['opera'].version;
        449 version = typeof operaVersion == 'function' ? operaVersion() : operaVersion;
        450 } else {
        451 if (goog.userAgent.GECKO) {
        452 re = /rv\:([^\);]+)(\)|;)/;
        453 } else if (goog.userAgent.IE) {
        454 re = /MSIE\s+([^\);]+)(\)|;)/;
        455 } else if (goog.userAgent.WEBKIT) {
        456 // WebKit/125.4
        457 re = /WebKit\/(\S+)/;
        458 }
        459 if (re) {
        460 var arr = re.exec(goog.userAgent.getUserAgentString());
        461 version = arr ? arr[1] : '';
        462 }
        463 }
        464 if (goog.userAgent.IE) {
        465 // IE9 can be in document mode 9 but be reporting an inconsistent user agent
        466 // version. If it is identifying as a version lower than 9 we take the
        467 // documentMode as the version instead. IE8 has similar behavior.
        468 // It is recommended to set the X-UA-Compatible header to ensure that IE9
        469 // uses documentMode 9.
        470 var docMode = goog.userAgent.getDocumentMode_();
        471 if (docMode > parseFloat(version)) {
        472 return String(docMode);
        473 }
        474 }
        475 return version;
        476};
        477
        478
        479/**
        480 * @return {number|undefined} Returns the document mode (for testing).
        481 * @private
        482 */
        483goog.userAgent.getDocumentMode_ = function() {
        484 // NOTE(user): goog.userAgent may be used in context where there is no DOM.
        485 var doc = goog.global['document'];
        486 return doc ? doc['documentMode'] : undefined;
        487};
        488
        489
        490/**
        491 * The version of the user agent. This is a string because it might contain
        492 * 'b' (as in beta) as well as multiple dots.
        493 * @type {string}
        494 */
        495goog.userAgent.VERSION = goog.userAgent.determineVersion_();
        496
        497
        498/**
        499 * Compares two version numbers.
        500 *
        501 * @param {string} v1 Version of first item.
        502 * @param {string} v2 Version of second item.
        503 *
        504 * @return {number} 1 if first argument is higher
        505 * 0 if arguments are equal
        506 * -1 if second argument is higher.
        507 * @deprecated Use goog.string.compareVersions.
        508 */
        509goog.userAgent.compare = function(v1, v2) {
        510 return goog.string.compareVersions(v1, v2);
        511};
        512
        513
        514/**
        515 * Cache for {@link goog.userAgent.isVersionOrHigher}.
        516 * Calls to compareVersions are surprisingly expensive and, as a browser's
        517 * version number is unlikely to change during a session, we cache the results.
        518 * @const
        519 * @private
        520 */
        521goog.userAgent.isVersionOrHigherCache_ = {};
        522
        523
        524/**
        525 * Whether the user agent version is higher or the same as the given version.
        526 * NOTE: When checking the version numbers for Firefox and Safari, be sure to
        527 * use the engine's version, not the browser's version number. For example,
        528 * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
        529 * Opera and Internet Explorer versions match the product release number.<br>
        530 * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
        531 * Webkit</a>
        532 * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
        533 *
        534 * @param {string|number} version The version to check.
        535 * @return {boolean} Whether the user agent version is higher or the same as
        536 * the given version.
        537 */
        538goog.userAgent.isVersionOrHigher = function(version) {
        539 return goog.userAgent.ASSUME_ANY_VERSION ||
        540 goog.userAgent.isVersionOrHigherCache_[version] ||
        541 (goog.userAgent.isVersionOrHigherCache_[version] =
        542 goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
        543};
        544
        545
        546/**
        547 * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
        548 * @param {string|number} version The version to check.
        549 * @return {boolean} Whether the user agent version is higher or the same as
        550 * the given version.
        551 * @deprecated Use goog.userAgent.isVersionOrHigher().
        552 */
        553goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
        554
        555
        556/**
        557 * Whether the IE effective document mode is higher or the same as the given
        558 * document mode version.
        559 * NOTE: Only for IE, return false for another browser.
        560 *
        561 * @param {number} documentMode The document mode version to check.
        562 * @return {boolean} Whether the IE effective document mode is higher or the
        563 * same as the given version.
        564 */
        565goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
        566 return goog.userAgent.IE && goog.userAgent.DOCUMENT_MODE >= documentMode;
        567};
        568
        569
        570/**
        571 * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
        572 * @param {number} version The version to check.
        573 * @return {boolean} Whether the IE effective document mode is higher or the
        574 * same as the given version.
        575 */
        576goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
        577
        578
        579/**
        580 * For IE version < 7, documentMode is undefined, so attempt to use the
        581 * CSS1Compat property to see if we are in standards mode. If we are in
        582 * standards mode, treat the browser version as the document mode. Otherwise,
        583 * IE is emulating version 5.
        584 * @type {number|undefined}
        585 * @const
        586 */
        587goog.userAgent.DOCUMENT_MODE = (function() {
        588 var doc = goog.global['document'];
        589 if (!doc || !goog.userAgent.IE) {
        590 return undefined;
        591 }
        592 var mode = goog.userAgent.getDocumentMode_();
        593 return mode || (doc['compatMode'] == 'CSS1Compat' ?
        594 parseInt(goog.userAgent.VERSION, 10) : 5);
        595})();
        \ No newline at end of file +useragent.js

        lib/goog/useragent/useragent.js

        1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS-IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Rendering engine detection.
        17 * @see <a href="http://www.useragentstring.com/">User agent strings</a>
        18 * For information on the browser brand (such as Safari versus Chrome), see
        19 * goog.userAgent.product.
        20 * @author arv@google.com (Erik Arvidsson)
        21 * @see ../demos/useragent.html
        22 */
        23
        24goog.provide('goog.userAgent');
        25
        26goog.require('goog.labs.userAgent.browser');
        27goog.require('goog.labs.userAgent.engine');
        28goog.require('goog.labs.userAgent.platform');
        29goog.require('goog.labs.userAgent.util');
        30goog.require('goog.string');
        31
        32
        33/**
        34 * @define {boolean} Whether we know at compile-time that the browser is IE.
        35 */
        36goog.define('goog.userAgent.ASSUME_IE', false);
        37
        38
        39/**
        40 * @define {boolean} Whether we know at compile-time that the browser is GECKO.
        41 */
        42goog.define('goog.userAgent.ASSUME_GECKO', false);
        43
        44
        45/**
        46 * @define {boolean} Whether we know at compile-time that the browser is WEBKIT.
        47 */
        48goog.define('goog.userAgent.ASSUME_WEBKIT', false);
        49
        50
        51/**
        52 * @define {boolean} Whether we know at compile-time that the browser is a
        53 * mobile device running WebKit e.g. iPhone or Android.
        54 */
        55goog.define('goog.userAgent.ASSUME_MOBILE_WEBKIT', false);
        56
        57
        58/**
        59 * @define {boolean} Whether we know at compile-time that the browser is OPERA.
        60 */
        61goog.define('goog.userAgent.ASSUME_OPERA', false);
        62
        63
        64/**
        65 * @define {boolean} Whether the
        66 * {@code goog.userAgent.isVersionOrHigher}
        67 * function will return true for any version.
        68 */
        69goog.define('goog.userAgent.ASSUME_ANY_VERSION', false);
        70
        71
        72/**
        73 * Whether we know the browser engine at compile-time.
        74 * @type {boolean}
        75 * @private
        76 */
        77goog.userAgent.BROWSER_KNOWN_ =
        78 goog.userAgent.ASSUME_IE ||
        79 goog.userAgent.ASSUME_GECKO ||
        80 goog.userAgent.ASSUME_MOBILE_WEBKIT ||
        81 goog.userAgent.ASSUME_WEBKIT ||
        82 goog.userAgent.ASSUME_OPERA;
        83
        84
        85/**
        86 * Returns the userAgent string for the current browser.
        87 *
        88 * @return {string} The userAgent string.
        89 */
        90goog.userAgent.getUserAgentString = function() {
        91 return goog.labs.userAgent.util.getUserAgent();
        92};
        93
        94
        95/**
        96 * TODO(nnaze): Change type to "Navigator" and update compilation targets.
        97 * @return {Object} The native navigator object.
        98 */
        99goog.userAgent.getNavigator = function() {
        100 // Need a local navigator reference instead of using the global one,
        101 // to avoid the rare case where they reference different objects.
        102 // (in a WorkerPool, for example).
        103 return goog.global['navigator'] || null;
        104};
        105
        106
        107/**
        108 * Whether the user agent is Opera.
        109 * @type {boolean}
        110 */
        111goog.userAgent.OPERA = goog.userAgent.BROWSER_KNOWN_ ?
        112 goog.userAgent.ASSUME_OPERA :
        113 goog.labs.userAgent.browser.isOpera();
        114
        115
        116/**
        117 * Whether the user agent is Internet Explorer.
        118 * @type {boolean}
        119 */
        120goog.userAgent.IE = goog.userAgent.BROWSER_KNOWN_ ?
        121 goog.userAgent.ASSUME_IE :
        122 goog.labs.userAgent.browser.isIE();
        123
        124
        125/**
        126 * Whether the user agent is Gecko. Gecko is the rendering engine used by
        127 * Mozilla, Firefox, and others.
        128 * @type {boolean}
        129 */
        130goog.userAgent.GECKO = goog.userAgent.BROWSER_KNOWN_ ?
        131 goog.userAgent.ASSUME_GECKO :
        132 goog.labs.userAgent.engine.isGecko();
        133
        134
        135/**
        136 * Whether the user agent is WebKit. WebKit is the rendering engine that
        137 * Safari, Android and others use.
        138 * @type {boolean}
        139 */
        140goog.userAgent.WEBKIT = goog.userAgent.BROWSER_KNOWN_ ?
        141 goog.userAgent.ASSUME_WEBKIT || goog.userAgent.ASSUME_MOBILE_WEBKIT :
        142 goog.labs.userAgent.engine.isWebKit();
        143
        144
        145/**
        146 * Whether the user agent is running on a mobile device.
        147 *
        148 * This is a separate function so that the logic can be tested.
        149 *
        150 * TODO(nnaze): Investigate swapping in goog.labs.userAgent.device.isMobile().
        151 *
        152 * @return {boolean} Whether the user agent is running on a mobile device.
        153 * @private
        154 */
        155goog.userAgent.isMobile_ = function() {
        156 return goog.userAgent.WEBKIT &&
        157 goog.labs.userAgent.util.matchUserAgent('Mobile');
        158};
        159
        160
        161/**
        162 * Whether the user agent is running on a mobile device.
        163 *
        164 * TODO(nnaze): Consider deprecating MOBILE when labs.userAgent
        165 * is promoted as the gecko/webkit logic is likely inaccurate.
        166 *
        167 * @type {boolean}
        168 */
        169goog.userAgent.MOBILE = goog.userAgent.ASSUME_MOBILE_WEBKIT ||
        170 goog.userAgent.isMobile_();
        171
        172
        173/**
        174 * Used while transitioning code to use WEBKIT instead.
        175 * @type {boolean}
        176 * @deprecated Use {@link goog.userAgent.product.SAFARI} instead.
        177 * TODO(nicksantos): Delete this from goog.userAgent.
        178 */
        179goog.userAgent.SAFARI = goog.userAgent.WEBKIT;
        180
        181
        182/**
        183 * @return {string} the platform (operating system) the user agent is running
        184 * on. Default to empty string because navigator.platform may not be defined
        185 * (on Rhino, for example).
        186 * @private
        187 */
        188goog.userAgent.determinePlatform_ = function() {
        189 var navigator = goog.userAgent.getNavigator();
        190 return navigator && navigator.platform || '';
        191};
        192
        193
        194/**
        195 * The platform (operating system) the user agent is running on. Default to
        196 * empty string because navigator.platform may not be defined (on Rhino, for
        197 * example).
        198 * @type {string}
        199 */
        200goog.userAgent.PLATFORM = goog.userAgent.determinePlatform_();
        201
        202
        203/**
        204 * @define {boolean} Whether the user agent is running on a Macintosh operating
        205 * system.
        206 */
        207goog.define('goog.userAgent.ASSUME_MAC', false);
        208
        209
        210/**
        211 * @define {boolean} Whether the user agent is running on a Windows operating
        212 * system.
        213 */
        214goog.define('goog.userAgent.ASSUME_WINDOWS', false);
        215
        216
        217/**
        218 * @define {boolean} Whether the user agent is running on a Linux operating
        219 * system.
        220 */
        221goog.define('goog.userAgent.ASSUME_LINUX', false);
        222
        223
        224/**
        225 * @define {boolean} Whether the user agent is running on a X11 windowing
        226 * system.
        227 */
        228goog.define('goog.userAgent.ASSUME_X11', false);
        229
        230
        231/**
        232 * @define {boolean} Whether the user agent is running on Android.
        233 */
        234goog.define('goog.userAgent.ASSUME_ANDROID', false);
        235
        236
        237/**
        238 * @define {boolean} Whether the user agent is running on an iPhone.
        239 */
        240goog.define('goog.userAgent.ASSUME_IPHONE', false);
        241
        242
        243/**
        244 * @define {boolean} Whether the user agent is running on an iPad.
        245 */
        246goog.define('goog.userAgent.ASSUME_IPAD', false);
        247
        248
        249/**
        250 * @type {boolean}
        251 * @private
        252 */
        253goog.userAgent.PLATFORM_KNOWN_ =
        254 goog.userAgent.ASSUME_MAC ||
        255 goog.userAgent.ASSUME_WINDOWS ||
        256 goog.userAgent.ASSUME_LINUX ||
        257 goog.userAgent.ASSUME_X11 ||
        258 goog.userAgent.ASSUME_ANDROID ||
        259 goog.userAgent.ASSUME_IPHONE ||
        260 goog.userAgent.ASSUME_IPAD;
        261
        262
        263/**
        264 * Whether the user agent is running on a Macintosh operating system.
        265 * @type {boolean}
        266 */
        267goog.userAgent.MAC = goog.userAgent.PLATFORM_KNOWN_ ?
        268 goog.userAgent.ASSUME_MAC : goog.labs.userAgent.platform.isMacintosh();
        269
        270
        271/**
        272 * Whether the user agent is running on a Windows operating system.
        273 * @type {boolean}
        274 */
        275goog.userAgent.WINDOWS = goog.userAgent.PLATFORM_KNOWN_ ?
        276 goog.userAgent.ASSUME_WINDOWS :
        277 goog.labs.userAgent.platform.isWindows();
        278
        279
        280/**
        281 * Whether the user agent is Linux per the legacy behavior of
        282 * goog.userAgent.LINUX, which considered ChromeOS to also be
        283 * Linux.
        284 * @return {boolean}
        285 * @private
        286 */
        287goog.userAgent.isLegacyLinux_ = function() {
        288 return goog.labs.userAgent.platform.isLinux() ||
        289 goog.labs.userAgent.platform.isChromeOS();
        290};
        291
        292
        293/**
        294 * Whether the user agent is running on a Linux operating system.
        295 *
        296 * Note that goog.userAgent.LINUX considers ChromeOS to be Linux,
        297 * while goog.labs.userAgent.platform considers ChromeOS and
        298 * Linux to be different OSes.
        299 *
        300 * @type {boolean}
        301 */
        302goog.userAgent.LINUX = goog.userAgent.PLATFORM_KNOWN_ ?
        303 goog.userAgent.ASSUME_LINUX :
        304 goog.userAgent.isLegacyLinux_();
        305
        306
        307/**
        308 * @return {boolean} Whether the user agent is an X11 windowing system.
        309 * @private
        310 */
        311goog.userAgent.isX11_ = function() {
        312 var navigator = goog.userAgent.getNavigator();
        313 return !!navigator &&
        314 goog.string.contains(navigator['appVersion'] || '', 'X11');
        315};
        316
        317
        318/**
        319 * Whether the user agent is running on a X11 windowing system.
        320 * @type {boolean}
        321 */
        322goog.userAgent.X11 = goog.userAgent.PLATFORM_KNOWN_ ?
        323 goog.userAgent.ASSUME_X11 :
        324 goog.userAgent.isX11_();
        325
        326
        327/**
        328 * Whether the user agent is running on Android.
        329 * @type {boolean}
        330 */
        331goog.userAgent.ANDROID = goog.userAgent.PLATFORM_KNOWN_ ?
        332 goog.userAgent.ASSUME_ANDROID :
        333 goog.labs.userAgent.platform.isAndroid();
        334
        335
        336/**
        337 * Whether the user agent is running on an iPhone.
        338 * @type {boolean}
        339 */
        340goog.userAgent.IPHONE = goog.userAgent.PLATFORM_KNOWN_ ?
        341 goog.userAgent.ASSUME_IPHONE :
        342 goog.labs.userAgent.platform.isIphone();
        343
        344
        345/**
        346 * Whether the user agent is running on an iPad.
        347 * @type {boolean}
        348 */
        349goog.userAgent.IPAD = goog.userAgent.PLATFORM_KNOWN_ ?
        350 goog.userAgent.ASSUME_IPAD :
        351 goog.labs.userAgent.platform.isIpad();
        352
        353
        354/**
        355 * @return {string} The string that describes the version number of the user
        356 * agent.
        357 * @private
        358 */
        359goog.userAgent.determineVersion_ = function() {
        360 // All browsers have different ways to detect the version and they all have
        361 // different naming schemes.
        362
        363 if (goog.userAgent.OPERA && goog.global['opera']) {
        364 var operaVersion = goog.global['opera'].version;
        365 return goog.isFunction(operaVersion) ? operaVersion() : operaVersion;
        366 }
        367
        368 // version is a string rather than a number because it may contain 'b', 'a',
        369 // and so on.
        370 var version = '';
        371 var arr = goog.userAgent.getVersionRegexResult_();
        372 if (arr) {
        373 version = arr ? arr[1] : '';
        374 }
        375
        376 if (goog.userAgent.IE && !goog.labs.userAgent.engine.isEdge()) {
        377 // IE9 can be in document mode 9 but be reporting an inconsistent user agent
        378 // version. If it is identifying as a version lower than 9 we take the
        379 // documentMode as the version instead. IE8 has similar behavior.
        380 // It is recommended to set the X-UA-Compatible header to ensure that IE9
        381 // uses documentMode 9.
        382 var docMode = goog.userAgent.getDocumentMode_();
        383 if (docMode > parseFloat(version)) {
        384 return String(docMode);
        385 }
        386 }
        387
        388 return version;
        389};
        390
        391
        392/**
        393 * @return {Array|undefined} The version regex matches from parsing the user
        394 * agent string. These regex statements must be executed inline so they can
        395 * be compiled out by the closure compiler with the rest of the useragent
        396 * detection logic when ASSUME_* is specified.
        397 * @private
        398 */
        399goog.userAgent.getVersionRegexResult_ = function() {
        400 var userAgent = goog.userAgent.getUserAgentString();
        401 if (goog.userAgent.GECKO) {
        402 return /rv\:([^\);]+)(\)|;)/.exec(userAgent);
        403 }
        404 if (goog.userAgent.IE && goog.labs.userAgent.engine.isEdge()) {
        405 return /Edge\/([\d\.]+)/.exec(userAgent);
        406 }
        407 if (goog.userAgent.IE) {
        408 return /\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(userAgent);
        409 }
        410 if (goog.userAgent.WEBKIT) {
        411 // WebKit/125.4
        412 return /WebKit\/(\S+)/.exec(userAgent);
        413 }
        414};
        415
        416
        417/**
        418 * @return {number|undefined} Returns the document mode (for testing).
        419 * @private
        420 */
        421goog.userAgent.getDocumentMode_ = function() {
        422 // NOTE(user): goog.userAgent may be used in context where there is no DOM.
        423 var doc = goog.global['document'];
        424 return doc ? doc['documentMode'] : undefined;
        425};
        426
        427
        428/**
        429 * The version of the user agent. This is a string because it might contain
        430 * 'b' (as in beta) as well as multiple dots.
        431 * @type {string}
        432 */
        433goog.userAgent.VERSION = goog.userAgent.determineVersion_();
        434
        435
        436/**
        437 * Compares two version numbers.
        438 *
        439 * @param {string} v1 Version of first item.
        440 * @param {string} v2 Version of second item.
        441 *
        442 * @return {number} 1 if first argument is higher
        443 * 0 if arguments are equal
        444 * -1 if second argument is higher.
        445 * @deprecated Use goog.string.compareVersions.
        446 */
        447goog.userAgent.compare = function(v1, v2) {
        448 return goog.string.compareVersions(v1, v2);
        449};
        450
        451
        452/**
        453 * Cache for {@link goog.userAgent.isVersionOrHigher}.
        454 * Calls to compareVersions are surprisingly expensive and, as a browser's
        455 * version number is unlikely to change during a session, we cache the results.
        456 * @const
        457 * @private
        458 */
        459goog.userAgent.isVersionOrHigherCache_ = {};
        460
        461
        462/**
        463 * Whether the user agent version is higher or the same as the given version.
        464 * NOTE: When checking the version numbers for Firefox and Safari, be sure to
        465 * use the engine's version, not the browser's version number. For example,
        466 * Firefox 3.0 corresponds to Gecko 1.9 and Safari 3.0 to Webkit 522.11.
        467 * Opera and Internet Explorer versions match the product release number.<br>
        468 * @see <a href="http://en.wikipedia.org/wiki/Safari_version_history">
        469 * Webkit</a>
        470 * @see <a href="http://en.wikipedia.org/wiki/Gecko_engine">Gecko</a>
        471 *
        472 * @param {string|number} version The version to check.
        473 * @return {boolean} Whether the user agent version is higher or the same as
        474 * the given version.
        475 */
        476goog.userAgent.isVersionOrHigher = function(version) {
        477 return goog.userAgent.ASSUME_ANY_VERSION ||
        478 goog.userAgent.isVersionOrHigherCache_[version] ||
        479 (goog.userAgent.isVersionOrHigherCache_[version] =
        480 goog.string.compareVersions(goog.userAgent.VERSION, version) >= 0);
        481};
        482
        483
        484/**
        485 * Deprecated alias to {@code goog.userAgent.isVersionOrHigher}.
        486 * @param {string|number} version The version to check.
        487 * @return {boolean} Whether the user agent version is higher or the same as
        488 * the given version.
        489 * @deprecated Use goog.userAgent.isVersionOrHigher().
        490 */
        491goog.userAgent.isVersion = goog.userAgent.isVersionOrHigher;
        492
        493
        494/**
        495 * Whether the IE effective document mode is higher or the same as the given
        496 * document mode version. Because document modes were deprecated with the launch
        497 * of IE's new Edge engine, Edge browsers will always return true for this
        498 * function.
        499 * NOTE: Only for IE, return false for another browser.
        500 *
        501 * @param {number} documentMode The document mode version to check.
        502 * @return {boolean} Whether the IE effective document mode is higher or the
        503 * same as the given version.
        504 */
        505goog.userAgent.isDocumentModeOrHigher = function(documentMode) {
        506 return goog.userAgent.IE && (goog.labs.userAgent.engine.isEdge() ||
        507 goog.userAgent.DOCUMENT_MODE >= documentMode);
        508};
        509
        510
        511/**
        512 * Deprecated alias to {@code goog.userAgent.isDocumentModeOrHigher}.
        513 * @param {number} version The version to check.
        514 * @return {boolean} Whether the IE effective document mode is higher or the
        515 * same as the given version.
        516 * @deprecated Use goog.userAgent.isDocumentModeOrHigher().
        517 */
        518goog.userAgent.isDocumentMode = goog.userAgent.isDocumentModeOrHigher;
        519
        520
        521/**
        522 * For IE version < 7 and IE Edge browsers, documentMode is undefined. For
        523 * non-Edge browsers attempt to use the CSS1Compat property to see if we are in
        524 * standards mode. If we are in standards mode, treat the browser version as the
        525 * document mode. Otherwise, IE is emulating version 5.
        526 * @type {number|undefined}
        527 * @const
        528 */
        529goog.userAgent.DOCUMENT_MODE = (function() {
        530 var doc = goog.global['document'];
        531 var mode = goog.userAgent.getDocumentMode_();
        532 if (!doc || !goog.userAgent.IE ||
        533 (!mode && goog.labs.userAgent.engine.isEdge())) {
        534 return undefined;
        535 }
        536 return mode || (doc['compatMode'] == 'CSS1Compat' ?
        537 parseInt(goog.userAgent.VERSION, 10) : 5);
        538})();
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/abstractbuilder.js.src.html b/docs/source/lib/webdriver/abstractbuilder.js.src.html index 242c7a7..a0de85a 100644 --- a/docs/source/lib/webdriver/abstractbuilder.js.src.html +++ b/docs/source/lib/webdriver/abstractbuilder.js.src.html @@ -1 +1 @@ -abstractbuilder.js

        lib/webdriver/abstractbuilder.js

        1// Copyright 2012 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.AbstractBuilder');
        16
        17goog.require('webdriver.Capabilities');
        18goog.require('webdriver.process');
        19
        20
        21
        22/**
        23 * Creates new {@code webdriver.WebDriver} clients. Upon instantiation, each
        24 * Builder will configure itself based on the following environment variables:
        25 * <dl>
        26 * <dt>{@code webdriver.AbstractBuilder.SERVER_URL_ENV}</dt>
        27 * <dd>Defines the remote WebDriver server that should be used for command
        28 * command execution; may be overridden using
        29 * {@code webdriver.AbstractBuilder.prototype.usingServer}.</dd>
        30 * </dl>
        31 * @constructor
        32 */
        33webdriver.AbstractBuilder = function() {
        34
        35 /**
        36 * URL of the remote server to use for new clients; initialized from the
        37 * value of the {@link webdriver.AbstractBuilder.SERVER_URL_ENV} environment
        38 * variable, but may be overridden using
        39 * {@link webdriver.AbstractBuilder#usingServer}.
        40 * @private {string}
        41 */
        42 this.serverUrl_ = webdriver.process.getEnv(
        43 webdriver.AbstractBuilder.SERVER_URL_ENV);
        44
        45 /**
        46 * The desired capabilities to use when creating a new session.
        47 * @private {!webdriver.Capabilities}
        48 */
        49 this.capabilities_ = new webdriver.Capabilities();
        50};
        51
        52
        53/**
        54 * Environment variable that defines the URL of the WebDriver server that
        55 * should be used for all new WebDriver clients. This setting may be overridden
        56 * using {@code #usingServer(url)}.
        57 * @type {string}
        58 * @const
        59 * @see webdriver.process.getEnv
        60 */
        61webdriver.AbstractBuilder.SERVER_URL_ENV = 'wdurl';
        62
        63
        64/**
        65 * The default URL of the WebDriver server to use if
        66 * {@link webdriver.AbstractBuilder.SERVER_URL_ENV} is not set.
        67 * @type {string}
        68 * @const
        69 */
        70webdriver.AbstractBuilder.DEFAULT_SERVER_URL = 'http://localhost:4444/wd/hub';
        71
        72
        73/**
        74 * Configures which WebDriver server should be used for new sessions. Overrides
        75 * the value loaded from the {@link webdriver.AbstractBuilder.SERVER_URL_ENV}
        76 * upon creation of this instance.
        77 * @param {string} url URL of the server to use.
        78 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        79 */
        80webdriver.AbstractBuilder.prototype.usingServer = function(url) {
        81 this.serverUrl_ = url;
        82 return this;
        83};
        84
        85
        86/**
        87 * @return {string} The URL of the WebDriver server this instance is configured
        88 * to use.
        89 */
        90webdriver.AbstractBuilder.prototype.getServerUrl = function() {
        91 return this.serverUrl_;
        92};
        93
        94
        95/**
        96 * Sets the desired capabilities when requesting a new session. This will
        97 * overwrite any previously set desired capabilities.
        98 * @param {!(Object|webdriver.Capabilities)} capabilities The desired
        99 * capabilities for a new session.
        100 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        101 */
        102webdriver.AbstractBuilder.prototype.withCapabilities = function(capabilities) {
        103 this.capabilities_ = new webdriver.Capabilities(capabilities);
        104 return this;
        105};
        106
        107
        108/**
        109 * @return {!webdriver.Capabilities} The current desired capabilities for this
        110 * builder.
        111 */
        112webdriver.AbstractBuilder.prototype.getCapabilities = function() {
        113 return this.capabilities_;
        114};
        115
        116
        117/**
        118 * Builds a new {@link webdriver.WebDriver} instance using this builder's
        119 * current configuration.
        120 * @return {!webdriver.WebDriver} A new WebDriver client.
        121 */
        122webdriver.AbstractBuilder.prototype.build = goog.abstractMethod;
        \ No newline at end of file +abstractbuilder.js

        lib/webdriver/abstractbuilder.js

        1// Copyright 2012 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.AbstractBuilder');
        16
        17goog.require('webdriver.Capabilities');
        18goog.require('webdriver.process');
        19
        20
        21
        22/**
        23 * Creates new {@code webdriver.WebDriver} clients. Upon instantiation, each
        24 * Builder will configure itself based on the following environment variables:
        25 * <dl>
        26 * <dt>{@code webdriver.AbstractBuilder.SERVER_URL_ENV}</dt>
        27 * <dd>Defines the remote WebDriver server that should be used for command
        28 * command execution; may be overridden using
        29 * {@code webdriver.AbstractBuilder.prototype.usingServer}.</dd>
        30 * </dl>
        31 * @constructor
        32 */
        33webdriver.AbstractBuilder = function() {
        34
        35 /**
        36 * URL of the remote server to use for new clients; initialized from the
        37 * value of the {@link webdriver.AbstractBuilder.SERVER_URL_ENV} environment
        38 * variable, but may be overridden using
        39 * {@link webdriver.AbstractBuilder#usingServer}.
        40 * @private {string}
        41 */
        42 this.serverUrl_ = webdriver.process.getEnv(
        43 webdriver.AbstractBuilder.SERVER_URL_ENV);
        44
        45 /**
        46 * The desired capabilities to use when creating a new session.
        47 * @private {!webdriver.Capabilities}
        48 */
        49 this.capabilities_ = new webdriver.Capabilities();
        50};
        51
        52
        53/**
        54 * Environment variable that defines the URL of the WebDriver server that
        55 * should be used for all new WebDriver clients. This setting may be overridden
        56 * using {@code #usingServer(url)}.
        57 * @type {string}
        58 * @const
        59 * @see webdriver.process.getEnv
        60 */
        61webdriver.AbstractBuilder.SERVER_URL_ENV = 'wdurl';
        62
        63
        64/**
        65 * The default URL of the WebDriver server to use if
        66 * {@link webdriver.AbstractBuilder.SERVER_URL_ENV} is not set.
        67 * @type {string}
        68 * @const
        69 */
        70webdriver.AbstractBuilder.DEFAULT_SERVER_URL = 'http://localhost:4444/wd/hub';
        71
        72
        73/**
        74 * Configures which WebDriver server should be used for new sessions. Overrides
        75 * the value loaded from the {@link webdriver.AbstractBuilder.SERVER_URL_ENV}
        76 * upon creation of this instance.
        77 * @param {string} url URL of the server to use.
        78 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        79 */
        80webdriver.AbstractBuilder.prototype.usingServer = function(url) {
        81 this.serverUrl_ = url;
        82 return this;
        83};
        84
        85
        86/**
        87 * @return {string} The URL of the WebDriver server this instance is configured
        88 * to use.
        89 */
        90webdriver.AbstractBuilder.prototype.getServerUrl = function() {
        91 return this.serverUrl_;
        92};
        93
        94
        95/**
        96 * Sets the desired capabilities when requesting a new session. This will
        97 * overwrite any previously set desired capabilities.
        98 * @param {!(Object|webdriver.Capabilities)} capabilities The desired
        99 * capabilities for a new session.
        100 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        101 */
        102webdriver.AbstractBuilder.prototype.withCapabilities = function(capabilities) {
        103 this.capabilities_ = new webdriver.Capabilities(capabilities);
        104 return this;
        105};
        106
        107
        108/**
        109 * @return {!webdriver.Capabilities} The current desired capabilities for this
        110 * builder.
        111 */
        112webdriver.AbstractBuilder.prototype.getCapabilities = function() {
        113 return this.capabilities_;
        114};
        115
        116
        117/**
        118 * Sets the logging preferences for the created session. Preferences may be
        119 * changed by repeated calls, or by calling {@link #withCapabilities}.
        120 * @param {!(webdriver.logging.Preferences|Object.<string, string>)} prefs The
        121 * desired logging preferences.
        122 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        123 */
        124webdriver.AbstractBuilder.prototype.setLoggingPreferences = function(prefs) {
        125 this.capabilities_.set(webdriver.Capability.LOGGING_PREFS, prefs);
        126 return this;
        127};
        128
        129
        130/**
        131 * Builds a new {@link webdriver.WebDriver} instance using this builder's
        132 * current configuration.
        133 * @return {!webdriver.WebDriver} A new WebDriver client.
        134 */
        135webdriver.AbstractBuilder.prototype.build = goog.abstractMethod;
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/actionsequence.js.src.html b/docs/source/lib/webdriver/actionsequence.js.src.html index 711b4de..265273a 100644 --- a/docs/source/lib/webdriver/actionsequence.js.src.html +++ b/docs/source/lib/webdriver/actionsequence.js.src.html @@ -1 +1 @@ -actionsequence.js

        lib/webdriver/actionsequence.js

        1// Copyright 2012 Selenium comitters
        2// Copyright 2012 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16goog.provide('webdriver.ActionSequence');
        17
        18goog.require('goog.array');
        19goog.require('webdriver.Button');
        20goog.require('webdriver.Command');
        21goog.require('webdriver.CommandName');
        22goog.require('webdriver.Key');
        23
        24
        25
        26/**
        27 * Class for defining sequences of complex user interactions. Each sequence
        28 * will not be executed until {@link #perform} is called.
        29 *
        30 * <p>Example:<pre><code>
        31 * new webdriver.ActionSequence(driver).
        32 * keyDown(webdriver.Key.SHIFT).
        33 * click(element1).
        34 * click(element2).
        35 * dragAndDrop(element3, element4).
        36 * keyUp(webdriver.Key.SHIFT).
        37 * perform();
        38 * </pre></code>
        39 *
        40 * @param {!webdriver.WebDriver} driver The driver instance to use.
        41 * @constructor
        42 */
        43webdriver.ActionSequence = function(driver) {
        44
        45 /** @private {!webdriver.WebDriver} */
        46 this.driver_ = driver;
        47
        48 /** @private {!Array.<{description: string, command: !webdriver.Command}>} */
        49 this.actions_ = [];
        50};
        51
        52
        53/**
        54 * Schedules an action to be executed each time {@link #perform} is called on
        55 * this instance.
        56 * @param {string} description A description of the command.
        57 * @param {!webdriver.Command} command The command.
        58 * @private
        59 */
        60webdriver.ActionSequence.prototype.schedule_ = function(description, command) {
        61 this.actions_.push({
        62 description: description,
        63 command: command
        64 });
        65};
        66
        67
        68/**
        69 * Executes this action sequence.
        70 * @return {!webdriver.promise.Promise} A promise that will be resolved once
        71 * this sequence has completed.
        72 */
        73webdriver.ActionSequence.prototype.perform = function() {
        74 // Make a protected copy of the scheduled actions. This will protect against
        75 // users defining additional commands before this sequence is actually
        76 // executed.
        77 var actions = goog.array.clone(this.actions_);
        78 var driver = this.driver_;
        79 return driver.controlFlow().execute(function() {
        80 goog.array.forEach(actions, function(action) {
        81 driver.schedule(action.command, action.description);
        82 });
        83 }, 'ActionSequence.perform');
        84};
        85
        86
        87/**
        88 * Moves the mouse. The location to move to may be specified in terms of the
        89 * mouse's current location, an offset relative to the top-left corner of an
        90 * element, or an element (in which case the middle of the element is used).
        91 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
        92 * location to drag to, as either another WebElement or an offset in pixels.
        93 * @param {{x: number, y: number}=} opt_offset If the target {@code location}
        94 * is defined as a {@link webdriver.WebElement}, this parameter defines an
        95 * offset within that element. The offset should be specified in pixels
        96 * relative to the top-left corner of the element's bounding box. If
        97 * omitted, the element's center will be used as the target offset.
        98 * @return {!webdriver.ActionSequence} A self reference.
        99 */
        100webdriver.ActionSequence.prototype.mouseMove = function(location, opt_offset) {
        101 var command = new webdriver.Command(webdriver.CommandName.MOVE_TO);
        102
        103 if (goog.isNumber(location.x)) {
        104 setOffset(/** @type {{x: number, y: number}} */(location));
        105 } else {
        106 // The interactions API expect the element ID to be encoded as a simple
        107 // string, not the usual JSON object.
        108 var id = /** @type {!webdriver.WebElement} */ (location).toWireValue().
        109 then(function(value) {
        110 return value['ELEMENT'];
        111 });
        112 command.setParameter('element', id);
        113 if (opt_offset) {
        114 setOffset(opt_offset);
        115 }
        116 }
        117
        118 this.schedule_('mouseMove', command);
        119 return this;
        120
        121 /** @param {{x: number, y: number}} offset The offset to use. */
        122 function setOffset(offset) {
        123 command.setParameter('xoffset', offset.x || 0);
        124 command.setParameter('yoffset', offset.y || 0);
        125 }
        126};
        127
        128
        129/**
        130 * Schedules a mouse action.
        131 * @param {string} description A simple descriptive label for the scheduled
        132 * action.
        133 * @param {!webdriver.CommandName} commandName The name of the command.
        134 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        135 * the element to interact with or the button to click with.
        136 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        137 * button is specified.
        138 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        139 * {@link webdriver.Button.LEFT}. Ignored if the previous argument is
        140 * provided as a button.
        141 * @return {!webdriver.ActionSequence} A self reference.
        142 * @private
        143 */
        144webdriver.ActionSequence.prototype.scheduleMouseAction_ = function(
        145 description, commandName, opt_elementOrButton, opt_button) {
        146 var button;
        147 if (goog.isNumber(opt_elementOrButton)) {
        148 button = opt_elementOrButton;
        149 } else {
        150 if (opt_elementOrButton) {
        151 this.mouseMove(
        152 /** @type {!webdriver.WebElement} */ (opt_elementOrButton));
        153 }
        154 button = goog.isDef(opt_button) ? opt_button : webdriver.Button.LEFT;
        155 }
        156
        157 var command = new webdriver.Command(commandName).
        158 setParameter('button', button);
        159 this.schedule_(description, command);
        160 return this;
        161};
        162
        163
        164/**
        165 * Presses a mouse button. The mouse button will not be released until
        166 * {@link #mouseUp} is called, regardless of whether that call is made in this
        167 * sequence or another. The behavior for out-of-order events (e.g. mouseDown,
        168 * click) is undefined.
        169 *
        170 * <p>If an element is provided, the mouse will first be moved to the center
        171 * of that element. This is equivalent to:
        172 * <pre><code>sequence.mouseMove(element).mouseDown()</code></pre>
        173 *
        174 * <p>Warning: this method currently only supports the left mouse button. See
        175 * http://code.google.com/p/selenium/issues/detail?id=4047
        176 *
        177 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        178 * the element to interact with or the button to click with.
        179 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        180 * button is specified.
        181 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        182 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        183 * first argument.
        184 * @return {!webdriver.ActionSequence} A self reference.
        185 */
        186webdriver.ActionSequence.prototype.mouseDown = function(opt_elementOrButton,
        187 opt_button) {
        188 return this.scheduleMouseAction_('mouseDown',
        189 webdriver.CommandName.MOUSE_DOWN, opt_elementOrButton, opt_button);
        190};
        191
        192
        193/**
        194 * Releases a mouse button. Behavior is undefined for calling this function
        195 * without a previous call to {@link #mouseDown}.
        196 *
        197 * <p>If an element is provided, the mouse will first be moved to the center
        198 * of that element. This is equivalent to:
        199 * <pre><code>sequence.mouseMove(element).mouseUp()</code></pre>
        200 *
        201 * <p>Warning: this method currently only supports the left mouse button. See
        202 * http://code.google.com/p/selenium/issues/detail?id=4047
        203 *
        204 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        205 * the element to interact with or the button to click with.
        206 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        207 * button is specified.
        208 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        209 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        210 * first argument.
        211 * @return {!webdriver.ActionSequence} A self reference.
        212 */
        213webdriver.ActionSequence.prototype.mouseUp = function(opt_elementOrButton,
        214 opt_button) {
        215 return this.scheduleMouseAction_('mouseUp',
        216 webdriver.CommandName.MOUSE_UP, opt_elementOrButton, opt_button);
        217};
        218
        219
        220/**
        221 * Convenience function for performing a "drag and drop" manuever. The target
        222 * element may be moved to the location of another element, or by an offset (in
        223 * pixels).
        224 * @param {!webdriver.WebElement} element The element to drag.
        225 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
        226 * location to drag to, either as another WebElement or an offset in pixels.
        227 * @return {!webdriver.ActionSequence} A self reference.
        228 */
        229webdriver.ActionSequence.prototype.dragAndDrop = function(element, location) {
        230 return this.mouseDown(element).mouseMove(location).mouseUp();
        231};
        232
        233
        234/**
        235 * Clicks a mouse button.
        236 *
        237 * <p>If an element is provided, the mouse will first be moved to the center
        238 * of that element. This is equivalent to:
        239 * <pre><code>sequence.mouseMove(element).click()</code></pre>
        240 *
        241 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        242 * the element to interact with or the button to click with.
        243 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        244 * button is specified.
        245 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        246 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        247 * first argument.
        248 * @return {!webdriver.ActionSequence} A self reference.
        249 */
        250webdriver.ActionSequence.prototype.click = function(opt_elementOrButton,
        251 opt_button) {
        252 return this.scheduleMouseAction_('click',
        253 webdriver.CommandName.CLICK, opt_elementOrButton, opt_button);
        254};
        255
        256
        257/**
        258 * Double-clicks a mouse button.
        259 *
        260 * <p>If an element is provided, the mouse will first be moved to the center of
        261 * that element. This is equivalent to:
        262 * <pre><code>sequence.mouseMove(element).doubleClick()</code></pre>
        263 *
        264 * <p>Warning: this method currently only supports the left mouse button. See
        265 * http://code.google.com/p/selenium/issues/detail?id=4047
        266 *
        267 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        268 * the element to interact with or the button to click with.
        269 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        270 * button is specified.
        271 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        272 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        273 * first argument.
        274 * @return {!webdriver.ActionSequence} A self reference.
        275 */
        276webdriver.ActionSequence.prototype.doubleClick = function(opt_elementOrButton,
        277 opt_button) {
        278 return this.scheduleMouseAction_('doubleClick',
        279 webdriver.CommandName.DOUBLE_CLICK, opt_elementOrButton, opt_button);
        280};
        281
        282
        283/**
        284 * Schedules a keyboard action.
        285 * @param {string} description A simple descriptive label for the scheduled
        286 * action.
        287 * @param {!Array.<(string|!webdriver.Key)>} keys The keys to send.
        288 * @return {!webdriver.ActionSequence} A self reference.
        289 * @private
        290 */
        291webdriver.ActionSequence.prototype.scheduleKeyboardAction_ = function(
        292 description, keys) {
        293 var command =
        294 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT).
        295 setParameter('value', keys);
        296 this.schedule_(description, command);
        297 return this;
        298};
        299
        300
        301/**
        302 * Checks that a key is a modifier key.
        303 * @param {!webdriver.Key} key The key to check.
        304 * @throws {Error} If the key is not a modifier key.
        305 * @private
        306 */
        307webdriver.ActionSequence.checkModifierKey_ = function(key) {
        308 if (key !== webdriver.Key.ALT && key !== webdriver.Key.CONTROL &&
        309 key !== webdriver.Key.SHIFT && key !== webdriver.Key.COMMAND) {
        310 throw Error('Not a modifier key');
        311 }
        312};
        313
        314
        315/**
        316 * Performs a modifier key press. The modifier key is <em>not released</em>
        317 * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be
        318 * targetted at the currently focused element.
        319 * @param {!webdriver.Key} key The modifier key to push. Must be one of
        320 * {ALT, CONTROL, SHIFT, COMMAND, META}.
        321 * @return {!webdriver.ActionSequence} A self reference.
        322 * @throws {Error} If the key is not a valid modifier key.
        323 */
        324webdriver.ActionSequence.prototype.keyDown = function(key) {
        325 webdriver.ActionSequence.checkModifierKey_(key);
        326 return this.scheduleKeyboardAction_('keyDown', [key]);
        327};
        328
        329
        330/**
        331 * Performs a modifier key release. The release is targetted at the currently
        332 * focused element.
        333 * @param {!webdriver.Key} key The modifier key to release. Must be one of
        334 * {ALT, CONTROL, SHIFT, COMMAND, META}.
        335 * @return {!webdriver.ActionSequence} A self reference.
        336 * @throws {Error} If the key is not a valid modifier key.
        337 */
        338webdriver.ActionSequence.prototype.keyUp = function(key) {
        339 webdriver.ActionSequence.checkModifierKey_(key);
        340 return this.scheduleKeyboardAction_('keyUp', [key]);
        341};
        342
        343
        344/**
        345 * Simulates typing multiple keys. Each modifier key encountered in the
        346 * sequence will not be released until it is encountered again. All key events
        347 * will be targetted at the currently focused element.
        348 * @param {...(string|!webdriver.Key|!Array.<(string|!webdriver.Key)>)} var_args
        349 * The keys to type.
        350 * @return {!webdriver.ActionSequence} A self reference.
        351 * @throws {Error} If the key is not a valid modifier key.
        352 */
        353webdriver.ActionSequence.prototype.sendKeys = function(var_args) {
        354 var keys = goog.array.flatten(goog.array.slice(arguments, 0));
        355 return this.scheduleKeyboardAction_('sendKeys', keys);
        356};
        \ No newline at end of file +actionsequence.js

        lib/webdriver/actionsequence.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.ActionSequence');
        19
        20goog.require('goog.array');
        21goog.require('webdriver.Button');
        22goog.require('webdriver.Command');
        23goog.require('webdriver.CommandName');
        24goog.require('webdriver.Key');
        25
        26
        27
        28/**
        29 * Class for defining sequences of complex user interactions. Each sequence
        30 * will not be executed until {@link #perform} is called.
        31 *
        32 * Example:
        33 *
        34 * new webdriver.ActionSequence(driver).
        35 * keyDown(webdriver.Key.SHIFT).
        36 * click(element1).
        37 * click(element2).
        38 * dragAndDrop(element3, element4).
        39 * keyUp(webdriver.Key.SHIFT).
        40 * perform();
        41 *
        42 * @param {!webdriver.WebDriver} driver The driver instance to use.
        43 * @constructor
        44 */
        45webdriver.ActionSequence = function(driver) {
        46
        47 /** @private {!webdriver.WebDriver} */
        48 this.driver_ = driver;
        49
        50 /** @private {!Array.<{description: string, command: !webdriver.Command}>} */
        51 this.actions_ = [];
        52};
        53
        54
        55/**
        56 * Schedules an action to be executed each time {@link #perform} is called on
        57 * this instance.
        58 * @param {string} description A description of the command.
        59 * @param {!webdriver.Command} command The command.
        60 * @private
        61 */
        62webdriver.ActionSequence.prototype.schedule_ = function(description, command) {
        63 this.actions_.push({
        64 description: description,
        65 command: command
        66 });
        67};
        68
        69
        70/**
        71 * Executes this action sequence.
        72 * @return {!webdriver.promise.Promise} A promise that will be resolved once
        73 * this sequence has completed.
        74 */
        75webdriver.ActionSequence.prototype.perform = function() {
        76 // Make a protected copy of the scheduled actions. This will protect against
        77 // users defining additional commands before this sequence is actually
        78 // executed.
        79 var actions = goog.array.clone(this.actions_);
        80 var driver = this.driver_;
        81 return driver.controlFlow().execute(function() {
        82 goog.array.forEach(actions, function(action) {
        83 driver.schedule(action.command, action.description);
        84 });
        85 }, 'ActionSequence.perform');
        86};
        87
        88
        89/**
        90 * Moves the mouse. The location to move to may be specified in terms of the
        91 * mouse's current location, an offset relative to the top-left corner of an
        92 * element, or an element (in which case the middle of the element is used).
        93 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
        94 * location to drag to, as either another WebElement or an offset in pixels.
        95 * @param {{x: number, y: number}=} opt_offset If the target {@code location}
        96 * is defined as a {@link webdriver.WebElement}, this parameter defines an
        97 * offset within that element. The offset should be specified in pixels
        98 * relative to the top-left corner of the element's bounding box. If
        99 * omitted, the element's center will be used as the target offset.
        100 * @return {!webdriver.ActionSequence} A self reference.
        101 */
        102webdriver.ActionSequence.prototype.mouseMove = function(location, opt_offset) {
        103 var command = new webdriver.Command(webdriver.CommandName.MOVE_TO);
        104
        105 if (goog.isNumber(location.x)) {
        106 setOffset(/** @type {{x: number, y: number}} */(location));
        107 } else {
        108 command.setParameter('element', location.getRawId());
        109 if (opt_offset) {
        110 setOffset(opt_offset);
        111 }
        112 }
        113
        114 this.schedule_('mouseMove', command);
        115 return this;
        116
        117 /** @param {{x: number, y: number}} offset The offset to use. */
        118 function setOffset(offset) {
        119 command.setParameter('xoffset', offset.x || 0);
        120 command.setParameter('yoffset', offset.y || 0);
        121 }
        122};
        123
        124
        125/**
        126 * Schedules a mouse action.
        127 * @param {string} description A simple descriptive label for the scheduled
        128 * action.
        129 * @param {!webdriver.CommandName} commandName The name of the command.
        130 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        131 * the element to interact with or the button to click with.
        132 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        133 * button is specified.
        134 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        135 * {@link webdriver.Button.LEFT}. Ignored if the previous argument is
        136 * provided as a button.
        137 * @return {!webdriver.ActionSequence} A self reference.
        138 * @private
        139 */
        140webdriver.ActionSequence.prototype.scheduleMouseAction_ = function(
        141 description, commandName, opt_elementOrButton, opt_button) {
        142 var button;
        143 if (goog.isNumber(opt_elementOrButton)) {
        144 button = opt_elementOrButton;
        145 } else {
        146 if (opt_elementOrButton) {
        147 this.mouseMove(
        148 /** @type {!webdriver.WebElement} */ (opt_elementOrButton));
        149 }
        150 button = goog.isDef(opt_button) ? opt_button : webdriver.Button.LEFT;
        151 }
        152
        153 var command = new webdriver.Command(commandName).
        154 setParameter('button', button);
        155 this.schedule_(description, command);
        156 return this;
        157};
        158
        159
        160/**
        161 * Presses a mouse button. The mouse button will not be released until
        162 * {@link #mouseUp} is called, regardless of whether that call is made in this
        163 * sequence or another. The behavior for out-of-order events (e.g. mouseDown,
        164 * click) is undefined.
        165 *
        166 * If an element is provided, the mouse will first be moved to the center
        167 * of that element. This is equivalent to:
        168 *
        169 * sequence.mouseMove(element).mouseDown()
        170 *
        171 * Warning: this method currently only supports the left mouse button. See
        172 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
        173 *
        174 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        175 * the element to interact with or the button to click with.
        176 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        177 * button is specified.
        178 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        179 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        180 * first argument.
        181 * @return {!webdriver.ActionSequence} A self reference.
        182 */
        183webdriver.ActionSequence.prototype.mouseDown = function(opt_elementOrButton,
        184 opt_button) {
        185 return this.scheduleMouseAction_('mouseDown',
        186 webdriver.CommandName.MOUSE_DOWN, opt_elementOrButton, opt_button);
        187};
        188
        189
        190/**
        191 * Releases a mouse button. Behavior is undefined for calling this function
        192 * without a previous call to {@link #mouseDown}.
        193 *
        194 * If an element is provided, the mouse will first be moved to the center
        195 * of that element. This is equivalent to:
        196 *
        197 * sequence.mouseMove(element).mouseUp()
        198 *
        199 * Warning: this method currently only supports the left mouse button. See
        200 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
        201 *
        202 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        203 * the element to interact with or the button to click with.
        204 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        205 * button is specified.
        206 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        207 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        208 * first argument.
        209 * @return {!webdriver.ActionSequence} A self reference.
        210 */
        211webdriver.ActionSequence.prototype.mouseUp = function(opt_elementOrButton,
        212 opt_button) {
        213 return this.scheduleMouseAction_('mouseUp',
        214 webdriver.CommandName.MOUSE_UP, opt_elementOrButton, opt_button);
        215};
        216
        217
        218/**
        219 * Convenience function for performing a "drag and drop" manuever. The target
        220 * element may be moved to the location of another element, or by an offset (in
        221 * pixels).
        222 * @param {!webdriver.WebElement} element The element to drag.
        223 * @param {(!webdriver.WebElement|{x: number, y: number})} location The
        224 * location to drag to, either as another WebElement or an offset in pixels.
        225 * @return {!webdriver.ActionSequence} A self reference.
        226 */
        227webdriver.ActionSequence.prototype.dragAndDrop = function(element, location) {
        228 return this.mouseDown(element).mouseMove(location).mouseUp();
        229};
        230
        231
        232/**
        233 * Clicks a mouse button.
        234 *
        235 * If an element is provided, the mouse will first be moved to the center
        236 * of that element. This is equivalent to:
        237 *
        238 * sequence.mouseMove(element).click()
        239 *
        240 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        241 * the element to interact with or the button to click with.
        242 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        243 * button is specified.
        244 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        245 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        246 * first argument.
        247 * @return {!webdriver.ActionSequence} A self reference.
        248 */
        249webdriver.ActionSequence.prototype.click = function(opt_elementOrButton,
        250 opt_button) {
        251 return this.scheduleMouseAction_('click',
        252 webdriver.CommandName.CLICK, opt_elementOrButton, opt_button);
        253};
        254
        255
        256/**
        257 * Double-clicks a mouse button.
        258 *
        259 * If an element is provided, the mouse will first be moved to the center of
        260 * that element. This is equivalent to:
        261 *
        262 * sequence.mouseMove(element).doubleClick()
        263 *
        264 * Warning: this method currently only supports the left mouse button. See
        265 * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047).
        266 *
        267 * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either
        268 * the element to interact with or the button to click with.
        269 * Defaults to {@link webdriver.Button.LEFT} if neither an element nor
        270 * button is specified.
        271 * @param {webdriver.Button=} opt_button The button to use. Defaults to
        272 * {@link webdriver.Button.LEFT}. Ignored if a button is provided as the
        273 * first argument.
        274 * @return {!webdriver.ActionSequence} A self reference.
        275 */
        276webdriver.ActionSequence.prototype.doubleClick = function(opt_elementOrButton,
        277 opt_button) {
        278 return this.scheduleMouseAction_('doubleClick',
        279 webdriver.CommandName.DOUBLE_CLICK, opt_elementOrButton, opt_button);
        280};
        281
        282
        283/**
        284 * Schedules a keyboard action.
        285 * @param {string} description A simple descriptive label for the scheduled
        286 * action.
        287 * @param {!Array.<(string|!webdriver.Key)>} keys The keys to send.
        288 * @return {!webdriver.ActionSequence} A self reference.
        289 * @private
        290 */
        291webdriver.ActionSequence.prototype.scheduleKeyboardAction_ = function(
        292 description, keys) {
        293 var command =
        294 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT).
        295 setParameter('value', keys);
        296 this.schedule_(description, command);
        297 return this;
        298};
        299
        300
        301/**
        302 * Checks that a key is a modifier key.
        303 * @param {!webdriver.Key} key The key to check.
        304 * @throws {Error} If the key is not a modifier key.
        305 * @private
        306 */
        307webdriver.ActionSequence.checkModifierKey_ = function(key) {
        308 if (key !== webdriver.Key.ALT && key !== webdriver.Key.CONTROL &&
        309 key !== webdriver.Key.SHIFT && key !== webdriver.Key.COMMAND) {
        310 throw Error('Not a modifier key');
        311 }
        312};
        313
        314
        315/**
        316 * Performs a modifier key press. The modifier key is <em>not released</em>
        317 * until {@link #keyUp} or {@link #sendKeys} is called. The key press will be
        318 * targetted at the currently focused element.
        319 * @param {!webdriver.Key} key The modifier key to push. Must be one of
        320 * {ALT, CONTROL, SHIFT, COMMAND, META}.
        321 * @return {!webdriver.ActionSequence} A self reference.
        322 * @throws {Error} If the key is not a valid modifier key.
        323 */
        324webdriver.ActionSequence.prototype.keyDown = function(key) {
        325 webdriver.ActionSequence.checkModifierKey_(key);
        326 return this.scheduleKeyboardAction_('keyDown', [key]);
        327};
        328
        329
        330/**
        331 * Performs a modifier key release. The release is targetted at the currently
        332 * focused element.
        333 * @param {!webdriver.Key} key The modifier key to release. Must be one of
        334 * {ALT, CONTROL, SHIFT, COMMAND, META}.
        335 * @return {!webdriver.ActionSequence} A self reference.
        336 * @throws {Error} If the key is not a valid modifier key.
        337 */
        338webdriver.ActionSequence.prototype.keyUp = function(key) {
        339 webdriver.ActionSequence.checkModifierKey_(key);
        340 return this.scheduleKeyboardAction_('keyUp', [key]);
        341};
        342
        343
        344/**
        345 * Simulates typing multiple keys. Each modifier key encountered in the
        346 * sequence will not be released until it is encountered again. All key events
        347 * will be targetted at the currently focused element.
        348 * @param {...(string|!webdriver.Key|!Array.<(string|!webdriver.Key)>)} var_args
        349 * The keys to type.
        350 * @return {!webdriver.ActionSequence} A self reference.
        351 * @throws {Error} If the key is not a valid modifier key.
        352 */
        353webdriver.ActionSequence.prototype.sendKeys = function(var_args) {
        354 var keys = goog.array.flatten(goog.array.slice(arguments, 0));
        355 return this.scheduleKeyboardAction_('sendKeys', keys);
        356};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/builder.js.src.html b/docs/source/lib/webdriver/builder.js.src.html index 8e10924..c51d70d 100644 --- a/docs/source/lib/webdriver/builder.js.src.html +++ b/docs/source/lib/webdriver/builder.js.src.html @@ -1 +1 @@ -builder.js

        lib/webdriver/builder.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.Builder');
        16
        17goog.require('goog.userAgent');
        18goog.require('webdriver.AbstractBuilder');
        19goog.require('webdriver.FirefoxDomExecutor');
        20goog.require('webdriver.WebDriver');
        21goog.require('webdriver.http.CorsClient');
        22goog.require('webdriver.http.Executor');
        23goog.require('webdriver.http.XhrClient');
        24goog.require('webdriver.process');
        25
        26
        27
        28/**
        29 * @constructor
        30 * @extends {webdriver.AbstractBuilder}
        31 */
        32webdriver.Builder = function() {
        33 goog.base(this);
        34
        35 /**
        36 * ID of an existing WebDriver session that new clients should use.
        37 * Initialized from the value of the
        38 * {@link webdriver.AbstractBuilder.SESSION_ID_ENV} environment variable, but
        39 * may be overridden using
        40 * {@link webdriver.AbstractBuilder#usingSession}.
        41 * @private {string}
        42 */
        43 this.sessionId_ =
        44 webdriver.process.getEnv(webdriver.Builder.SESSION_ID_ENV);
        45};
        46goog.inherits(webdriver.Builder, webdriver.AbstractBuilder);
        47
        48
        49/**
        50 * Environment variable that defines the session ID of an existing WebDriver
        51 * session to use when creating clients. If set, all new Builder instances will
        52 * default to creating clients that use this session. To create a new session,
        53 * use {@code #useExistingSession(boolean)}. The use of this environment
        54 * variable requires that {@link webdriver.AbstractBuilder.SERVER_URL_ENV} also
        55 * be set.
        56 * @type {string}
        57 * @const
        58 * @see webdriver.process.getEnv
        59 */
        60webdriver.Builder.SESSION_ID_ENV = 'wdsid';
        61
        62
        63/**
        64 * Configures the builder to create a client that will use an existing WebDriver
        65 * session.
        66 * @param {string} id The existing session ID to use.
        67 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        68 */
        69webdriver.Builder.prototype.usingSession = function(id) {
        70 this.sessionId_ = id;
        71 return this;
        72};
        73
        74
        75/**
        76 * @return {string} The ID of the session, if any, this builder is configured
        77 * to reuse.
        78 */
        79webdriver.Builder.prototype.getSession = function() {
        80 return this.sessionId_;
        81};
        82
        83
        84/**
        85 * @override
        86 */
        87webdriver.Builder.prototype.build = function() {
        88 if (goog.userAgent.GECKO && document.readyState != 'complete') {
        89 throw Error('Cannot create driver instance before window.onload');
        90 }
        91
        92 var executor;
        93
        94 if (webdriver.FirefoxDomExecutor.isAvailable()) {
        95 executor = new webdriver.FirefoxDomExecutor();
        96 return webdriver.WebDriver.createSession(executor, this.getCapabilities());
        97 } else {
        98 var url = this.getServerUrl() ||
        99 webdriver.AbstractBuilder.DEFAULT_SERVER_URL;
        100 var client;
        101 if (url[0] == '/') {
        102 var origin = window.location.origin ||
        103 (window.location.protocol + '//' + window.location.host);
        104 client = new webdriver.http.XhrClient(origin + url);
        105 } else {
        106 client = new webdriver.http.CorsClient(url);
        107 }
        108 executor = new webdriver.http.Executor(client);
        109
        110 if (this.getSession()) {
        111 return webdriver.WebDriver.attachToSession(executor, this.getSession());
        112 } else {
        113 throw new Error('Unable to create a new client for this browser. The ' +
        114 'WebDriver session ID has not been defined.');
        115 }
        116 }
        117};
        \ No newline at end of file +builder.js

        lib/webdriver/builder.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.Builder');
        16
        17goog.require('goog.userAgent');
        18goog.require('webdriver.AbstractBuilder');
        19goog.require('webdriver.FirefoxDomExecutor');
        20goog.require('webdriver.WebDriver');
        21goog.require('webdriver.http.CorsClient');
        22goog.require('webdriver.http.Executor');
        23goog.require('webdriver.http.XhrClient');
        24goog.require('webdriver.process');
        25
        26
        27
        28/**
        29 * @constructor
        30 * @extends {webdriver.AbstractBuilder}
        31 */
        32webdriver.Builder = function() {
        33 goog.base(this);
        34
        35 /**
        36 * ID of an existing WebDriver session that new clients should use.
        37 * Initialized from the value of the
        38 * {@link webdriver.AbstractBuilder.SESSION_ID_ENV} environment variable, but
        39 * may be overridden using
        40 * {@link webdriver.AbstractBuilder#usingSession}.
        41 * @private {string}
        42 */
        43 this.sessionId_ =
        44 webdriver.process.getEnv(webdriver.Builder.SESSION_ID_ENV);
        45};
        46goog.inherits(webdriver.Builder, webdriver.AbstractBuilder);
        47
        48
        49/**
        50 * Environment variable that defines the session ID of an existing WebDriver
        51 * session to use when creating clients. If set, all new Builder instances will
        52 * default to creating clients that use this session. To create a new session,
        53 * use {@code #useExistingSession(boolean)}. The use of this environment
        54 * variable requires that {@link webdriver.AbstractBuilder.SERVER_URL_ENV} also
        55 * be set.
        56 * @type {string}
        57 * @const
        58 * @see webdriver.process.getEnv
        59 */
        60webdriver.Builder.SESSION_ID_ENV = 'wdsid';
        61
        62
        63/**
        64 * Configures the builder to create a client that will use an existing WebDriver
        65 * session.
        66 * @param {string} id The existing session ID to use.
        67 * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling.
        68 */
        69webdriver.Builder.prototype.usingSession = function(id) {
        70 this.sessionId_ = id;
        71 return this;
        72};
        73
        74
        75/**
        76 * @return {string} The ID of the session, if any, this builder is configured
        77 * to reuse.
        78 */
        79webdriver.Builder.prototype.getSession = function() {
        80 return this.sessionId_;
        81};
        82
        83
        84/**
        85 * @override
        86 */
        87webdriver.Builder.prototype.build = function() {
        88 if (goog.userAgent.GECKO && document.readyState != 'complete') {
        89 throw Error('Cannot create driver instance before window.onload');
        90 }
        91
        92 var executor;
        93
        94 if (webdriver.FirefoxDomExecutor.isAvailable()) {
        95 executor = new webdriver.FirefoxDomExecutor();
        96 return webdriver.WebDriver.createSession(executor, this.getCapabilities());
        97 } else {
        98 var url = this.getServerUrl() ||
        99 webdriver.AbstractBuilder.DEFAULT_SERVER_URL;
        100 var client;
        101 if (url[0] == '/') {
        102 var origin = window.location.origin ||
        103 (window.location.protocol + '//' + window.location.host);
        104 client = new webdriver.http.XhrClient(origin + url);
        105 } else {
        106 client = new webdriver.http.CorsClient(url);
        107 }
        108 executor = new webdriver.http.Executor(client);
        109
        110 if (this.getSession()) {
        111 return webdriver.WebDriver.attachToSession(executor, this.getSession());
        112 } else {
        113 throw new Error('Unable to create a new client for this browser. The ' +
        114 'WebDriver session ID has not been defined.');
        115 }
        116 }
        117};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/button.js.src.html b/docs/source/lib/webdriver/button.js.src.html index 2a8728a..6d00b9a 100644 --- a/docs/source/lib/webdriver/button.js.src.html +++ b/docs/source/lib/webdriver/button.js.src.html @@ -1 +1 @@ -button.js

        lib/webdriver/button.js

        1// Copyright 2012 Selenium comitters
        2// Copyright 2012 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16goog.provide('webdriver.Button');
        17
        18
        19/**
        20 * Enumeration of the buttons used in the advanced interactions API.
        21 * @enum {number}
        22 */
        23webdriver.Button = {
        24 LEFT: 0,
        25 MIDDLE: 1,
        26 RIGHT: 2
        27};
        \ No newline at end of file +button.js

        lib/webdriver/button.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.Button');
        19
        20
        21/**
        22 * Enumeration of the buttons used in the advanced interactions API.
        23 * @enum {number}
        24 */
        25webdriver.Button = {
        26 LEFT: 0,
        27 MIDDLE: 1,
        28 RIGHT: 2
        29};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/capabilities.js.src.html b/docs/source/lib/webdriver/capabilities.js.src.html index bd1a54b..4d1e7de 100644 --- a/docs/source/lib/webdriver/capabilities.js.src.html +++ b/docs/source/lib/webdriver/capabilities.js.src.html @@ -1 +1 @@ -capabilities.js

        lib/webdriver/capabilities.js

        1// Copyright 2013 Software Freedom Conservancy
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines the webdriver.Capabilities class.
        17 */
        18
        19goog.provide('webdriver.Browser');
        20goog.provide('webdriver.Capabilities');
        21goog.provide('webdriver.Capability');
        22
        23
        24
        25/**
        26 * Recognized browser names.
        27 * @enum {string}
        28 */
        29webdriver.Browser = {
        30 ANDROID: 'android',
        31 CHROME: 'chrome',
        32 FIREFOX: 'firefox',
        33 INTERNET_EXPLORER: 'internet explorer',
        34 IPAD: 'iPad',
        35 IPHONE: 'iPhone',
        36 OPERA: 'opera',
        37 PHANTOM_JS: 'phantomjs',
        38 SAFARI: 'safari',
        39 HTMLUNIT: 'htmlunit'
        40};
        41
        42
        43
        44/**
        45 * Common webdriver capability keys.
        46 * @enum {string}
        47 */
        48webdriver.Capability = {
        49
        50 /**
        51 * Indicates whether a driver should accept all SSL certs by default. This
        52 * capability only applies when requesting a new session. To query whether
        53 * a driver can handle insecure SSL certs, see
        54 * {@link webdriver.Capability.SECURE_SSL}.
        55 */
        56 ACCEPT_SSL_CERTS: 'acceptSslCerts',
        57
        58
        59 /**
        60 * The browser name. Common browser names are defined in the
        61 * {@link webdriver.Browser} enum.
        62 */
        63 BROWSER_NAME: 'browserName',
        64
        65 /**
        66 * Whether the driver is capable of handling modal alerts (e.g. alert,
        67 * confirm, prompt). To define how a driver <i>should</i> handle alerts,
        68 * use {@link webdriver.Capability.UNEXPECTED_ALERT_BEHAVIOR}.
        69 */
        70 HANDLES_ALERTS: 'handlesAlerts',
        71
        72 /**
        73 * Key for the logging driver logging preferences.
        74 */
        75 LOGGING_PREFS: 'loggingPrefs',
        76
        77 /**
        78 * Describes the platform the browser is running on. Will be one of
        79 * ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When <i>requesting</i> a
        80 * session, ANY may be used to indicate no platform preference (this is
        81 * semantically equivalent to omitting the platform capability).
        82 */
        83 PLATFORM: 'platform',
        84
        85 /**
        86 * Describes the proxy configuration to use for a new WebDriver session.
        87 */
        88 PROXY: 'proxy',
        89
        90 /** Whether the driver supports changing the brower's orientation. */
        91 ROTATABLE: 'rotatable',
        92
        93 /**
        94 * Whether a driver is only capable of handling secure SSL certs. To request
        95 * that a driver accept insecure SSL certs by default, use
        96 * {@link webdriver.Capability.ACCEPT_SSL_CERTS}.
        97 */
        98 SECURE_SSL: 'secureSsl',
        99
        100 /** Whether the driver supports manipulating the app cache. */
        101 SUPPORTS_APPLICATION_CACHE: 'applicationCacheEnabled',
        102
        103 /**
        104 * Whether the driver supports controlling the browser's internet
        105 * connectivity.
        106 */
        107 SUPPORTS_BROWSER_CONNECTION: 'browserConnectionEnabled',
        108
        109 /** Whether the driver supports locating elements with CSS selectors. */
        110 SUPPORTS_CSS_SELECTORS: 'cssSelectorsEnabled',
        111
        112 /** Whether the browser supports JavaScript. */
        113 SUPPORTS_JAVASCRIPT: 'javascriptEnabled',
        114
        115 /** Whether the driver supports controlling the browser's location info. */
        116 SUPPORTS_LOCATION_CONTEXT: 'locationContextEnabled',
        117
        118 /** Whether the driver supports taking screenshots. */
        119 TAKES_SCREENSHOT: 'takesScreenshot',
        120
        121 /**
        122 * Defines how the driver should handle unexpected alerts. The value should
        123 * be one of "accept", "dismiss", or "ignore.
        124 */
        125 UNEXPECTED_ALERT_BEHAVIOR: 'unexpectedAlertBehavior',
        126
        127 /** Defines the browser version. */
        128 VERSION: 'version'
        129};
        130
        131
        132
        133/**
        134 * @param {(webdriver.Capabilities|Object)=} opt_other Another set of
        135 * capabilities to merge into this instance.
        136 * @constructor
        137 */
        138webdriver.Capabilities = function(opt_other) {
        139
        140 /** @private {!Object} */
        141 this.caps_ = {};
        142
        143 if (opt_other) {
        144 this.merge(opt_other);
        145 }
        146};
        147
        148
        149/**
        150 * @return {!webdriver.Capabilities} A basic set of capabilities for Android.
        151 */
        152webdriver.Capabilities.android = function() {
        153 return new webdriver.Capabilities().
        154 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.ANDROID).
        155 set(webdriver.Capability.PLATFORM, 'ANDROID');
        156};
        157
        158
        159/**
        160 * @return {!webdriver.Capabilities} A basic set of capabilities for Chrome.
        161 */
        162webdriver.Capabilities.chrome = function() {
        163 return new webdriver.Capabilities().
        164 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.CHROME);
        165};
        166
        167
        168/**
        169 * @return {!webdriver.Capabilities} A basic set of capabilities for Firefox.
        170 */
        171webdriver.Capabilities.firefox = function() {
        172 return new webdriver.Capabilities().
        173 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.FIREFOX);
        174};
        175
        176
        177/**
        178 * @return {!webdriver.Capabilities} A basic set of capabilities for
        179 * Internet Explorer.
        180 */
        181webdriver.Capabilities.ie = function() {
        182 return new webdriver.Capabilities().
        183 set(webdriver.Capability.BROWSER_NAME,
        184 webdriver.Browser.INTERNET_EXPLORER).
        185 set(webdriver.Capability.PLATFORM, 'WINDOWS');
        186};
        187
        188
        189/**
        190 * @return {!webdriver.Capabilities} A basic set of capabilities for iPad.
        191 */
        192webdriver.Capabilities.ipad = function() {
        193 return new webdriver.Capabilities().
        194 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.IPAD).
        195 set(webdriver.Capability.PLATFORM, 'MAC');
        196};
        197
        198
        199/**
        200 * @return {!webdriver.Capabilities} A basic set of capabilities for iPhone.
        201 */
        202webdriver.Capabilities.iphone = function() {
        203 return new webdriver.Capabilities().
        204 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.IPHONE).
        205 set(webdriver.Capability.PLATFORM, 'MAC');
        206};
        207
        208
        209/**
        210 * @return {!webdriver.Capabilities} A basic set of capabilities for Opera.
        211 */
        212webdriver.Capabilities.opera = function() {
        213 return new webdriver.Capabilities().
        214 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.OPERA);
        215};
        216
        217
        218/**
        219 * @return {!webdriver.Capabilities} A basic set of capabilities for
        220 * PhantomJS.
        221 */
        222webdriver.Capabilities.phantomjs = function() {
        223 return new webdriver.Capabilities().
        224 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.PHANTOM_JS);
        225};
        226
        227
        228/**
        229 * @return {!webdriver.Capabilities} A basic set of capabilities for Safari.
        230 */
        231webdriver.Capabilities.safari = function() {
        232 return new webdriver.Capabilities().
        233 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.SAFARI);
        234};
        235
        236
        237/**
        238 * @return {!webdriver.Capabilities} A basic set of capabilities for HTMLUnit.
        239 */
        240webdriver.Capabilities.htmlunit = function() {
        241 return new webdriver.Capabilities().
        242 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.HTMLUNIT);
        243};
        244
        245
        246/**
        247 * @return {!webdriver.Capabilities} A basic set of capabilities for HTMLUnit
        248 * with enabled Javascript.
        249 */
        250webdriver.Capabilities.htmlunitwithjs = function() {
        251 return new webdriver.Capabilities().
        252 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.HTMLUNIT).
        253 set(webdriver.Capability.SUPPORTS_JAVASCRIPT, true);
        254};
        255
        256
        257/** @return {!Object} The JSON representation of this instance. */
        258webdriver.Capabilities.prototype.toJSON = function() {
        259 return this.caps_;
        260};
        261
        262
        263/**
        264 * Merges another set of capabilities into this instance. Any duplicates in
        265 * the provided set will override those already set on this instance.
        266 * @param {!(webdriver.Capabilities|Object)} other The capabilities to
        267 * merge into this instance.
        268 * @return {!webdriver.Capabilities} A self reference.
        269 */
        270webdriver.Capabilities.prototype.merge = function(other) {
        271 var caps = other instanceof webdriver.Capabilities ?
        272 other.caps_ : other;
        273 for (var key in caps) {
        274 if (caps.hasOwnProperty(key)) {
        275 this.set(key, caps[key]);
        276 }
        277 }
        278 return this;
        279};
        280
        281
        282/**
        283 * @param {string} key The capability to set.
        284 * @param {*} value The capability value. Capability values must be JSON
        285 * serializable. Pass {@code null} to unset the capability.
        286 * @return {!webdriver.Capabilities} A self reference.
        287 */
        288webdriver.Capabilities.prototype.set = function(key, value) {
        289 if (goog.isDefAndNotNull(value)) {
        290 this.caps_[key] = value;
        291 } else {
        292 delete this.caps_[key];
        293 }
        294 return this;
        295};
        296
        297
        298/**
        299 * @param {string} key The capability to return.
        300 * @return {*} The capability with the given key, or {@code null} if it has
        301 * not been set.
        302 */
        303webdriver.Capabilities.prototype.get = function(key) {
        304 var val = null;
        305 if (this.caps_.hasOwnProperty(key)) {
        306 val = this.caps_[key];
        307 }
        308 return goog.isDefAndNotNull(val) ? val : null;
        309};
        310
        311
        312/**
        313 * @param {string} key The capability to check.
        314 * @return {boolean} Whether the specified capability is set.
        315 */
        316webdriver.Capabilities.prototype.has = function(key) {
        317 return !!this.get(key);
        318};
        \ No newline at end of file +capabilities.js

        lib/webdriver/capabilities.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines the webdriver.Capabilities class.
        20 */
        21
        22goog.provide('webdriver.Browser');
        23goog.provide('webdriver.Capabilities');
        24goog.provide('webdriver.Capability');
        25goog.provide('webdriver.ProxyConfig');
        26
        27goog.require('webdriver.Serializable');
        28goog.require('webdriver.logging');
        29
        30
        31
        32/**
        33 * Recognized browser names.
        34 * @enum {string}
        35 */
        36webdriver.Browser = {
        37 ANDROID: 'android',
        38 CHROME: 'chrome',
        39 FIREFOX: 'firefox',
        40 IE: 'internet explorer',
        41 INTERNET_EXPLORER: 'internet explorer',
        42 IPAD: 'iPad',
        43 IPHONE: 'iPhone',
        44 OPERA: 'opera',
        45 PHANTOM_JS: 'phantomjs',
        46 SAFARI: 'safari',
        47 HTMLUNIT: 'htmlunit'
        48};
        49
        50
        51
        52/**
        53 * Describes how a proxy should be configured for a WebDriver session.
        54 * Proxy configuration object, as defined by the WebDriver wire protocol.
        55 * @typedef {(
        56 * {proxyType: string}|
        57 * {proxyType: string,
        58 * proxyAutoconfigUrl: string}|
        59 * {proxyType: string,
        60 * ftpProxy: string,
        61 * httpProxy: string,
        62 * sslProxy: string,
        63 * noProxy: string})}
        64 */
        65webdriver.ProxyConfig;
        66
        67
        68
        69/**
        70 * Common webdriver capability keys.
        71 * @enum {string}
        72 */
        73webdriver.Capability = {
        74
        75 /**
        76 * Indicates whether a driver should accept all SSL certs by default. This
        77 * capability only applies when requesting a new session. To query whether
        78 * a driver can handle insecure SSL certs, see {@link #SECURE_SSL}.
        79 */
        80 ACCEPT_SSL_CERTS: 'acceptSslCerts',
        81
        82
        83 /**
        84 * The browser name. Common browser names are defined in the
        85 * {@link webdriver.Browser} enum.
        86 */
        87 BROWSER_NAME: 'browserName',
        88
        89 /**
        90 * Defines how elements should be scrolled into the viewport for interaction.
        91 * This capability will be set to zero (0) if elements are aligned with the
        92 * top of the viewport, or one (1) if aligned with the bottom. The default
        93 * behavior is to align with the top of the viewport.
        94 */
        95 ELEMENT_SCROLL_BEHAVIOR: 'elementScrollBehavior',
        96
        97 /**
        98 * Whether the driver is capable of handling modal alerts (e.g. alert,
        99 * confirm, prompt). To define how a driver <i>should</i> handle alerts,
        100 * use {@link #UNEXPECTED_ALERT_BEHAVIOR}.
        101 */
        102 HANDLES_ALERTS: 'handlesAlerts',
        103
        104 /**
        105 * Key for the logging driver logging preferences.
        106 */
        107 LOGGING_PREFS: 'loggingPrefs',
        108
        109 /**
        110 * Whether this session generates native events when simulating user input.
        111 */
        112 NATIVE_EVENTS: 'nativeEvents',
        113
        114 /**
        115 * Describes the platform the browser is running on. Will be one of
        116 * ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When <i>requesting</i> a
        117 * session, ANY may be used to indicate no platform preference (this is
        118 * semantically equivalent to omitting the platform capability).
        119 */
        120 PLATFORM: 'platform',
        121
        122 /**
        123 * Describes the proxy configuration to use for a new WebDriver session.
        124 */
        125 PROXY: 'proxy',
        126
        127 /** Whether the driver supports changing the brower's orientation. */
        128 ROTATABLE: 'rotatable',
        129
        130 /**
        131 * Whether a driver is only capable of handling secure SSL certs. To request
        132 * that a driver accept insecure SSL certs by default, use
        133 * {@link #ACCEPT_SSL_CERTS}.
        134 */
        135 SECURE_SSL: 'secureSsl',
        136
        137 /** Whether the driver supports manipulating the app cache. */
        138 SUPPORTS_APPLICATION_CACHE: 'applicationCacheEnabled',
        139
        140 /** Whether the driver supports locating elements with CSS selectors. */
        141 SUPPORTS_CSS_SELECTORS: 'cssSelectorsEnabled',
        142
        143 /** Whether the browser supports JavaScript. */
        144 SUPPORTS_JAVASCRIPT: 'javascriptEnabled',
        145
        146 /** Whether the driver supports controlling the browser's location info. */
        147 SUPPORTS_LOCATION_CONTEXT: 'locationContextEnabled',
        148
        149 /** Whether the driver supports taking screenshots. */
        150 TAKES_SCREENSHOT: 'takesScreenshot',
        151
        152 /**
        153 * Defines how the driver should handle unexpected alerts. The value should
        154 * be one of "accept", "dismiss", or "ignore.
        155 */
        156 UNEXPECTED_ALERT_BEHAVIOR: 'unexpectedAlertBehavior',
        157
        158 /** Defines the browser version. */
        159 VERSION: 'version'
        160};
        161
        162
        163
        164/**
        165 * @param {(webdriver.Capabilities|Object)=} opt_other Another set of
        166 * capabilities to merge into this instance.
        167 * @constructor
        168 * @extends {webdriver.Serializable.<!Object.<string, ?>>}
        169 */
        170webdriver.Capabilities = function(opt_other) {
        171 webdriver.Serializable.call(this);
        172
        173 /** @private {!Object.<string, ?>} */
        174 this.caps_ = {};
        175
        176 if (opt_other) {
        177 this.merge(opt_other);
        178 }
        179};
        180goog.inherits(webdriver.Capabilities, webdriver.Serializable);
        181
        182
        183/**
        184 * @return {!webdriver.Capabilities} A basic set of capabilities for Android.
        185 */
        186webdriver.Capabilities.android = function() {
        187 return new webdriver.Capabilities().
        188 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.ANDROID).
        189 set(webdriver.Capability.PLATFORM, 'ANDROID');
        190};
        191
        192
        193/**
        194 * @return {!webdriver.Capabilities} A basic set of capabilities for Chrome.
        195 */
        196webdriver.Capabilities.chrome = function() {
        197 return new webdriver.Capabilities().
        198 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.CHROME);
        199};
        200
        201
        202/**
        203 * @return {!webdriver.Capabilities} A basic set of capabilities for Firefox.
        204 */
        205webdriver.Capabilities.firefox = function() {
        206 return new webdriver.Capabilities().
        207 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.FIREFOX);
        208};
        209
        210
        211/**
        212 * @return {!webdriver.Capabilities} A basic set of capabilities for
        213 * Internet Explorer.
        214 */
        215webdriver.Capabilities.ie = function() {
        216 return new webdriver.Capabilities().
        217 set(webdriver.Capability.BROWSER_NAME,
        218 webdriver.Browser.INTERNET_EXPLORER).
        219 set(webdriver.Capability.PLATFORM, 'WINDOWS');
        220};
        221
        222
        223/**
        224 * @return {!webdriver.Capabilities} A basic set of capabilities for iPad.
        225 */
        226webdriver.Capabilities.ipad = function() {
        227 return new webdriver.Capabilities().
        228 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.IPAD).
        229 set(webdriver.Capability.PLATFORM, 'MAC');
        230};
        231
        232
        233/**
        234 * @return {!webdriver.Capabilities} A basic set of capabilities for iPhone.
        235 */
        236webdriver.Capabilities.iphone = function() {
        237 return new webdriver.Capabilities().
        238 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.IPHONE).
        239 set(webdriver.Capability.PLATFORM, 'MAC');
        240};
        241
        242
        243/**
        244 * @return {!webdriver.Capabilities} A basic set of capabilities for Opera.
        245 */
        246webdriver.Capabilities.opera = function() {
        247 return new webdriver.Capabilities().
        248 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.OPERA);
        249};
        250
        251/**
        252 * @return {!webdriver.Capabilities} A basic set of capabilities for
        253 * PhantomJS.
        254 */
        255webdriver.Capabilities.phantomjs = function() {
        256 return new webdriver.Capabilities().
        257 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.PHANTOM_JS);
        258};
        259
        260
        261/**
        262 * @return {!webdriver.Capabilities} A basic set of capabilities for Safari.
        263 */
        264webdriver.Capabilities.safari = function() {
        265 return new webdriver.Capabilities().
        266 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.SAFARI);
        267};
        268
        269
        270/**
        271 * @return {!webdriver.Capabilities} A basic set of capabilities for HTMLUnit.
        272 */
        273webdriver.Capabilities.htmlunit = function() {
        274 return new webdriver.Capabilities().
        275 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.HTMLUNIT);
        276};
        277
        278
        279/**
        280 * @return {!webdriver.Capabilities} A basic set of capabilities for HTMLUnit
        281 * with enabled Javascript.
        282 */
        283webdriver.Capabilities.htmlunitwithjs = function() {
        284 return new webdriver.Capabilities().
        285 set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.HTMLUNIT).
        286 set(webdriver.Capability.SUPPORTS_JAVASCRIPT, true);
        287};
        288
        289
        290/**
        291 * @return {!Object.<string, ?>} The JSON representation of this instance. Note,
        292 * the returned object may contain nested promises that are promised values.
        293 * @override
        294 */
        295webdriver.Capabilities.prototype.serialize = function() {
        296 return this.caps_;
        297};
        298
        299
        300/**
        301 * Merges another set of capabilities into this instance. Any duplicates in
        302 * the provided set will override those already set on this instance.
        303 * @param {!(webdriver.Capabilities|Object)} other The capabilities to
        304 * merge into this instance.
        305 * @return {!webdriver.Capabilities} A self reference.
        306 */
        307webdriver.Capabilities.prototype.merge = function(other) {
        308 var caps = other instanceof webdriver.Capabilities ?
        309 other.caps_ : other;
        310 for (var key in caps) {
        311 if (caps.hasOwnProperty(key)) {
        312 this.set(key, caps[key]);
        313 }
        314 }
        315 return this;
        316};
        317
        318
        319/**
        320 * @param {string} key The capability to set.
        321 * @param {*} value The capability value. Capability values must be JSON
        322 * serializable. Pass {@code null} to unset the capability.
        323 * @return {!webdriver.Capabilities} A self reference.
        324 */
        325webdriver.Capabilities.prototype.set = function(key, value) {
        326 if (goog.isDefAndNotNull(value)) {
        327 this.caps_[key] = value;
        328 } else {
        329 delete this.caps_[key];
        330 }
        331 return this;
        332};
        333
        334
        335/**
        336 * @param {string} key The capability to return.
        337 * @return {*} The capability with the given key, or {@code null} if it has
        338 * not been set.
        339 */
        340webdriver.Capabilities.prototype.get = function(key) {
        341 var val = null;
        342 if (this.caps_.hasOwnProperty(key)) {
        343 val = this.caps_[key];
        344 }
        345 return goog.isDefAndNotNull(val) ? val : null;
        346};
        347
        348
        349/**
        350 * @param {string} key The capability to check.
        351 * @return {boolean} Whether the specified capability is set.
        352 */
        353webdriver.Capabilities.prototype.has = function(key) {
        354 return !!this.get(key);
        355};
        356
        357
        358/**
        359 * Sets the logging preferences. Preferences may be specified as a
        360 * {@link webdriver.logging.Preferences} instance, or a as a map of log-type to
        361 * log-level.
        362 * @param {!(webdriver.logging.Preferences|Object.<string, string>)} prefs The
        363 * logging preferences.
        364 * @return {!webdriver.Capabilities} A self reference.
        365 */
        366webdriver.Capabilities.prototype.setLoggingPrefs = function(prefs) {
        367 return this.set(webdriver.Capability.LOGGING_PREFS, prefs);
        368};
        369
        370
        371/**
        372 * Sets the proxy configuration for this instance.
        373 * @param {webdriver.ProxyConfig} proxy The desired proxy configuration.
        374 * @return {!webdriver.Capabilities} A self reference.
        375 */
        376webdriver.Capabilities.prototype.setProxy = function(proxy) {
        377 return this.set(webdriver.Capability.PROXY, proxy);
        378};
        379
        380
        381/**
        382 * Sets whether native events should be used.
        383 * @param {boolean} enabled Whether to enable native events.
        384 * @return {!webdriver.Capabilities} A self reference.
        385 */
        386webdriver.Capabilities.prototype.setEnableNativeEvents = function(enabled) {
        387 return this.set(webdriver.Capability.NATIVE_EVENTS, enabled);
        388};
        389
        390
        391/**
        392 * Sets how elements should be scrolled into view for interaction.
        393 * @param {number} behavior The desired scroll behavior: either 0 to align with
        394 * the top of the viewport or 1 to align with the bottom.
        395 * @return {!webdriver.Capabilities} A self reference.
        396 */
        397webdriver.Capabilities.prototype.setScrollBehavior = function(behavior) {
        398 return this.set(webdriver.Capability.ELEMENT_SCROLL_BEHAVIOR, behavior);
        399};
        400
        401
        402/**
        403 * Sets the default action to take with an unexpected alert before returning
        404 * an error.
        405 * @param {string} behavior The desired behavior; should be "accept", "dismiss",
        406 * or "ignore". Defaults to "dismiss".
        407 * @return {!webdriver.Capabilities} A self reference.
        408 */
        409webdriver.Capabilities.prototype.setAlertBehavior = function(behavior) {
        410 return this.set(webdriver.Capability.UNEXPECTED_ALERT_BEHAVIOR, behavior);
        411};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/command.js.src.html b/docs/source/lib/webdriver/command.js.src.html index 903630f..6eeaf6e 100644 --- a/docs/source/lib/webdriver/command.js.src.html +++ b/docs/source/lib/webdriver/command.js.src.html @@ -1 +1 @@ -command.js

        lib/webdriver/command.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Contains several classes for handling commands.
        17 */
        18
        19goog.provide('webdriver.Command');
        20goog.provide('webdriver.CommandExecutor');
        21goog.provide('webdriver.CommandName');
        22
        23
        24
        25/**
        26 * Describes a command to be executed by the WebDriverJS framework.
        27 * @param {!webdriver.CommandName} name The name of this command.
        28 * @constructor
        29 */
        30webdriver.Command = function(name) {
        31
        32 /**
        33 * The name of this command.
        34 * @private {!webdriver.CommandName}
        35 */
        36 this.name_ = name;
        37
        38 /**
        39 * The parameters to this command.
        40 * @private {!Object.<*>}
        41 */
        42 this.parameters_ = {};
        43};
        44
        45
        46/**
        47 * @return {!webdriver.CommandName} This command's name.
        48 */
        49webdriver.Command.prototype.getName = function() {
        50 return this.name_;
        51};
        52
        53
        54/**
        55 * Sets a parameter to send with this command.
        56 * @param {string} name The parameter name.
        57 * @param {*} value The parameter value.
        58 * @return {!webdriver.Command} A self reference.
        59 */
        60webdriver.Command.prototype.setParameter = function(name, value) {
        61 this.parameters_[name] = value;
        62 return this;
        63};
        64
        65
        66/**
        67 * Sets the parameters for this command.
        68 * @param {!Object.<*>} parameters The command parameters.
        69 * @return {!webdriver.Command} A self reference.
        70 */
        71webdriver.Command.prototype.setParameters = function(parameters) {
        72 this.parameters_ = parameters;
        73 return this;
        74};
        75
        76
        77/**
        78 * Returns a named command parameter.
        79 * @param {string} key The parameter key to look up.
        80 * @return {*} The parameter value, or undefined if it has not been set.
        81 */
        82webdriver.Command.prototype.getParameter = function(key) {
        83 return this.parameters_[key];
        84};
        85
        86
        87/**
        88 * @return {!Object.<*>} The parameters to send with this command.
        89 */
        90webdriver.Command.prototype.getParameters = function() {
        91 return this.parameters_;
        92};
        93
        94
        95/**
        96 * Enumeration of predefined names command names that all command processors
        97 * will support.
        98 * @enum {string}
        99 */
        100// TODO: Delete obsolete command names.
        101webdriver.CommandName = {
        102 GET_SERVER_STATUS: 'getStatus',
        103
        104 NEW_SESSION: 'newSession',
        105 GET_SESSIONS: 'getSessions',
        106 DESCRIBE_SESSION: 'getSessionCapabilities',
        107
        108 CLOSE: 'close',
        109 QUIT: 'quit',
        110
        111 GET_CURRENT_URL: 'getCurrentUrl',
        112 GET: 'get',
        113 GO_BACK: 'goBack',
        114 GO_FORWARD: 'goForward',
        115 REFRESH: 'refresh',
        116
        117 ADD_COOKIE: 'addCookie',
        118 GET_COOKIE: 'getCookie',
        119 GET_ALL_COOKIES: 'getCookies',
        120 DELETE_COOKIE: 'deleteCookie',
        121 DELETE_ALL_COOKIES: 'deleteAllCookies',
        122
        123 GET_ACTIVE_ELEMENT: 'getActiveElement',
        124 FIND_ELEMENT: 'findElement',
        125 FIND_ELEMENTS: 'findElements',
        126 FIND_CHILD_ELEMENT: 'findChildElement',
        127 FIND_CHILD_ELEMENTS: 'findChildElements',
        128
        129 CLEAR_ELEMENT: 'clearElement',
        130 CLICK_ELEMENT: 'clickElement',
        131 SEND_KEYS_TO_ELEMENT: 'sendKeysToElement',
        132 SUBMIT_ELEMENT: 'submitElement',
        133
        134 GET_CURRENT_WINDOW_HANDLE: 'getCurrentWindowHandle',
        135 GET_WINDOW_HANDLES: 'getWindowHandles',
        136 GET_WINDOW_POSITION: 'getWindowPosition',
        137 SET_WINDOW_POSITION: 'setWindowPosition',
        138 GET_WINDOW_SIZE: 'getWindowSize',
        139 SET_WINDOW_SIZE: 'setWindowSize',
        140 MAXIMIZE_WINDOW: 'maximizeWindow',
        141
        142 SWITCH_TO_WINDOW: 'switchToWindow',
        143 SWITCH_TO_FRAME: 'switchToFrame',
        144 GET_PAGE_SOURCE: 'getPageSource',
        145 GET_TITLE: 'getTitle',
        146
        147 EXECUTE_SCRIPT: 'executeScript',
        148 EXECUTE_ASYNC_SCRIPT: 'executeAsyncScript',
        149
        150 GET_ELEMENT_TEXT: 'getElementText',
        151 GET_ELEMENT_TAG_NAME: 'getElementTagName',
        152 IS_ELEMENT_SELECTED: 'isElementSelected',
        153 IS_ELEMENT_ENABLED: 'isElementEnabled',
        154 IS_ELEMENT_DISPLAYED: 'isElementDisplayed',
        155 GET_ELEMENT_LOCATION: 'getElementLocation',
        156 GET_ELEMENT_LOCATION_IN_VIEW: 'getElementLocationOnceScrolledIntoView',
        157 GET_ELEMENT_SIZE: 'getElementSize',
        158 GET_ELEMENT_ATTRIBUTE: 'getElementAttribute',
        159 GET_ELEMENT_VALUE_OF_CSS_PROPERTY: 'getElementValueOfCssProperty',
        160 ELEMENT_EQUALS: 'elementEquals',
        161
        162 SCREENSHOT: 'screenshot',
        163 IMPLICITLY_WAIT: 'implicitlyWait',
        164 SET_SCRIPT_TIMEOUT: 'setScriptTimeout',
        165 SET_TIMEOUT: 'setTimeout',
        166
        167 ACCEPT_ALERT: 'acceptAlert',
        168 DISMISS_ALERT: 'dismissAlert',
        169 GET_ALERT_TEXT: 'getAlertText',
        170 SET_ALERT_TEXT: 'setAlertValue',
        171
        172 EXECUTE_SQL: 'executeSQL',
        173 GET_LOCATION: 'getLocation',
        174 SET_LOCATION: 'setLocation',
        175 GET_APP_CACHE: 'getAppCache',
        176 GET_APP_CACHE_STATUS: 'getStatus',
        177 CLEAR_APP_CACHE: 'clearAppCache',
        178 IS_BROWSER_ONLINE: 'isBrowserOnline',
        179 SET_BROWSER_ONLINE: 'setBrowserOnline',
        180
        181 GET_LOCAL_STORAGE_ITEM: 'getLocalStorageItem',
        182 GET_LOCAL_STORAGE_KEYS: 'getLocalStorageKeys',
        183 SET_LOCAL_STORAGE_ITEM: 'setLocalStorageItem',
        184 REMOVE_LOCAL_STORAGE_ITEM: 'removeLocalStorageItem',
        185 CLEAR_LOCAL_STORAGE: 'clearLocalStorage',
        186 GET_LOCAL_STORAGE_SIZE: 'getLocalStorageSize',
        187
        188 GET_SESSION_STORAGE_ITEM: 'getSessionStorageItem',
        189 GET_SESSION_STORAGE_KEYS: 'getSessionStorageKey',
        190 SET_SESSION_STORAGE_ITEM: 'setSessionStorageItem',
        191 REMOVE_SESSION_STORAGE_ITEM: 'removeSessionStorageItem',
        192 CLEAR_SESSION_STORAGE: 'clearSessionStorage',
        193 GET_SESSION_STORAGE_SIZE: 'getSessionStorageSize',
        194
        195 SET_SCREEN_ORIENTATION: 'setScreenOrientation',
        196 GET_SCREEN_ORIENTATION: 'getScreenOrientation',
        197
        198 // These belong to the Advanced user interactions - an element is
        199 // optional for these commands.
        200 CLICK: 'mouseClick',
        201 DOUBLE_CLICK: 'mouseDoubleClick',
        202 MOUSE_DOWN: 'mouseDown',
        203 MOUSE_UP: 'mouseUp',
        204 MOVE_TO: 'mouseMove',
        205 SEND_KEYS_TO_ACTIVE_ELEMENT: 'sendKeysToActiveElement',
        206
        207 // These belong to the Advanced Touch API
        208 TOUCH_SINGLE_TAP: 'touchSingleTap',
        209 TOUCH_DOWN: 'touchDown',
        210 TOUCH_UP: 'touchUp',
        211 TOUCH_MOVE: 'touchMove',
        212 TOUCH_SCROLL: 'touchScroll',
        213 TOUCH_DOUBLE_TAP: 'touchDoubleTap',
        214 TOUCH_LONG_PRESS: 'touchLongPress',
        215 TOUCH_FLICK: 'touchFlick',
        216
        217 GET_AVAILABLE_LOG_TYPES: 'getAvailableLogTypes',
        218 GET_LOG: 'getLog',
        219 GET_SESSION_LOGS: 'getSessionLogs'
        220};
        221
        222
        223
        224/**
        225 * Handles the execution of {@code webdriver.Command} objects.
        226 * @interface
        227 */
        228webdriver.CommandExecutor = function() {};
        229
        230
        231/**
        232 * Executes the given {@code command}. If there is an error executing the
        233 * command, the provided callback will be invoked with the offending error.
        234 * Otherwise, the callback will be invoked with a null Error and non-null
        235 * {@link bot.response.ResponseObject} object.
        236 * @param {!webdriver.Command} command The command to execute.
        237 * @param {function(Error, !bot.response.ResponseObject=)} callback the function
        238 * to invoke when the command response is ready.
        239 */
        240webdriver.CommandExecutor.prototype.execute = goog.abstractMethod;
        \ No newline at end of file +command.js

        lib/webdriver/command.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Contains several classes for handling commands.
        20 */
        21
        22goog.provide('webdriver.Command');
        23goog.provide('webdriver.CommandExecutor');
        24goog.provide('webdriver.CommandName');
        25
        26
        27
        28/**
        29 * Describes a command to be executed by the WebDriverJS framework.
        30 * @param {!webdriver.CommandName} name The name of this command.
        31 * @constructor
        32 */
        33webdriver.Command = function(name) {
        34
        35 /**
        36 * The name of this command.
        37 * @private {!webdriver.CommandName}
        38 */
        39 this.name_ = name;
        40
        41 /**
        42 * The parameters to this command.
        43 * @private {!Object.<*>}
        44 */
        45 this.parameters_ = {};
        46};
        47
        48
        49/**
        50 * @return {!webdriver.CommandName} This command's name.
        51 */
        52webdriver.Command.prototype.getName = function() {
        53 return this.name_;
        54};
        55
        56
        57/**
        58 * Sets a parameter to send with this command.
        59 * @param {string} name The parameter name.
        60 * @param {*} value The parameter value.
        61 * @return {!webdriver.Command} A self reference.
        62 */
        63webdriver.Command.prototype.setParameter = function(name, value) {
        64 this.parameters_[name] = value;
        65 return this;
        66};
        67
        68
        69/**
        70 * Sets the parameters for this command.
        71 * @param {!Object.<*>} parameters The command parameters.
        72 * @return {!webdriver.Command} A self reference.
        73 */
        74webdriver.Command.prototype.setParameters = function(parameters) {
        75 this.parameters_ = parameters;
        76 return this;
        77};
        78
        79
        80/**
        81 * Returns a named command parameter.
        82 * @param {string} key The parameter key to look up.
        83 * @return {*} The parameter value, or undefined if it has not been set.
        84 */
        85webdriver.Command.prototype.getParameter = function(key) {
        86 return this.parameters_[key];
        87};
        88
        89
        90/**
        91 * @return {!Object.<*>} The parameters to send with this command.
        92 */
        93webdriver.Command.prototype.getParameters = function() {
        94 return this.parameters_;
        95};
        96
        97
        98/**
        99 * Enumeration of predefined names command names that all command processors
        100 * will support.
        101 * @enum {string}
        102 */
        103// TODO: Delete obsolete command names.
        104webdriver.CommandName = {
        105 GET_SERVER_STATUS: 'getStatus',
        106
        107 NEW_SESSION: 'newSession',
        108 GET_SESSIONS: 'getSessions',
        109 DESCRIBE_SESSION: 'getSessionCapabilities',
        110
        111 CLOSE: 'close',
        112 QUIT: 'quit',
        113
        114 GET_CURRENT_URL: 'getCurrentUrl',
        115 GET: 'get',
        116 GO_BACK: 'goBack',
        117 GO_FORWARD: 'goForward',
        118 REFRESH: 'refresh',
        119
        120 ADD_COOKIE: 'addCookie',
        121 GET_COOKIE: 'getCookie',
        122 GET_ALL_COOKIES: 'getCookies',
        123 DELETE_COOKIE: 'deleteCookie',
        124 DELETE_ALL_COOKIES: 'deleteAllCookies',
        125
        126 GET_ACTIVE_ELEMENT: 'getActiveElement',
        127 FIND_ELEMENT: 'findElement',
        128 FIND_ELEMENTS: 'findElements',
        129 FIND_CHILD_ELEMENT: 'findChildElement',
        130 FIND_CHILD_ELEMENTS: 'findChildElements',
        131
        132 CLEAR_ELEMENT: 'clearElement',
        133 CLICK_ELEMENT: 'clickElement',
        134 SEND_KEYS_TO_ELEMENT: 'sendKeysToElement',
        135 SUBMIT_ELEMENT: 'submitElement',
        136
        137 GET_CURRENT_WINDOW_HANDLE: 'getCurrentWindowHandle',
        138 GET_WINDOW_HANDLES: 'getWindowHandles',
        139 GET_WINDOW_POSITION: 'getWindowPosition',
        140 SET_WINDOW_POSITION: 'setWindowPosition',
        141 GET_WINDOW_SIZE: 'getWindowSize',
        142 SET_WINDOW_SIZE: 'setWindowSize',
        143 MAXIMIZE_WINDOW: 'maximizeWindow',
        144
        145 SWITCH_TO_WINDOW: 'switchToWindow',
        146 SWITCH_TO_FRAME: 'switchToFrame',
        147 GET_PAGE_SOURCE: 'getPageSource',
        148 GET_TITLE: 'getTitle',
        149
        150 EXECUTE_SCRIPT: 'executeScript',
        151 EXECUTE_ASYNC_SCRIPT: 'executeAsyncScript',
        152
        153 GET_ELEMENT_TEXT: 'getElementText',
        154 GET_ELEMENT_TAG_NAME: 'getElementTagName',
        155 IS_ELEMENT_SELECTED: 'isElementSelected',
        156 IS_ELEMENT_ENABLED: 'isElementEnabled',
        157 IS_ELEMENT_DISPLAYED: 'isElementDisplayed',
        158 GET_ELEMENT_LOCATION: 'getElementLocation',
        159 GET_ELEMENT_LOCATION_IN_VIEW: 'getElementLocationOnceScrolledIntoView',
        160 GET_ELEMENT_SIZE: 'getElementSize',
        161 GET_ELEMENT_ATTRIBUTE: 'getElementAttribute',
        162 GET_ELEMENT_VALUE_OF_CSS_PROPERTY: 'getElementValueOfCssProperty',
        163 ELEMENT_EQUALS: 'elementEquals',
        164
        165 SCREENSHOT: 'screenshot',
        166 IMPLICITLY_WAIT: 'implicitlyWait',
        167 SET_SCRIPT_TIMEOUT: 'setScriptTimeout',
        168 SET_TIMEOUT: 'setTimeout',
        169
        170 ACCEPT_ALERT: 'acceptAlert',
        171 DISMISS_ALERT: 'dismissAlert',
        172 GET_ALERT_TEXT: 'getAlertText',
        173 SET_ALERT_TEXT: 'setAlertValue',
        174
        175 EXECUTE_SQL: 'executeSQL',
        176 GET_LOCATION: 'getLocation',
        177 SET_LOCATION: 'setLocation',
        178 GET_APP_CACHE: 'getAppCache',
        179 GET_APP_CACHE_STATUS: 'getStatus',
        180 CLEAR_APP_CACHE: 'clearAppCache',
        181 IS_BROWSER_ONLINE: 'isBrowserOnline',
        182 SET_BROWSER_ONLINE: 'setBrowserOnline',
        183
        184 GET_LOCAL_STORAGE_ITEM: 'getLocalStorageItem',
        185 GET_LOCAL_STORAGE_KEYS: 'getLocalStorageKeys',
        186 SET_LOCAL_STORAGE_ITEM: 'setLocalStorageItem',
        187 REMOVE_LOCAL_STORAGE_ITEM: 'removeLocalStorageItem',
        188 CLEAR_LOCAL_STORAGE: 'clearLocalStorage',
        189 GET_LOCAL_STORAGE_SIZE: 'getLocalStorageSize',
        190
        191 GET_SESSION_STORAGE_ITEM: 'getSessionStorageItem',
        192 GET_SESSION_STORAGE_KEYS: 'getSessionStorageKey',
        193 SET_SESSION_STORAGE_ITEM: 'setSessionStorageItem',
        194 REMOVE_SESSION_STORAGE_ITEM: 'removeSessionStorageItem',
        195 CLEAR_SESSION_STORAGE: 'clearSessionStorage',
        196 GET_SESSION_STORAGE_SIZE: 'getSessionStorageSize',
        197
        198 SET_SCREEN_ORIENTATION: 'setScreenOrientation',
        199 GET_SCREEN_ORIENTATION: 'getScreenOrientation',
        200
        201 // These belong to the Advanced user interactions - an element is
        202 // optional for these commands.
        203 CLICK: 'mouseClick',
        204 DOUBLE_CLICK: 'mouseDoubleClick',
        205 MOUSE_DOWN: 'mouseButtonDown',
        206 MOUSE_UP: 'mouseButtonUp',
        207 MOVE_TO: 'mouseMoveTo',
        208 SEND_KEYS_TO_ACTIVE_ELEMENT: 'sendKeysToActiveElement',
        209
        210 // These belong to the Advanced Touch API
        211 TOUCH_SINGLE_TAP: 'touchSingleTap',
        212 TOUCH_DOWN: 'touchDown',
        213 TOUCH_UP: 'touchUp',
        214 TOUCH_MOVE: 'touchMove',
        215 TOUCH_SCROLL: 'touchScroll',
        216 TOUCH_DOUBLE_TAP: 'touchDoubleTap',
        217 TOUCH_LONG_PRESS: 'touchLongPress',
        218 TOUCH_FLICK: 'touchFlick',
        219
        220 GET_AVAILABLE_LOG_TYPES: 'getAvailableLogTypes',
        221 GET_LOG: 'getLog',
        222 GET_SESSION_LOGS: 'getSessionLogs',
        223
        224 // Non-standard commands used by the standalone Selenium server.
        225 UPLOAD_FILE: 'uploadFile'
        226};
        227
        228
        229
        230/**
        231 * Handles the execution of WebDriver {@link webdriver.Command commands}.
        232 * @interface
        233 */
        234webdriver.CommandExecutor = function() {};
        235
        236
        237/**
        238 * Executes the given {@code command}. If there is an error executing the
        239 * command, the provided callback will be invoked with the offending error.
        240 * Otherwise, the callback will be invoked with a null Error and non-null
        241 * {@link bot.response.ResponseObject} object.
        242 * @param {!webdriver.Command} command The command to execute.
        243 * @param {function(Error, !bot.response.ResponseObject=)} callback the function
        244 * to invoke when the command response is ready.
        245 */
        246webdriver.CommandExecutor.prototype.execute = goog.abstractMethod;
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/events.js.src.html b/docs/source/lib/webdriver/events.js.src.html index c60d9c5..8d50f5d 100644 --- a/docs/source/lib/webdriver/events.js.src.html +++ b/docs/source/lib/webdriver/events.js.src.html @@ -1 +1 @@ -events.js

        lib/webdriver/events.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview A light weight event system modeled after Node's EventEmitter.
        17 */
        18
        19goog.provide('webdriver.EventEmitter');
        20
        21
        22
        23/**
        24 * Object that can emit events for others to listen for. This is used instead
        25 * of Closure's event system because it is much more light weight. The API is
        26 * based on Node's EventEmitters.
        27 * @constructor
        28 */
        29webdriver.EventEmitter = function() {
        30 /**
        31 * Map of events to registered listeners.
        32 * @private {!Object.<!Array.<{fn: !Function, oneshot: boolean,
        33 * scope: (Object|undefined)}>>}
        34 */
        35 this.events_ = {};
        36};
        37
        38
        39/**
        40 * Fires an event and calls all listeners.
        41 * @param {string} type The type of event to emit.
        42 * @param {...*} var_args Any arguments to pass to each listener.
        43 */
        44webdriver.EventEmitter.prototype.emit = function(type, var_args) {
        45 var args = Array.prototype.slice.call(arguments, 1);
        46 var listeners = this.events_[type];
        47 if (!listeners) {
        48 return;
        49 }
        50 for (var i = 0; i < listeners.length;) {
        51 var listener = listeners[i];
        52 listener.fn.apply(listener.scope, args);
        53 if (listeners[i] === listener) {
        54 if (listeners[i].oneshot) {
        55 listeners.splice(i, 1);
        56 } else {
        57 i += 1;
        58 }
        59 }
        60 }
        61};
        62
        63
        64/**
        65 * Returns a mutable list of listeners for a specific type of event.
        66 * @param {string} type The type of event to retrieve the listeners for.
        67 * @return {!Array.<{fn: !Function, oneshot: boolean,
        68 * scope: (Object|undefined)}>} The registered listeners for
        69 * the given event type.
        70 */
        71webdriver.EventEmitter.prototype.listeners = function(type) {
        72 var listeners = this.events_[type];
        73 if (!listeners) {
        74 listeners = this.events_[type] = [];
        75 }
        76 return listeners;
        77};
        78
        79
        80/**
        81 * Registers a listener.
        82 * @param {string} type The type of event to listen for.
        83 * @param {!Function} listenerFn The function to invoke when the event is fired.
        84 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        85 * @param {boolean=} opt_oneshot Whether the listener should be removed after
        86 * the first event is fired.
        87 * @return {!webdriver.EventEmitter} A self reference.
        88 * @private
        89 */
        90webdriver.EventEmitter.prototype.addListener_ = function(type, listenerFn,
        91 opt_scope, opt_oneshot) {
        92 var listeners = this.listeners(type);
        93 var n = listeners.length;
        94 for (var i = 0; i < n; ++i) {
        95 if (listeners[i].fn == listenerFn) {
        96 return this;
        97 }
        98 }
        99
        100 listeners.push({
        101 fn: listenerFn,
        102 scope: opt_scope,
        103 oneshot: !!opt_oneshot
        104 });
        105 return this;
        106};
        107
        108
        109/**
        110 * Registers a listener.
        111 * @param {string} type The type of event to listen for.
        112 * @param {!Function} listenerFn The function to invoke when the event is fired.
        113 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        114 * @return {!webdriver.EventEmitter} A self reference.
        115 */
        116webdriver.EventEmitter.prototype.addListener = function(type, listenerFn,
        117 opt_scope) {
        118 return this.addListener_(type, listenerFn, opt_scope);
        119};
        120
        121
        122/**
        123 * Registers a one-time listener which will be called only the first time an
        124 * event is emitted, after which it will be removed.
        125 * @param {string} type The type of event to listen for.
        126 * @param {!Function} listenerFn The function to invoke when the event is fired.
        127 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        128 * @return {!webdriver.EventEmitter} A self reference.
        129 */
        130webdriver.EventEmitter.prototype.once = function(type, listenerFn, opt_scope) {
        131 return this.addListener_(type, listenerFn, opt_scope, true);
        132};
        133
        134
        135/**
        136 * An alias for {@code #addListener()}.
        137 * @param {string} type The type of event to listen for.
        138 * @param {!Function} listenerFn The function to invoke when the event is fired.
        139 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        140 * @return {!webdriver.EventEmitter} A self reference.
        141 */
        142webdriver.EventEmitter.prototype.on =
        143 webdriver.EventEmitter.prototype.addListener;
        144
        145
        146/**
        147 * Removes a previously registered event listener.
        148 * @param {string} type The type of event to unregister.
        149 * @param {!Function} listenerFn The handler function to remove.
        150 * @return {!webdriver.EventEmitter} A self reference.
        151 */
        152webdriver.EventEmitter.prototype.removeListener = function(type, listenerFn) {
        153 var listeners = this.events_[type];
        154 if (listeners) {
        155 var n = listeners.length;
        156 for (var i = 0; i < n; ++i) {
        157 if (listeners[i].fn == listenerFn) {
        158 listeners.splice(i, 1);
        159 return this;
        160 }
        161 }
        162 }
        163 return this;
        164};
        165
        166
        167/**
        168 * Removes all listeners for a specific type of event. If no event is
        169 * specified, all listeners across all types will be removed.
        170 * @param {string=} opt_type The type of event to remove listeners from.
        171 * @return {!webdriver.EventEmitter} A self reference.
        172 */
        173webdriver.EventEmitter.prototype.removeAllListeners = function(opt_type) {
        174 goog.isDef(opt_type) ? delete this.events_[opt_type] : this.events_ = {};
        175 return this;
        176};
        \ No newline at end of file +events.js

        lib/webdriver/events.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview A light weight event system modeled after Node's EventEmitter.
        20 */
        21
        22goog.provide('webdriver.EventEmitter');
        23
        24
        25
        26/**
        27 * Object that can emit events for others to listen for. This is used instead
        28 * of Closure's event system because it is much more light weight. The API is
        29 * based on Node's EventEmitters.
        30 * @constructor
        31 */
        32webdriver.EventEmitter = function() {
        33 /**
        34 * Map of events to registered listeners.
        35 * @private {!Object.<!Array.<{fn: !Function, oneshot: boolean,
        36 * scope: (Object|undefined)}>>}
        37 */
        38 this.events_ = {};
        39};
        40
        41
        42/**
        43 * Fires an event and calls all listeners.
        44 * @param {string} type The type of event to emit.
        45 * @param {...*} var_args Any arguments to pass to each listener.
        46 */
        47webdriver.EventEmitter.prototype.emit = function(type, var_args) {
        48 var args = Array.prototype.slice.call(arguments, 1);
        49 var listeners = this.events_[type];
        50 if (!listeners) {
        51 return;
        52 }
        53 for (var i = 0; i < listeners.length;) {
        54 var listener = listeners[i];
        55 listener.fn.apply(listener.scope, args);
        56 if (listeners[i] === listener) {
        57 if (listeners[i].oneshot) {
        58 listeners.splice(i, 1);
        59 } else {
        60 i += 1;
        61 }
        62 }
        63 }
        64};
        65
        66
        67/**
        68 * Returns a mutable list of listeners for a specific type of event.
        69 * @param {string} type The type of event to retrieve the listeners for.
        70 * @return {!Array.<{fn: !Function, oneshot: boolean,
        71 * scope: (Object|undefined)}>} The registered listeners for
        72 * the given event type.
        73 */
        74webdriver.EventEmitter.prototype.listeners = function(type) {
        75 var listeners = this.events_[type];
        76 if (!listeners) {
        77 listeners = this.events_[type] = [];
        78 }
        79 return listeners;
        80};
        81
        82
        83/**
        84 * Registers a listener.
        85 * @param {string} type The type of event to listen for.
        86 * @param {!Function} listenerFn The function to invoke when the event is fired.
        87 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        88 * @param {boolean=} opt_oneshot Whether the listener should be removed after
        89 * the first event is fired.
        90 * @return {!webdriver.EventEmitter} A self reference.
        91 * @private
        92 */
        93webdriver.EventEmitter.prototype.addListener_ = function(type, listenerFn,
        94 opt_scope, opt_oneshot) {
        95 var listeners = this.listeners(type);
        96 var n = listeners.length;
        97 for (var i = 0; i < n; ++i) {
        98 if (listeners[i].fn == listenerFn) {
        99 return this;
        100 }
        101 }
        102
        103 listeners.push({
        104 fn: listenerFn,
        105 scope: opt_scope,
        106 oneshot: !!opt_oneshot
        107 });
        108 return this;
        109};
        110
        111
        112/**
        113 * Registers a listener.
        114 * @param {string} type The type of event to listen for.
        115 * @param {!Function} listenerFn The function to invoke when the event is fired.
        116 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        117 * @return {!webdriver.EventEmitter} A self reference.
        118 */
        119webdriver.EventEmitter.prototype.addListener = function(type, listenerFn,
        120 opt_scope) {
        121 return this.addListener_(type, listenerFn, opt_scope);
        122};
        123
        124
        125/**
        126 * Registers a one-time listener which will be called only the first time an
        127 * event is emitted, after which it will be removed.
        128 * @param {string} type The type of event to listen for.
        129 * @param {!Function} listenerFn The function to invoke when the event is fired.
        130 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        131 * @return {!webdriver.EventEmitter} A self reference.
        132 */
        133webdriver.EventEmitter.prototype.once = function(type, listenerFn, opt_scope) {
        134 return this.addListener_(type, listenerFn, opt_scope, true);
        135};
        136
        137
        138/**
        139 * An alias for {@code #addListener()}.
        140 * @param {string} type The type of event to listen for.
        141 * @param {!Function} listenerFn The function to invoke when the event is fired.
        142 * @param {Object=} opt_scope The object in whose scope to invoke the listener.
        143 * @return {!webdriver.EventEmitter} A self reference.
        144 */
        145webdriver.EventEmitter.prototype.on =
        146 webdriver.EventEmitter.prototype.addListener;
        147
        148
        149/**
        150 * Removes a previously registered event listener.
        151 * @param {string} type The type of event to unregister.
        152 * @param {!Function} listenerFn The handler function to remove.
        153 * @return {!webdriver.EventEmitter} A self reference.
        154 */
        155webdriver.EventEmitter.prototype.removeListener = function(type, listenerFn) {
        156 var listeners = this.events_[type];
        157 if (listeners) {
        158 var n = listeners.length;
        159 for (var i = 0; i < n; ++i) {
        160 if (listeners[i].fn == listenerFn) {
        161 listeners.splice(i, 1);
        162 return this;
        163 }
        164 }
        165 }
        166 return this;
        167};
        168
        169
        170/**
        171 * Removes all listeners for a specific type of event. If no event is
        172 * specified, all listeners across all types will be removed.
        173 * @param {string=} opt_type The type of event to remove listeners from.
        174 * @return {!webdriver.EventEmitter} A self reference.
        175 */
        176webdriver.EventEmitter.prototype.removeAllListeners = function(opt_type) {
        177 goog.isDef(opt_type) ? delete this.events_[opt_type] : this.events_ = {};
        178 return this;
        179};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/firefoxdomexecutor.js.src.html b/docs/source/lib/webdriver/firefoxdomexecutor.js.src.html index a463d7e..3fb0708 100644 --- a/docs/source/lib/webdriver/firefoxdomexecutor.js.src.html +++ b/docs/source/lib/webdriver/firefoxdomexecutor.js.src.html @@ -1 +1 @@ -firefoxdomexecutor.js

        lib/webdriver/firefoxdomexecutor.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.FirefoxDomExecutor');
        16
        17goog.require('bot.response');
        18goog.require('goog.json');
        19goog.require('goog.userAgent.product');
        20goog.require('webdriver.Command');
        21goog.require('webdriver.CommandName');
        22
        23
        24
        25/**
        26 * @constructor
        27 * @implements {webdriver.CommandExecutor}
        28 */
        29webdriver.FirefoxDomExecutor = function() {
        30 if (!webdriver.FirefoxDomExecutor.isAvailable()) {
        31 throw Error(
        32 'The current environment does not support the FirefoxDomExecutor');
        33 }
        34
        35 /** @private {!Document} */
        36 this.doc_ = document;
        37
        38 /** @private {!Element} */
        39 this.docElement_ = document.documentElement;
        40
        41 this.docElement_.addEventListener(
        42 webdriver.FirefoxDomExecutor.EventType_.RESPONSE,
        43 goog.bind(this.onResponse_, this), false);
        44};
        45
        46
        47/**
        48 * @return {boolean} Whether the current environment supports the
        49 * FirefoxDomExecutor.
        50 */
        51webdriver.FirefoxDomExecutor.isAvailable = function() {
        52 return goog.userAgent.product.FIREFOX &&
        53 typeof document !== 'undefined' &&
        54 document.documentElement &&
        55 goog.isFunction(document.documentElement.hasAttribute) &&
        56 document.documentElement.hasAttribute('webdriver');
        57};
        58
        59
        60/**
        61 * Attributes used to communicate with the FirefoxDriver extension.
        62 * @enum {string}
        63 * @private
        64 */
        65webdriver.FirefoxDomExecutor.Attribute_ = {
        66 COMMAND: 'command',
        67 RESPONSE: 'response'
        68};
        69
        70
        71/**
        72 * Events used to communicate with the FirefoxDriver extension.
        73 * @enum {string}
        74 * @private
        75 */
        76webdriver.FirefoxDomExecutor.EventType_ = {
        77 COMMAND: 'webdriverCommand',
        78 RESPONSE: 'webdriverResponse'
        79};
        80
        81
        82/**
        83 * The pending command, if any.
        84 * @private {?{name:string, callback:!Function}}
        85 */
        86webdriver.FirefoxDomExecutor.prototype.pendingCommand_ = null;
        87
        88
        89/** @override */
        90webdriver.FirefoxDomExecutor.prototype.execute = function(command, callback) {
        91 if (this.pendingCommand_) {
        92 throw Error('Currently awaiting a command response!');
        93 }
        94
        95 this.pendingCommand_ = {
        96 name: command.getName(),
        97 callback: callback
        98 };
        99
        100 var parameters = command.getParameters();
        101
        102 // There are two means for communicating with the FirefoxDriver: via
        103 // HTTP using WebDriver's wire protocol and over the DOM using a custom
        104 // JSON protocol. This class uses the latter. When the FirefoxDriver receives
        105 // commands over HTTP, it builds a parameters object from the URL parameters.
        106 // When an element ID is sent in the URL, it'll be decoded as just id:string
        107 // instead of id:{ELEMENT:string}. When switching to a frame by element,
        108 // however, the element ID is not sent through the URL, so we must make sure
        109 // to encode that parameter properly here. It would be nice if we unified
        110 // the two protocols used by the FirefoxDriver...
        111 if (parameters['id'] &&
        112 parameters['id']['ELEMENT'] &&
        113 command.getName() != webdriver.CommandName.SWITCH_TO_FRAME) {
        114 parameters['id'] = parameters['id']['ELEMENT'];
        115 }
        116
        117 var json = goog.json.serialize({
        118 'name': command.getName(),
        119 'sessionId': {
        120 'value': parameters['sessionId']
        121 },
        122 'parameters': parameters
        123 });
        124 this.docElement_.setAttribute(
        125 webdriver.FirefoxDomExecutor.Attribute_.COMMAND, json);
        126
        127 var event = this.doc_.createEvent('Event');
        128 event.initEvent(webdriver.FirefoxDomExecutor.EventType_.COMMAND,
        129 /*canBubble=*/true, /*cancelable=*/true);
        130
        131 this.docElement_.dispatchEvent(event);
        132};
        133
        134
        135/** @private */
        136webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() {
        137 if (!this.pendingCommand_) {
        138 return; // Not expecting a response.
        139 }
        140
        141 var command = this.pendingCommand_;
        142 this.pendingCommand_ = null;
        143
        144 var json = this.docElement_.getAttribute(
        145 webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);
        146 if (!json) {
        147 command.callback(Error('Empty command response!'));
        148 return;
        149 }
        150
        151 this.docElement_.removeAttribute(
        152 webdriver.FirefoxDomExecutor.Attribute_.COMMAND);
        153 this.docElement_.removeAttribute(
        154 webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);
        155
        156 try {
        157 var response = bot.response.checkResponse(
        158 /** @type {!bot.response.ResponseObject} */ (goog.json.parse(json)));
        159 } catch (ex) {
        160 command.callback(ex);
        161 return;
        162 }
        163
        164 // Prior to Selenium 2.35.0, two commands are required to fully create a
        165 // session: one to allocate the session, and another to fetch the
        166 // capabilities.
        167 if (command.name == webdriver.CommandName.NEW_SESSION &&
        168 goog.isString(response['value'])) {
        169 var cmd = new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
        170 setParameter('sessionId', response['value']);
        171 this.execute(cmd, command.callback);
        172 } else {
        173 command.callback(null, response);
        174 }
        175};
        \ No newline at end of file +firefoxdomexecutor.js

        lib/webdriver/firefoxdomexecutor.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.FirefoxDomExecutor');
        16
        17goog.require('bot.response');
        18goog.require('goog.json');
        19goog.require('goog.userAgent.product');
        20goog.require('webdriver.Command');
        21goog.require('webdriver.CommandName');
        22
        23
        24
        25/**
        26 * @constructor
        27 * @implements {webdriver.CommandExecutor}
        28 */
        29webdriver.FirefoxDomExecutor = function() {
        30 if (!webdriver.FirefoxDomExecutor.isAvailable()) {
        31 throw Error(
        32 'The current environment does not support the FirefoxDomExecutor');
        33 }
        34
        35 /** @private {!Document} */
        36 this.doc_ = document;
        37
        38 /** @private {!Element} */
        39 this.docElement_ = document.documentElement;
        40
        41 this.docElement_.addEventListener(
        42 webdriver.FirefoxDomExecutor.EventType_.RESPONSE,
        43 goog.bind(this.onResponse_, this), false);
        44};
        45
        46
        47/**
        48 * @return {boolean} Whether the current environment supports the
        49 * FirefoxDomExecutor.
        50 */
        51webdriver.FirefoxDomExecutor.isAvailable = function() {
        52 return goog.userAgent.product.FIREFOX &&
        53 typeof document !== 'undefined' &&
        54 document.documentElement &&
        55 goog.isFunction(document.documentElement.hasAttribute) &&
        56 document.documentElement.hasAttribute('webdriver');
        57};
        58
        59
        60/**
        61 * Attributes used to communicate with the FirefoxDriver extension.
        62 * @enum {string}
        63 * @private
        64 */
        65webdriver.FirefoxDomExecutor.Attribute_ = {
        66 COMMAND: 'command',
        67 RESPONSE: 'response'
        68};
        69
        70
        71/**
        72 * Events used to communicate with the FirefoxDriver extension.
        73 * @enum {string}
        74 * @private
        75 */
        76webdriver.FirefoxDomExecutor.EventType_ = {
        77 COMMAND: 'webdriverCommand',
        78 RESPONSE: 'webdriverResponse'
        79};
        80
        81
        82/**
        83 * The pending command, if any.
        84 * @private {?{name:string, callback:!Function}}
        85 */
        86webdriver.FirefoxDomExecutor.prototype.pendingCommand_ = null;
        87
        88
        89/** @override */
        90webdriver.FirefoxDomExecutor.prototype.execute = function(command, callback) {
        91 if (this.pendingCommand_) {
        92 throw Error('Currently awaiting a command response!');
        93 }
        94
        95 this.pendingCommand_ = {
        96 name: command.getName(),
        97 callback: callback
        98 };
        99
        100 var parameters = command.getParameters();
        101
        102 // There are two means for communicating with the FirefoxDriver: via
        103 // HTTP using WebDriver's wire protocol and over the DOM using a custom
        104 // JSON protocol. This class uses the latter. When the FirefoxDriver receives
        105 // commands over HTTP, it builds a parameters object from the URL parameters.
        106 // When an element ID is sent in the URL, it'll be decoded as just id:string
        107 // instead of id:{ELEMENT:string}. When switching to a frame by element,
        108 // however, the element ID is not sent through the URL, so we must make sure
        109 // to encode that parameter properly here. It would be nice if we unified
        110 // the two protocols used by the FirefoxDriver...
        111 if (parameters['id'] &&
        112 parameters['id']['ELEMENT'] &&
        113 command.getName() != webdriver.CommandName.SWITCH_TO_FRAME) {
        114 parameters['id'] = parameters['id']['ELEMENT'];
        115 }
        116 var json = goog.json.serialize({
        117 'name': command.getName(),
        118 'sessionId': parameters['sessionId'],
        119 'parameters': parameters
        120 });
        121 this.docElement_.setAttribute(
        122 webdriver.FirefoxDomExecutor.Attribute_.COMMAND, json);
        123
        124 var event = this.doc_.createEvent('Event');
        125 event.initEvent(webdriver.FirefoxDomExecutor.EventType_.COMMAND,
        126 /*canBubble=*/true, /*cancelable=*/true);
        127
        128 this.docElement_.dispatchEvent(event);
        129};
        130
        131
        132/** @private */
        133webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() {
        134 if (!this.pendingCommand_) {
        135 return; // Not expecting a response.
        136 }
        137
        138 var command = this.pendingCommand_;
        139 this.pendingCommand_ = null;
        140
        141 var json = this.docElement_.getAttribute(
        142 webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);
        143 if (!json) {
        144 command.callback(Error('Empty command response!'));
        145 return;
        146 }
        147
        148 this.docElement_.removeAttribute(
        149 webdriver.FirefoxDomExecutor.Attribute_.COMMAND);
        150 this.docElement_.removeAttribute(
        151 webdriver.FirefoxDomExecutor.Attribute_.RESPONSE);
        152
        153 try {
        154 var response = bot.response.checkResponse(
        155 /** @type {!bot.response.ResponseObject} */ (goog.json.parse(json)));
        156 } catch (ex) {
        157 command.callback(ex);
        158 return;
        159 }
        160
        161 // Prior to Selenium 2.35.0, two commands are required to fully create a
        162 // session: one to allocate the session, and another to fetch the
        163 // capabilities.
        164 if (command.name == webdriver.CommandName.NEW_SESSION &&
        165 goog.isString(response['value'])) {
        166 var cmd = new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
        167 setParameter('sessionId', response['value']);
        168 this.execute(cmd, command.callback);
        169 } else {
        170 command.callback(null, response);
        171 }
        172};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/http/corsclient.js.src.html b/docs/source/lib/webdriver/http/corsclient.js.src.html index f33da44..cb2a928 100644 --- a/docs/source/lib/webdriver/http/corsclient.js.src.html +++ b/docs/source/lib/webdriver/http/corsclient.js.src.html @@ -1 +1 @@ -corsclient.js

        lib/webdriver/http/corsclient.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.http.CorsClient');
        16
        17goog.require('goog.json');
        18goog.require('webdriver.http.Response');
        19
        20
        21
        22/**
        23 * Communicates with a WebDriver server, which may be on a different domain,
        24 * using the <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing
        25 * </a> (CORS) extension to WebDriver's JSON wire protocol.
        26 *
        27 * <p>Each command from the standard JSON protocol will be encoded in a
        28 * JSON object with the following form:
        29 * {method:string, path:string, data:!Object}
        30 *
        31 * <p>The encoded command is then sent as a POST request to the server's /xdrpc
        32 * endpoint. The server will decode the command, re-route it to the appropriate
        33 * handler, and then return the command's response as a standard JSON response
        34 * object. The JSON responses will <em>always</em> be returned with a 200
        35 * response from the server; clients must rely on the response's "status" field
        36 * to determine whether the command succeeded.
        37 *
        38 * <p>This client cannot be used with the standard wire protocol due to
        39 * limitations in the various browser implementations of the CORS specification:
        40 * <ul>
        41 * <li>IE's <a href="http://goo.gl/6l3kA">XDomainRequest</a> object is only
        42 * capable of generating the types of requests that may be generated through
        43 * a standard <a href="http://goo.gl/vgzAU">HTML form</a> - it can not send
        44 * DELETE requests, as is required in the wire protocol.
        45 * <li>WebKit's implementation of CORS does not follow the spec and forbids
        46 * redirects: https://bugs.webkit.org/show_bug.cgi?id=57600
        47 * This limitation appears to be intentional and is documented in WebKit's
        48 * Layout tests:
        49 * //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html
        50 * <li>If the server does not return a 2xx response, IE and Opera's
        51 * implementations will fire the XDomainRequest/XMLHttpRequest object's
        52 * onerror handler, but without the corresponding response text returned by
        53 * the server. This renders IE and Opera incapable of handling command
        54 * failures in the standard JSON protocol.
        55 * </ul>
        56 *
        57 * @param {string} url URL for the WebDriver server to send commands to.
        58 * @constructor
        59 * @implements {webdriver.http.Client}
        60 * @see <a href="http://www.w3.org/TR/cors/">CORS Spec</a>
        61 * @see <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">
        62 * JSON wire protocol</a>
        63 */
        64webdriver.http.CorsClient = function(url) {
        65 if (!webdriver.http.CorsClient.isAvailable()) {
        66 throw Error('The current environment does not support cross-origin ' +
        67 'resource sharing');
        68 }
        69
        70 /** @private {string} */
        71 this.url_ = url + webdriver.http.CorsClient.XDRPC_ENDPOINT;
        72};
        73
        74
        75/**
        76 * Resource URL to send commands to on the server.
        77 * @type {string}
        78 * @const
        79 */
        80webdriver.http.CorsClient.XDRPC_ENDPOINT = '/xdrpc';
        81
        82
        83/**
        84 * Tests whether the current environment supports cross-origin resource sharing.
        85 * @return {boolean} Whether cross-origin resource sharing is supported.
        86 * @see http://www.w3.org/TR/cors/
        87 */
        88webdriver.http.CorsClient.isAvailable = function() {
        89 return typeof XDomainRequest !== 'undefined' ||
        90 (typeof XMLHttpRequest !== 'undefined' &&
        91 goog.isBoolean(new XMLHttpRequest().withCredentials));
        92};
        93
        94
        95/** @override */
        96webdriver.http.CorsClient.prototype.send = function(request, callback) {
        97 try {
        98 var xhr = new (typeof XDomainRequest !== 'undefined' ?
        99 XDomainRequest : XMLHttpRequest);
        100 xhr.open('POST', this.url_, true);
        101
        102 xhr.onload = function() {
        103 callback(null, webdriver.http.Response.fromXmlHttpRequest(
        104 /** @type {!XMLHttpRequest} */ (xhr)));
        105 };
        106
        107 var url = this.url_;
        108 xhr.onerror = function() {
        109 callback(Error([
        110 'Unable to send request: POST ', url,
        111 '\nPerhaps the server did not respond to the preflight request ',
        112 'with valid access control headers?'
        113 ].join('')));
        114 };
        115
        116 // Define event handlers for all events on the XDomainRequest. Apparently,
        117 // if we don't do this, IE9+10 will silently abort our request. Yay IE.
        118 // Note, we're not using goog.nullFunction, because it tends to get
        119 // optimized away by the compiler, which leaves us where we were before.
        120 xhr.onprogress = xhr.ontimeout = function() {};
        121
        122 xhr.send(goog.json.serialize({
        123 'method': request.method,
        124 'path': request.path,
        125 'data': request.data
        126 }));
        127 } catch (ex) {
        128 callback(ex);
        129 }
        130};
        \ No newline at end of file +corsclient.js

        lib/webdriver/http/corsclient.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.http.CorsClient');
        16
        17goog.require('goog.json');
        18goog.require('webdriver.http.Response');
        19
        20
        21
        22/**
        23 * Communicates with a WebDriver server, which may be on a different domain,
        24 * using the <a href="http://www.w3.org/TR/cors/">cross-origin resource sharing
        25 * </a> (CORS) extension to WebDriver's JSON wire protocol.
        26 *
        27 * <p>Each command from the standard JSON protocol will be encoded in a
        28 * JSON object with the following form:
        29 * {method:string, path:string, data:!Object}
        30 *
        31 * <p>The encoded command is then sent as a POST request to the server's /xdrpc
        32 * endpoint. The server will decode the command, re-route it to the appropriate
        33 * handler, and then return the command's response as a standard JSON response
        34 * object. The JSON responses will <em>always</em> be returned with a 200
        35 * response from the server; clients must rely on the response's "status" field
        36 * to determine whether the command succeeded.
        37 *
        38 * <p>This client cannot be used with the standard wire protocol due to
        39 * limitations in the various browser implementations of the CORS specification:
        40 * <ul>
        41 * <li>IE's <a href="http://goo.gl/6l3kA">XDomainRequest</a> object is only
        42 * capable of generating the types of requests that may be generated through
        43 * a standard <a href="http://goo.gl/vgzAU">HTML form</a> - it can not send
        44 * DELETE requests, as is required in the wire protocol.
        45 * <li>WebKit's implementation of CORS does not follow the spec and forbids
        46 * redirects: https://bugs.webkit.org/show_bug.cgi?id=57600
        47 * This limitation appears to be intentional and is documented in WebKit's
        48 * Layout tests:
        49 * //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html
        50 * <li>If the server does not return a 2xx response, IE and Opera's
        51 * implementations will fire the XDomainRequest/XMLHttpRequest object's
        52 * onerror handler, but without the corresponding response text returned by
        53 * the server. This renders IE and Opera incapable of handling command
        54 * failures in the standard JSON protocol.
        55 * </ul>
        56 *
        57 * @param {string} url URL for the WebDriver server to send commands to.
        58 * @constructor
        59 * @implements {webdriver.http.Client}
        60 * @see <a href="http://www.w3.org/TR/cors/">CORS Spec</a>
        61 * @see <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">
        62 * JSON wire protocol</a>
        63 */
        64webdriver.http.CorsClient = function(url) {
        65 if (!webdriver.http.CorsClient.isAvailable()) {
        66 throw Error('The current environment does not support cross-origin ' +
        67 'resource sharing');
        68 }
        69
        70 /** @private {string} */
        71 this.url_ = url + webdriver.http.CorsClient.XDRPC_ENDPOINT;
        72};
        73
        74
        75/**
        76 * Resource URL to send commands to on the server.
        77 * @type {string}
        78 * @const
        79 */
        80webdriver.http.CorsClient.XDRPC_ENDPOINT = '/xdrpc';
        81
        82
        83/**
        84 * Tests whether the current environment supports cross-origin resource sharing.
        85 * @return {boolean} Whether cross-origin resource sharing is supported.
        86 * @see http://www.w3.org/TR/cors/
        87 */
        88webdriver.http.CorsClient.isAvailable = function() {
        89 return typeof XDomainRequest !== 'undefined' ||
        90 (typeof XMLHttpRequest !== 'undefined' &&
        91 goog.isBoolean(new XMLHttpRequest().withCredentials));
        92};
        93
        94
        95/** @override */
        96webdriver.http.CorsClient.prototype.send = function(request, callback) {
        97 try {
        98 var xhr = new (typeof XDomainRequest !== 'undefined' ?
        99 XDomainRequest : XMLHttpRequest);
        100 xhr.open('POST', this.url_, true);
        101
        102 xhr.onload = function() {
        103 callback(null, webdriver.http.Response.fromXmlHttpRequest(
        104 /** @type {!XMLHttpRequest} */ (xhr)));
        105 };
        106
        107 var url = this.url_;
        108 xhr.onerror = function() {
        109 callback(Error([
        110 'Unable to send request: POST ', url,
        111 '\nPerhaps the server did not respond to the preflight request ',
        112 'with valid access control headers?'
        113 ].join('')));
        114 };
        115
        116 // Define event handlers for all events on the XDomainRequest. Apparently,
        117 // if we don't do this, IE9+10 will silently abort our request. Yay IE.
        118 // Note, we're not using goog.nullFunction, because it tends to get
        119 // optimized away by the compiler, which leaves us where we were before.
        120 xhr.onprogress = xhr.ontimeout = function() {};
        121
        122 xhr.send(goog.json.serialize({
        123 'method': request.method,
        124 'path': request.path,
        125 'data': request.data
        126 }));
        127 } catch (ex) {
        128 callback(ex);
        129 }
        130};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/http/http.js.src.html b/docs/source/lib/webdriver/http/http.js.src.html index 291e8c3..2ac026c 100644 --- a/docs/source/lib/webdriver/http/http.js.src.html +++ b/docs/source/lib/webdriver/http/http.js.src.html @@ -1 +1 @@ -http.js

        lib/webdriver/http/http.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines a {@code webdriver.CommandExecutor} that communicates
        17 * with a server over HTTP.
        18 */
        19
        20goog.provide('webdriver.http.Client');
        21goog.provide('webdriver.http.Executor');
        22goog.provide('webdriver.http.Request');
        23goog.provide('webdriver.http.Response');
        24
        25goog.require('bot.ErrorCode');
        26goog.require('goog.array');
        27goog.require('goog.json');
        28goog.require('webdriver.CommandName');
        29goog.require('webdriver.promise.Deferred');
        30
        31
        32
        33/**
        34 * Interface used for sending individual HTTP requests to the server.
        35 * @interface
        36 */
        37webdriver.http.Client = function() {
        38};
        39
        40
        41/**
        42 * Sends a request to the server. If an error occurs while sending the request,
        43 * such as a failure to connect to the server, the provided callback will be
        44 * invoked with a non-null {@code Error} describing the error. Otherwise, when
        45 * the server's response has been received, the callback will be invoked with a
        46 * null Error and non-null {@code webdriver.http.Response} object.
        47 *
        48 * @param {!webdriver.http.Request} request The request to send.
        49 * @param {function(Error, !webdriver.http.Response=)} callback the function to
        50 * invoke when the server's response is ready.
        51 */
        52webdriver.http.Client.prototype.send = function(request, callback) {
        53};
        54
        55
        56
        57/**
        58 * A command executor that communicates with a server using the WebDriver
        59 * command protocol.
        60 * @param {!webdriver.http.Client} client The client to use when sending
        61 * requests to the server.
        62 * @constructor
        63 * @implements {webdriver.CommandExecutor}
        64 */
        65webdriver.http.Executor = function(client) {
        66
        67 /**
        68 * Client used to communicate with the server.
        69 * @private {!webdriver.http.Client}
        70 */
        71 this.client_ = client;
        72};
        73
        74
        75/** @override */
        76webdriver.http.Executor.prototype.execute = function(command, callback) {
        77 var resource = webdriver.http.Executor.COMMAND_MAP_[command.getName()];
        78 if (!resource) {
        79 throw new Error('Unrecognized command: ' + command.getName());
        80 }
        81
        82 var parameters = command.getParameters();
        83 var path = webdriver.http.Executor.buildPath_(resource.path, parameters);
        84 var request = new webdriver.http.Request(resource.method, path, parameters);
        85
        86 this.client_.send(request, function(e, response) {
        87 var responseObj;
        88 if (!e) {
        89 try {
        90 responseObj = webdriver.http.Executor.parseHttpResponse_(
        91 /** @type {!webdriver.http.Response} */ (response));
        92 } catch (ex) {
        93 e = ex;
        94 }
        95 }
        96 callback(e, responseObj);
        97 });
        98};
        99
        100
        101/**
        102 * Builds a fully qualified path using the given set of command parameters. Each
        103 * path segment prefixed with ':' will be replaced by the value of the
        104 * corresponding parameter. All parameters spliced into the path will be
        105 * removed from the parameter map.
        106 * @param {string} path The original resource path.
        107 * @param {!Object.<*>} parameters The parameters object to splice into
        108 * the path.
        109 * @return {string} The modified path.
        110 * @private
        111 */
        112webdriver.http.Executor.buildPath_ = function(path, parameters) {
        113 var pathParameters = path.match(/\/:(\w+)\b/g);
        114 if (pathParameters) {
        115 for (var i = 0; i < pathParameters.length; ++i) {
        116 var key = pathParameters[i].substring(2); // Trim the /:
        117 if (key in parameters) {
        118 var value = parameters[key];
        119 // TODO: move webdriver.WebElement.ELEMENT definition to a
        120 // common file so we can reference it here without pulling in all of
        121 // webdriver.WebElement's dependencies.
        122 if (value && value['ELEMENT']) {
        123 // When inserting a WebElement into the URL, only use its ID value,
        124 // not the full JSON.
        125 value = value['ELEMENT'];
        126 }
        127 path = path.replace(pathParameters[i], '/' + value);
        128 delete parameters[key];
        129 } else {
        130 throw new Error('Missing required parameter: ' + key);
        131 }
        132 }
        133 }
        134 return path;
        135};
        136
        137
        138/**
        139 * Callback used to parse {@link webdriver.http.Response} objects from a
        140 * {@link webdriver.http.Client}.
        141 * @param {!webdriver.http.Response} httpResponse The HTTP response to parse.
        142 * @return {!bot.response.ResponseObject} The parsed response.
        143 * @private
        144 */
        145webdriver.http.Executor.parseHttpResponse_ = function(httpResponse) {
        146 try {
        147 return /** @type {!bot.response.ResponseObject} */ (goog.json.parse(
        148 httpResponse.body));
        149 } catch (ex) {
        150 // Whoops, looks like the server sent us a malformed response. We'll need
        151 // to manually build a response object based on the response code.
        152 }
        153
        154 var response = {
        155 'status': bot.ErrorCode.SUCCESS,
        156 'value': httpResponse.body.replace(/\r\n/g, '\n')
        157 };
        158
        159 if (!(httpResponse.status > 199 && httpResponse.status < 300)) {
        160 // 404 represents an unknown command; anything else is a generic unknown
        161 // error.
        162 response['status'] = httpResponse.status == 404 ?
        163 bot.ErrorCode.UNKNOWN_COMMAND :
        164 bot.ErrorCode.UNKNOWN_ERROR;
        165 }
        166
        167 return response;
        168};
        169
        170
        171/**
        172 * Maps command names to resource locator.
        173 * @private {!Object.<{method:string, path:string}>}
        174 * @const
        175 */
        176webdriver.http.Executor.COMMAND_MAP_ = (function() {
        177 return new Builder().
        178 put(webdriver.CommandName.GET_SERVER_STATUS, get('/status')).
        179 put(webdriver.CommandName.NEW_SESSION, post('/session')).
        180 put(webdriver.CommandName.GET_SESSIONS, get('/sessions')).
        181 put(webdriver.CommandName.DESCRIBE_SESSION, get('/session/:sessionId')).
        182 put(webdriver.CommandName.QUIT, del('/session/:sessionId')).
        183 put(webdriver.CommandName.CLOSE, del('/session/:sessionId/window')).
        184 put(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE,
        185 get('/session/:sessionId/window_handle')).
        186 put(webdriver.CommandName.GET_WINDOW_HANDLES,
        187 get('/session/:sessionId/window_handles')).
        188 put(webdriver.CommandName.GET_CURRENT_URL,
        189 get('/session/:sessionId/url')).
        190 put(webdriver.CommandName.GET, post('/session/:sessionId/url')).
        191 put(webdriver.CommandName.GO_BACK, post('/session/:sessionId/back')).
        192 put(webdriver.CommandName.GO_FORWARD,
        193 post('/session/:sessionId/forward')).
        194 put(webdriver.CommandName.REFRESH,
        195 post('/session/:sessionId/refresh')).
        196 put(webdriver.CommandName.ADD_COOKIE,
        197 post('/session/:sessionId/cookie')).
        198 put(webdriver.CommandName.GET_ALL_COOKIES,
        199 get('/session/:sessionId/cookie')).
        200 put(webdriver.CommandName.DELETE_ALL_COOKIES,
        201 del('/session/:sessionId/cookie')).
        202 put(webdriver.CommandName.DELETE_COOKIE,
        203 del('/session/:sessionId/cookie/:name')).
        204 put(webdriver.CommandName.FIND_ELEMENT,
        205 post('/session/:sessionId/element')).
        206 put(webdriver.CommandName.FIND_ELEMENTS,
        207 post('/session/:sessionId/elements')).
        208 put(webdriver.CommandName.GET_ACTIVE_ELEMENT,
        209 post('/session/:sessionId/element/active')).
        210 put(webdriver.CommandName.FIND_CHILD_ELEMENT,
        211 post('/session/:sessionId/element/:id/element')).
        212 put(webdriver.CommandName.FIND_CHILD_ELEMENTS,
        213 post('/session/:sessionId/element/:id/elements')).
        214 put(webdriver.CommandName.CLEAR_ELEMENT,
        215 post('/session/:sessionId/element/:id/clear')).
        216 put(webdriver.CommandName.CLICK_ELEMENT,
        217 post('/session/:sessionId/element/:id/click')).
        218 put(webdriver.CommandName.SEND_KEYS_TO_ELEMENT,
        219 post('/session/:sessionId/element/:id/value')).
        220 put(webdriver.CommandName.SUBMIT_ELEMENT,
        221 post('/session/:sessionId/element/:id/submit')).
        222 put(webdriver.CommandName.GET_ELEMENT_TEXT,
        223 get('/session/:sessionId/element/:id/text')).
        224 put(webdriver.CommandName.GET_ELEMENT_TAG_NAME,
        225 get('/session/:sessionId/element/:id/name')).
        226 put(webdriver.CommandName.IS_ELEMENT_SELECTED,
        227 get('/session/:sessionId/element/:id/selected')).
        228 put(webdriver.CommandName.IS_ELEMENT_ENABLED,
        229 get('/session/:sessionId/element/:id/enabled')).
        230 put(webdriver.CommandName.IS_ELEMENT_DISPLAYED,
        231 get('/session/:sessionId/element/:id/displayed')).
        232 put(webdriver.CommandName.GET_ELEMENT_LOCATION,
        233 get('/session/:sessionId/element/:id/location')).
        234 put(webdriver.CommandName.GET_ELEMENT_SIZE,
        235 get('/session/:sessionId/element/:id/size')).
        236 put(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE,
        237 get('/session/:sessionId/element/:id/attribute/:name')).
        238 put(webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
        239 get('/session/:sessionId/element/:id/css/:propertyName')).
        240 put(webdriver.CommandName.ELEMENT_EQUALS,
        241 get('/session/:sessionId/element/:id/equals/:other')).
        242 put(webdriver.CommandName.SWITCH_TO_WINDOW,
        243 post('/session/:sessionId/window')).
        244 put(webdriver.CommandName.MAXIMIZE_WINDOW,
        245 post('/session/:sessionId/window/:windowHandle/maximize')).
        246 put(webdriver.CommandName.GET_WINDOW_POSITION,
        247 get('/session/:sessionId/window/:windowHandle/position')).
        248 put(webdriver.CommandName.SET_WINDOW_POSITION,
        249 post('/session/:sessionId/window/:windowHandle/position')).
        250 put(webdriver.CommandName.GET_WINDOW_SIZE,
        251 get('/session/:sessionId/window/:windowHandle/size')).
        252 put(webdriver.CommandName.SET_WINDOW_SIZE,
        253 post('/session/:sessionId/window/:windowHandle/size')).
        254 put(webdriver.CommandName.SWITCH_TO_FRAME,
        255 post('/session/:sessionId/frame')).
        256 put(webdriver.CommandName.GET_PAGE_SOURCE,
        257 get('/session/:sessionId/source')).
        258 put(webdriver.CommandName.GET_TITLE,
        259 get('/session/:sessionId/title')).
        260 put(webdriver.CommandName.EXECUTE_SCRIPT,
        261 post('/session/:sessionId/execute')).
        262 put(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT,
        263 post('/session/:sessionId/execute_async')).
        264 put(webdriver.CommandName.SCREENSHOT,
        265 get('/session/:sessionId/screenshot')).
        266 put(webdriver.CommandName.SET_TIMEOUT,
        267 post('/session/:sessionId/timeouts')).
        268 put(webdriver.CommandName.SET_SCRIPT_TIMEOUT,
        269 post('/session/:sessionId/timeouts/async_script')).
        270 put(webdriver.CommandName.IMPLICITLY_WAIT,
        271 post('/session/:sessionId/timeouts/implicit_wait')).
        272 put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
        273 put(webdriver.CommandName.CLICK, post('/session/:sessionId/click')).
        274 put(webdriver.CommandName.DOUBLE_CLICK,
        275 post('/session/:sessionId/doubleclick')).
        276 put(webdriver.CommandName.MOUSE_DOWN,
        277 post('/session/:sessionId/buttondown')).
        278 put(webdriver.CommandName.MOUSE_UP, post('/session/:sessionId/buttonup')).
        279 put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
        280 put(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT,
        281 post('/session/:sessionId/keys')).
        282 put(webdriver.CommandName.ACCEPT_ALERT,
        283 post('/session/:sessionId/accept_alert')).
        284 put(webdriver.CommandName.DISMISS_ALERT,
        285 post('/session/:sessionId/dismiss_alert')).
        286 put(webdriver.CommandName.GET_ALERT_TEXT,
        287 get('/session/:sessionId/alert_text')).
        288 put(webdriver.CommandName.SET_ALERT_TEXT,
        289 post('/session/:sessionId/alert_text')).
        290 put(webdriver.CommandName.GET_LOG, post('/session/:sessionId/log')).
        291 put(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES,
        292 get('/session/:sessionId/log/types')).
        293 put(webdriver.CommandName.GET_SESSION_LOGS, post('/logs')).
        294 build();
        295
        296 /** @constructor */
        297 function Builder() {
        298 var map = {};
        299
        300 this.put = function(name, resource) {
        301 map[name] = resource;
        302 return this;
        303 };
        304
        305 this.build = function() {
        306 return map;
        307 };
        308 }
        309
        310 function post(path) { return resource('POST', path); }
        311 function del(path) { return resource('DELETE', path); }
        312 function get(path) { return resource('GET', path); }
        313 function resource(method, path) { return {method: method, path: path}; }
        314})();
        315
        316
        317/**
        318 * Converts a headers object to a HTTP header block string.
        319 * @param {!Object.<string>} headers The headers object to convert.
        320 * @return {string} The headers as a string.
        321 * @private
        322 */
        323webdriver.http.headersToString_ = function(headers) {
        324 var ret = [];
        325 for (var key in headers) {
        326 ret.push(key + ': ' + headers[key]);
        327 }
        328 return ret.join('\n');
        329};
        330
        331
        332
        333/**
        334 * Describes a partial HTTP request. This class is a "partial" request and only
        335 * defines the path on the server to send a request to. It is each
        336 * {@code webdriver.http.Client}'s responsibility to build the full URL for the
        337 * final request.
        338 * @param {string} method The HTTP method to use for the request.
        339 * @param {string} path Path on the server to send the request to.
        340 * @param {Object=} opt_data This request's JSON data.
        341 * @constructor
        342 */
        343webdriver.http.Request = function(method, path, opt_data) {
        344
        345 /**
        346 * The HTTP method to use for the request.
        347 * @type {string}
        348 */
        349 this.method = method;
        350
        351 /**
        352 * The path on the server to send the request to.
        353 * @type {string}
        354 */
        355 this.path = path;
        356
        357 /**
        358 * This request's body.
        359 * @type {!Object}
        360 */
        361 this.data = opt_data || {};
        362
        363 /**
        364 * The headers to send with the request.
        365 * @type {!Object.<(string|number)>}
        366 */
        367 this.headers = {'Accept': 'application/json; charset=utf-8'};
        368};
        369
        370
        371/** @override */
        372webdriver.http.Request.prototype.toString = function() {
        373 return [
        374 this.method + ' ' + this.path + ' HTTP/1.1',
        375 webdriver.http.headersToString_(this.headers),
        376 '',
        377 goog.json.serialize(this.data)
        378 ].join('\n');
        379};
        380
        381
        382
        383/**
        384 * Represents a HTTP response.
        385 * @param {number} status The response code.
        386 * @param {!Object.<string>} headers The response headers. All header
        387 * names will be converted to lowercase strings for consistent lookups.
        388 * @param {string} body The response body.
        389 * @constructor
        390 */
        391webdriver.http.Response = function(status, headers, body) {
        392
        393 /**
        394 * The HTTP response code.
        395 * @type {number}
        396 */
        397 this.status = status;
        398
        399 /**
        400 * The response body.
        401 * @type {string}
        402 */
        403 this.body = body;
        404
        405 /**
        406 * The response body.
        407 * @type {!Object.<string>}
        408 */
        409 this.headers = {};
        410 for (var header in headers) {
        411 this.headers[header.toLowerCase()] = headers[header];
        412 }
        413};
        414
        415
        416/**
        417 * Builds a {@code webdriver.http.Response} from a {@code XMLHttpRequest} or
        418 * {@code XDomainRequest} response object.
        419 * @param {!(XDomainRequest|XMLHttpRequest)} xhr The request to parse.
        420 * @return {!webdriver.http.Response} The parsed response.
        421 */
        422webdriver.http.Response.fromXmlHttpRequest = function(xhr) {
        423 var headers = {};
        424
        425 // getAllResponseHeaders is only available on XMLHttpRequest objects.
        426 if (xhr.getAllResponseHeaders) {
        427 var tmp = xhr.getAllResponseHeaders();
        428 if (tmp) {
        429 tmp = tmp.replace(/\r\n/g, '\n').split('\n');
        430 goog.array.forEach(tmp, function(header) {
        431 var parts = header.split(/\s*:\s*/, 2);
        432 if (parts[0]) {
        433 headers[parts[0]] = parts[1] || '';
        434 }
        435 });
        436 }
        437 }
        438
        439 // If xhr is a XDomainRequest object, it will not have a status.
        440 // However, if we're parsing the response from a XDomainRequest, then
        441 // that request must have been a success, so we can assume status == 200.
        442 var status = xhr.status || 200;
        443 return new webdriver.http.Response(status, headers,
        444 xhr.responseText.replace(/\0/g, ''));
        445};
        446
        447
        448/** @override */
        449webdriver.http.Response.prototype.toString = function() {
        450 var headers = webdriver.http.headersToString_(this.headers);
        451 var ret = ['HTTP/1.1 ' + this.status, headers];
        452
        453 if (headers) {
        454 ret.push('');
        455 }
        456
        457 if (this.body) {
        458 ret.push(this.body);
        459 }
        460
        461 return ret.join('\n');
        462};
        \ No newline at end of file +http.js

        lib/webdriver/http/http.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a {@code webdriver.CommandExecutor} that communicates
        20 * with a server over HTTP.
        21 */
        22
        23goog.provide('webdriver.http.Client');
        24goog.provide('webdriver.http.Executor');
        25goog.provide('webdriver.http.Request');
        26goog.provide('webdriver.http.Response');
        27
        28goog.require('bot.ErrorCode');
        29goog.require('goog.array');
        30goog.require('webdriver.CommandExecutor');
        31goog.require('webdriver.CommandName');
        32goog.require('webdriver.logging');
        33goog.require('webdriver.promise');
        34
        35
        36
        37/**
        38 * Interface used for sending individual HTTP requests to the server.
        39 * @interface
        40 */
        41webdriver.http.Client = function() {
        42};
        43
        44
        45/**
        46 * Sends a request to the server. If an error occurs while sending the request,
        47 * such as a failure to connect to the server, the provided callback will be
        48 * invoked with a non-null {@link Error} describing the error. Otherwise, when
        49 * the server's response has been received, the callback will be invoked with a
        50 * null Error and non-null {@link webdriver.http.Response} object.
        51 *
        52 * @param {!webdriver.http.Request} request The request to send.
        53 * @param {function(Error, !webdriver.http.Response=)} callback the function to
        54 * invoke when the server's response is ready.
        55 */
        56webdriver.http.Client.prototype.send = function(request, callback) {
        57};
        58
        59
        60
        61/**
        62 * A command executor that communicates with a server using the WebDriver
        63 * command protocol.
        64 * @param {!webdriver.http.Client} client The client to use when sending
        65 * requests to the server.
        66 * @constructor
        67 * @implements {webdriver.CommandExecutor}
        68 */
        69webdriver.http.Executor = function(client) {
        70
        71 /**
        72 * Client used to communicate with the server.
        73 * @private {!webdriver.http.Client}
        74 */
        75 this.client_ = client;
        76
        77 /**
        78 * @private {!Object<{method:string, path:string}>}
        79 */
        80 this.customCommands_ = {};
        81
        82 /**
        83 * @private {!webdriver.logging.Logger}
        84 */
        85 this.log_ = webdriver.logging.getLogger('webdriver.http.Executor');
        86};
        87
        88
        89/**
        90 * Defines a new command for use with this executor. When a command is sent,
        91 * the {@code path} will be preprocessed using the command's parameters; any
        92 * path segments prefixed with ":" will be replaced by the parameter of the
        93 * same name. For example, given "/person/:name" and the parameters
        94 * "{name: 'Bob'}", the final command path will be "/person/Bob".
        95 *
        96 * @param {string} name The command name.
        97 * @param {string} method The HTTP method to use when sending this command.
        98 * @param {string} path The path to send the command to, relative to
        99 * the WebDriver server's command root and of the form
        100 * "/path/:variable/segment".
        101 */
        102webdriver.http.Executor.prototype.defineCommand = function(
        103 name, method, path) {
        104 this.customCommands_[name] = {method: method, path: path};
        105};
        106
        107
        108/** @override */
        109webdriver.http.Executor.prototype.execute = function(command, callback) {
        110 var resource =
        111 this.customCommands_[command.getName()] ||
        112 webdriver.http.Executor.COMMAND_MAP_[command.getName()];
        113 if (!resource) {
        114 throw new Error('Unrecognized command: ' + command.getName());
        115 }
        116
        117 var parameters = command.getParameters();
        118 var path = webdriver.http.Executor.buildPath_(resource.path, parameters);
        119 var request = new webdriver.http.Request(resource.method, path, parameters);
        120
        121 var log = this.log_;
        122 log.finer(function() {
        123 return '>>>\n' + request;
        124 });
        125
        126 this.client_.send(request, function(e, response) {
        127 var responseObj;
        128 if (!e) {
        129 log.finer(function() {
        130 return '<<<\n' + response;
        131 });
        132 try {
        133 responseObj = webdriver.http.Executor.parseHttpResponse_(
        134 /** @type {!webdriver.http.Response} */ (response));
        135 } catch (ex) {
        136 log.warning('Error parsing response', ex);
        137 e = ex;
        138 }
        139 }
        140 callback(e, responseObj);
        141 });
        142};
        143
        144
        145/**
        146 * Builds a fully qualified path using the given set of command parameters. Each
        147 * path segment prefixed with ':' will be replaced by the value of the
        148 * corresponding parameter. All parameters spliced into the path will be
        149 * removed from the parameter map.
        150 * @param {string} path The original resource path.
        151 * @param {!Object.<*>} parameters The parameters object to splice into
        152 * the path.
        153 * @return {string} The modified path.
        154 * @private
        155 */
        156webdriver.http.Executor.buildPath_ = function(path, parameters) {
        157 var pathParameters = path.match(/\/:(\w+)\b/g);
        158 if (pathParameters) {
        159 for (var i = 0; i < pathParameters.length; ++i) {
        160 var key = pathParameters[i].substring(2); // Trim the /:
        161 if (key in parameters) {
        162 var value = parameters[key];
        163 // TODO: move webdriver.WebElement.ELEMENT definition to a
        164 // common file so we can reference it here without pulling in all of
        165 // webdriver.WebElement's dependencies.
        166 if (value && value['ELEMENT']) {
        167 // When inserting a WebElement into the URL, only use its ID value,
        168 // not the full JSON.
        169 value = value['ELEMENT'];
        170 }
        171 path = path.replace(pathParameters[i], '/' + value);
        172 delete parameters[key];
        173 } else {
        174 throw new Error('Missing required parameter: ' + key);
        175 }
        176 }
        177 }
        178 return path;
        179};
        180
        181
        182/**
        183 * Callback used to parse {@link webdriver.http.Response} objects from a
        184 * {@link webdriver.http.Client}.
        185 * @param {!webdriver.http.Response} httpResponse The HTTP response to parse.
        186 * @return {!bot.response.ResponseObject} The parsed response.
        187 * @private
        188 */
        189webdriver.http.Executor.parseHttpResponse_ = function(httpResponse) {
        190 try {
        191 return /** @type {!bot.response.ResponseObject} */ (JSON.parse(
        192 httpResponse.body));
        193 } catch (ex) {
        194 // Whoops, looks like the server sent us a malformed response. We'll need
        195 // to manually build a response object based on the response code.
        196 }
        197
        198 var response = {
        199 'status': bot.ErrorCode.SUCCESS,
        200 'value': httpResponse.body.replace(/\r\n/g, '\n')
        201 };
        202
        203 if (!(httpResponse.status > 199 && httpResponse.status < 300)) {
        204 // 404 represents an unknown command; anything else is a generic unknown
        205 // error.
        206 response['status'] = httpResponse.status == 404 ?
        207 bot.ErrorCode.UNKNOWN_COMMAND :
        208 bot.ErrorCode.UNKNOWN_ERROR;
        209 }
        210
        211 return response;
        212};
        213
        214
        215/**
        216 * Maps command names to resource locator.
        217 * @private {!Object.<{method:string, path:string}>}
        218 * @const
        219 */
        220webdriver.http.Executor.COMMAND_MAP_ = (function() {
        221 return new Builder().
        222 put(webdriver.CommandName.GET_SERVER_STATUS, get('/status')).
        223 put(webdriver.CommandName.NEW_SESSION, post('/session')).
        224 put(webdriver.CommandName.GET_SESSIONS, get('/sessions')).
        225 put(webdriver.CommandName.DESCRIBE_SESSION, get('/session/:sessionId')).
        226 put(webdriver.CommandName.QUIT, del('/session/:sessionId')).
        227 put(webdriver.CommandName.CLOSE, del('/session/:sessionId/window')).
        228 put(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE,
        229 get('/session/:sessionId/window_handle')).
        230 put(webdriver.CommandName.GET_WINDOW_HANDLES,
        231 get('/session/:sessionId/window_handles')).
        232 put(webdriver.CommandName.GET_CURRENT_URL,
        233 get('/session/:sessionId/url')).
        234 put(webdriver.CommandName.GET, post('/session/:sessionId/url')).
        235 put(webdriver.CommandName.GO_BACK, post('/session/:sessionId/back')).
        236 put(webdriver.CommandName.GO_FORWARD,
        237 post('/session/:sessionId/forward')).
        238 put(webdriver.CommandName.REFRESH,
        239 post('/session/:sessionId/refresh')).
        240 put(webdriver.CommandName.ADD_COOKIE,
        241 post('/session/:sessionId/cookie')).
        242 put(webdriver.CommandName.GET_ALL_COOKIES,
        243 get('/session/:sessionId/cookie')).
        244 put(webdriver.CommandName.DELETE_ALL_COOKIES,
        245 del('/session/:sessionId/cookie')).
        246 put(webdriver.CommandName.DELETE_COOKIE,
        247 del('/session/:sessionId/cookie/:name')).
        248 put(webdriver.CommandName.FIND_ELEMENT,
        249 post('/session/:sessionId/element')).
        250 put(webdriver.CommandName.FIND_ELEMENTS,
        251 post('/session/:sessionId/elements')).
        252 put(webdriver.CommandName.GET_ACTIVE_ELEMENT,
        253 post('/session/:sessionId/element/active')).
        254 put(webdriver.CommandName.FIND_CHILD_ELEMENT,
        255 post('/session/:sessionId/element/:id/element')).
        256 put(webdriver.CommandName.FIND_CHILD_ELEMENTS,
        257 post('/session/:sessionId/element/:id/elements')).
        258 put(webdriver.CommandName.CLEAR_ELEMENT,
        259 post('/session/:sessionId/element/:id/clear')).
        260 put(webdriver.CommandName.CLICK_ELEMENT,
        261 post('/session/:sessionId/element/:id/click')).
        262 put(webdriver.CommandName.SEND_KEYS_TO_ELEMENT,
        263 post('/session/:sessionId/element/:id/value')).
        264 put(webdriver.CommandName.SUBMIT_ELEMENT,
        265 post('/session/:sessionId/element/:id/submit')).
        266 put(webdriver.CommandName.GET_ELEMENT_TEXT,
        267 get('/session/:sessionId/element/:id/text')).
        268 put(webdriver.CommandName.GET_ELEMENT_TAG_NAME,
        269 get('/session/:sessionId/element/:id/name')).
        270 put(webdriver.CommandName.IS_ELEMENT_SELECTED,
        271 get('/session/:sessionId/element/:id/selected')).
        272 put(webdriver.CommandName.IS_ELEMENT_ENABLED,
        273 get('/session/:sessionId/element/:id/enabled')).
        274 put(webdriver.CommandName.IS_ELEMENT_DISPLAYED,
        275 get('/session/:sessionId/element/:id/displayed')).
        276 put(webdriver.CommandName.GET_ELEMENT_LOCATION,
        277 get('/session/:sessionId/element/:id/location')).
        278 put(webdriver.CommandName.GET_ELEMENT_SIZE,
        279 get('/session/:sessionId/element/:id/size')).
        280 put(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE,
        281 get('/session/:sessionId/element/:id/attribute/:name')).
        282 put(webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY,
        283 get('/session/:sessionId/element/:id/css/:propertyName')).
        284 put(webdriver.CommandName.ELEMENT_EQUALS,
        285 get('/session/:sessionId/element/:id/equals/:other')).
        286 put(webdriver.CommandName.SWITCH_TO_WINDOW,
        287 post('/session/:sessionId/window')).
        288 put(webdriver.CommandName.MAXIMIZE_WINDOW,
        289 post('/session/:sessionId/window/:windowHandle/maximize')).
        290 put(webdriver.CommandName.GET_WINDOW_POSITION,
        291 get('/session/:sessionId/window/:windowHandle/position')).
        292 put(webdriver.CommandName.SET_WINDOW_POSITION,
        293 post('/session/:sessionId/window/:windowHandle/position')).
        294 put(webdriver.CommandName.GET_WINDOW_SIZE,
        295 get('/session/:sessionId/window/:windowHandle/size')).
        296 put(webdriver.CommandName.SET_WINDOW_SIZE,
        297 post('/session/:sessionId/window/:windowHandle/size')).
        298 put(webdriver.CommandName.SWITCH_TO_FRAME,
        299 post('/session/:sessionId/frame')).
        300 put(webdriver.CommandName.GET_PAGE_SOURCE,
        301 get('/session/:sessionId/source')).
        302 put(webdriver.CommandName.GET_TITLE,
        303 get('/session/:sessionId/title')).
        304 put(webdriver.CommandName.EXECUTE_SCRIPT,
        305 post('/session/:sessionId/execute')).
        306 put(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT,
        307 post('/session/:sessionId/execute_async')).
        308 put(webdriver.CommandName.SCREENSHOT,
        309 get('/session/:sessionId/screenshot')).
        310 put(webdriver.CommandName.SET_TIMEOUT,
        311 post('/session/:sessionId/timeouts')).
        312 put(webdriver.CommandName.SET_SCRIPT_TIMEOUT,
        313 post('/session/:sessionId/timeouts/async_script')).
        314 put(webdriver.CommandName.IMPLICITLY_WAIT,
        315 post('/session/:sessionId/timeouts/implicit_wait')).
        316 put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
        317 put(webdriver.CommandName.CLICK, post('/session/:sessionId/click')).
        318 put(webdriver.CommandName.DOUBLE_CLICK,
        319 post('/session/:sessionId/doubleclick')).
        320 put(webdriver.CommandName.MOUSE_DOWN,
        321 post('/session/:sessionId/buttondown')).
        322 put(webdriver.CommandName.MOUSE_UP, post('/session/:sessionId/buttonup')).
        323 put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')).
        324 put(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT,
        325 post('/session/:sessionId/keys')).
        326 put(webdriver.CommandName.TOUCH_SINGLE_TAP,
        327 post('/session/:sessionId/touch/click')).
        328 put(webdriver.CommandName.TOUCH_DOUBLE_TAP,
        329 post('/session/:sessionId/touch/doubleclick')).
        330 put(webdriver.CommandName.TOUCH_DOWN,
        331 post('/session/:sessionId/touch/down')).
        332 put(webdriver.CommandName.TOUCH_UP,
        333 post('/session/:sessionId/touch/up')).
        334 put(webdriver.CommandName.TOUCH_MOVE,
        335 post('/session/:sessionId/touch/move')).
        336 put(webdriver.CommandName.TOUCH_SCROLL,
        337 post('/session/:sessionId/touch/scroll')).
        338 put(webdriver.CommandName.TOUCH_LONG_PRESS,
        339 post('/session/:sessionId/touch/longclick')).
        340 put(webdriver.CommandName.TOUCH_FLICK,
        341 post('/session/:sessionId/touch/flick')).
        342 put(webdriver.CommandName.ACCEPT_ALERT,
        343 post('/session/:sessionId/accept_alert')).
        344 put(webdriver.CommandName.DISMISS_ALERT,
        345 post('/session/:sessionId/dismiss_alert')).
        346 put(webdriver.CommandName.GET_ALERT_TEXT,
        347 get('/session/:sessionId/alert_text')).
        348 put(webdriver.CommandName.SET_ALERT_TEXT,
        349 post('/session/:sessionId/alert_text')).
        350 put(webdriver.CommandName.GET_LOG, post('/session/:sessionId/log')).
        351 put(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES,
        352 get('/session/:sessionId/log/types')).
        353 put(webdriver.CommandName.GET_SESSION_LOGS, post('/logs')).
        354 put(webdriver.CommandName.UPLOAD_FILE, post('/session/:sessionId/file')).
        355 build();
        356
        357 /** @constructor */
        358 function Builder() {
        359 var map = {};
        360
        361 this.put = function(name, resource) {
        362 map[name] = resource;
        363 return this;
        364 };
        365
        366 this.build = function() {
        367 return map;
        368 };
        369 }
        370
        371 function post(path) { return resource('POST', path); }
        372 function del(path) { return resource('DELETE', path); }
        373 function get(path) { return resource('GET', path); }
        374 function resource(method, path) { return {method: method, path: path}; }
        375})();
        376
        377
        378/**
        379 * Converts a headers object to a HTTP header block string.
        380 * @param {!Object.<string>} headers The headers object to convert.
        381 * @return {string} The headers as a string.
        382 * @private
        383 */
        384webdriver.http.headersToString_ = function(headers) {
        385 var ret = [];
        386 for (var key in headers) {
        387 ret.push(key + ': ' + headers[key]);
        388 }
        389 return ret.join('\n');
        390};
        391
        392
        393
        394/**
        395 * Describes a partial HTTP request. This class is a "partial" request and only
        396 * defines the path on the server to send a request to. It is each
        397 * {@link webdriver.http.Client}'s responsibility to build the full URL for the
        398 * final request.
        399 * @param {string} method The HTTP method to use for the request.
        400 * @param {string} path Path on the server to send the request to.
        401 * @param {Object=} opt_data This request's JSON data.
        402 * @constructor
        403 */
        404webdriver.http.Request = function(method, path, opt_data) {
        405
        406 /**
        407 * The HTTP method to use for the request.
        408 * @type {string}
        409 */
        410 this.method = method;
        411
        412 /**
        413 * The path on the server to send the request to.
        414 * @type {string}
        415 */
        416 this.path = path;
        417
        418 /**
        419 * This request's body.
        420 * @type {!Object}
        421 */
        422 this.data = opt_data || {};
        423
        424 /**
        425 * The headers to send with the request.
        426 * @type {!Object.<(string|number)>}
        427 */
        428 this.headers = {'Accept': 'application/json; charset=utf-8'};
        429};
        430
        431
        432/** @override */
        433webdriver.http.Request.prototype.toString = function() {
        434 return [
        435 this.method + ' ' + this.path + ' HTTP/1.1',
        436 webdriver.http.headersToString_(this.headers),
        437 '',
        438 JSON.stringify(this.data)
        439 ].join('\n');
        440};
        441
        442
        443
        444/**
        445 * Represents a HTTP response.
        446 * @param {number} status The response code.
        447 * @param {!Object.<string>} headers The response headers. All header
        448 * names will be converted to lowercase strings for consistent lookups.
        449 * @param {string} body The response body.
        450 * @constructor
        451 */
        452webdriver.http.Response = function(status, headers, body) {
        453
        454 /**
        455 * The HTTP response code.
        456 * @type {number}
        457 */
        458 this.status = status;
        459
        460 /**
        461 * The response body.
        462 * @type {string}
        463 */
        464 this.body = body;
        465
        466 /**
        467 * The response body.
        468 * @type {!Object.<string>}
        469 */
        470 this.headers = {};
        471 for (var header in headers) {
        472 this.headers[header.toLowerCase()] = headers[header];
        473 }
        474};
        475
        476
        477/**
        478 * Builds a {@link webdriver.http.Response} from a {@link XMLHttpRequest} or
        479 * {@link XDomainRequest} response object.
        480 * @param {!(XDomainRequest|XMLHttpRequest)} xhr The request to parse.
        481 * @return {!webdriver.http.Response} The parsed response.
        482 */
        483webdriver.http.Response.fromXmlHttpRequest = function(xhr) {
        484 var headers = {};
        485
        486 // getAllResponseHeaders is only available on XMLHttpRequest objects.
        487 if (xhr.getAllResponseHeaders) {
        488 var tmp = xhr.getAllResponseHeaders();
        489 if (tmp) {
        490 tmp = tmp.replace(/\r\n/g, '\n').split('\n');
        491 goog.array.forEach(tmp, function(header) {
        492 var parts = header.split(/\s*:\s*/, 2);
        493 if (parts[0]) {
        494 headers[parts[0]] = parts[1] || '';
        495 }
        496 });
        497 }
        498 }
        499
        500 // If xhr is a XDomainRequest object, it will not have a status.
        501 // However, if we're parsing the response from a XDomainRequest, then
        502 // that request must have been a success, so we can assume status == 200.
        503 var status = xhr.status || 200;
        504 return new webdriver.http.Response(status, headers,
        505 xhr.responseText.replace(/\0/g, ''));
        506};
        507
        508
        509/** @override */
        510webdriver.http.Response.prototype.toString = function() {
        511 var headers = webdriver.http.headersToString_(this.headers);
        512 var ret = ['HTTP/1.1 ' + this.status, headers];
        513
        514 if (headers) {
        515 ret.push('');
        516 }
        517
        518 if (this.body) {
        519 ret.push(this.body);
        520 }
        521
        522 return ret.join('\n');
        523};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/http/xhrclient.js.src.html b/docs/source/lib/webdriver/http/xhrclient.js.src.html index d810afe..a71f98f 100644 --- a/docs/source/lib/webdriver/http/xhrclient.js.src.html +++ b/docs/source/lib/webdriver/http/xhrclient.js.src.html @@ -1 +1 @@ -xhrclient.js

        lib/webdriver/http/xhrclient.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/** @fileoverview A XHR client. */
        16
        17goog.provide('webdriver.http.XhrClient');
        18
        19goog.require('goog.json');
        20goog.require('goog.net.XmlHttp');
        21goog.require('webdriver.http.Response');
        22
        23
        24
        25/**
        26 * A HTTP client that sends requests using XMLHttpRequests.
        27 * @param {string} url URL for the WebDriver server to send commands to.
        28 * @constructor
        29 * @implements {webdriver.http.Client}
        30 */
        31webdriver.http.XhrClient = function(url) {
        32
        33 /** @private {string} */
        34 this.url_ = url;
        35};
        36
        37
        38/** @override */
        39webdriver.http.XhrClient.prototype.send = function(request, callback) {
        40 try {
        41 var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp());
        42 var url = this.url_ + request.path;
        43 xhr.open(request.method, url, true);
        44
        45 xhr.onload = function() {
        46 callback(null, webdriver.http.Response.fromXmlHttpRequest(xhr));
        47 };
        48
        49 xhr.onerror = function() {
        50 callback(Error([
        51 'Unable to send request: ', request.method, ' ', url,
        52 '\nOriginal request:\n', request
        53 ].join('')));
        54 };
        55
        56 for (var header in request.headers) {
        57 xhr.setRequestHeader(header, request.headers[header] + '');
        58 }
        59
        60 xhr.send(goog.json.serialize(request.data));
        61 } catch (ex) {
        62 callback(ex);
        63 }
        64};
        \ No newline at end of file +xhrclient.js

        lib/webdriver/http/xhrclient.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/** @fileoverview A XHR client. */
        16
        17goog.provide('webdriver.http.XhrClient');
        18
        19goog.require('goog.json');
        20goog.require('goog.net.XmlHttp');
        21goog.require('webdriver.http.Response');
        22
        23
        24
        25/**
        26 * A HTTP client that sends requests using XMLHttpRequests.
        27 * @param {string} url URL for the WebDriver server to send commands to.
        28 * @constructor
        29 * @implements {webdriver.http.Client}
        30 */
        31webdriver.http.XhrClient = function(url) {
        32
        33 /** @private {string} */
        34 this.url_ = url;
        35};
        36
        37
        38/** @override */
        39webdriver.http.XhrClient.prototype.send = function(request, callback) {
        40 try {
        41 var xhr = /** @type {!XMLHttpRequest} */ (goog.net.XmlHttp());
        42 var url = this.url_ + request.path;
        43 xhr.open(request.method, url, true);
        44
        45 xhr.onload = function() {
        46 callback(null, webdriver.http.Response.fromXmlHttpRequest(xhr));
        47 };
        48
        49 xhr.onerror = function() {
        50 callback(Error([
        51 'Unable to send request: ', request.method, ' ', url,
        52 '\nOriginal request:\n', request
        53 ].join('')));
        54 };
        55
        56 for (var header in request.headers) {
        57 xhr.setRequestHeader(header, request.headers[header] + '');
        58 }
        59
        60 xhr.send(goog.json.serialize(request.data));
        61 } catch (ex) {
        62 callback(ex);
        63 }
        64};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/key.js.src.html b/docs/source/lib/webdriver/key.js.src.html index 4cb50ac..1813550 100644 --- a/docs/source/lib/webdriver/key.js.src.html +++ b/docs/source/lib/webdriver/key.js.src.html @@ -1 +1 @@ -key.js

        lib/webdriver/key.js

        1// Copyright 2012 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.Key');
        16
        17
        18/**
        19 * Representations of pressable keys that aren't text. These are stored in
        20 * the Unicode PUA (Private Use Area) code points, 0xE000-0xF8FF. Refer to
        21 * http://www.google.com.au/search?&q=unicode+pua&btnG=Search
        22 *
        23 * @enum {string}
        24 */
        25webdriver.Key = {
        26 NULL: '\uE000',
        27 CANCEL: '\uE001', // ^break
        28 HELP: '\uE002',
        29 BACK_SPACE: '\uE003',
        30 TAB: '\uE004',
        31 CLEAR: '\uE005',
        32 RETURN: '\uE006',
        33 ENTER: '\uE007',
        34 SHIFT: '\uE008',
        35 CONTROL: '\uE009',
        36 ALT: '\uE00A',
        37 PAUSE: '\uE00B',
        38 ESCAPE: '\uE00C',
        39 SPACE: '\uE00D',
        40 PAGE_UP: '\uE00E',
        41 PAGE_DOWN: '\uE00F',
        42 END: '\uE010',
        43 HOME: '\uE011',
        44 ARROW_LEFT: '\uE012',
        45 LEFT: '\uE012',
        46 ARROW_UP: '\uE013',
        47 UP: '\uE013',
        48 ARROW_RIGHT: '\uE014',
        49 RIGHT: '\uE014',
        50 ARROW_DOWN: '\uE015',
        51 DOWN: '\uE015',
        52 INSERT: '\uE016',
        53 DELETE: '\uE017',
        54 SEMICOLON: '\uE018',
        55 EQUALS: '\uE019',
        56
        57 NUMPAD0: '\uE01A', // number pad keys
        58 NUMPAD1: '\uE01B',
        59 NUMPAD2: '\uE01C',
        60 NUMPAD3: '\uE01D',
        61 NUMPAD4: '\uE01E',
        62 NUMPAD5: '\uE01F',
        63 NUMPAD6: '\uE020',
        64 NUMPAD7: '\uE021',
        65 NUMPAD8: '\uE022',
        66 NUMPAD9: '\uE023',
        67 MULTIPLY: '\uE024',
        68 ADD: '\uE025',
        69 SEPARATOR: '\uE026',
        70 SUBTRACT: '\uE027',
        71 DECIMAL: '\uE028',
        72 DIVIDE: '\uE029',
        73
        74 F1: '\uE031', // function keys
        75 F2: '\uE032',
        76 F3: '\uE033',
        77 F4: '\uE034',
        78 F5: '\uE035',
        79 F6: '\uE036',
        80 F7: '\uE037',
        81 F8: '\uE038',
        82 F9: '\uE039',
        83 F10: '\uE03A',
        84 F11: '\uE03B',
        85 F12: '\uE03C',
        86
        87 COMMAND: '\uE03D', // Apple command key
        88 META: '\uE03D' // alias for Windows key
        89};
        \ No newline at end of file +key.js

        lib/webdriver/key.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.Key');
        19
        20
        21/**
        22 * Representations of pressable keys that aren't text. These are stored in
        23 * the Unicode PUA (Private Use Area) code points, 0xE000-0xF8FF. Refer to
        24 * http://www.google.com.au/search?&q=unicode+pua&btnG=Search
        25 *
        26 * @enum {string}
        27 */
        28webdriver.Key = {
        29 NULL: '\uE000',
        30 CANCEL: '\uE001', // ^break
        31 HELP: '\uE002',
        32 BACK_SPACE: '\uE003',
        33 TAB: '\uE004',
        34 CLEAR: '\uE005',
        35 RETURN: '\uE006',
        36 ENTER: '\uE007',
        37 SHIFT: '\uE008',
        38 CONTROL: '\uE009',
        39 ALT: '\uE00A',
        40 PAUSE: '\uE00B',
        41 ESCAPE: '\uE00C',
        42 SPACE: '\uE00D',
        43 PAGE_UP: '\uE00E',
        44 PAGE_DOWN: '\uE00F',
        45 END: '\uE010',
        46 HOME: '\uE011',
        47 ARROW_LEFT: '\uE012',
        48 LEFT: '\uE012',
        49 ARROW_UP: '\uE013',
        50 UP: '\uE013',
        51 ARROW_RIGHT: '\uE014',
        52 RIGHT: '\uE014',
        53 ARROW_DOWN: '\uE015',
        54 DOWN: '\uE015',
        55 INSERT: '\uE016',
        56 DELETE: '\uE017',
        57 SEMICOLON: '\uE018',
        58 EQUALS: '\uE019',
        59
        60 NUMPAD0: '\uE01A', // number pad keys
        61 NUMPAD1: '\uE01B',
        62 NUMPAD2: '\uE01C',
        63 NUMPAD3: '\uE01D',
        64 NUMPAD4: '\uE01E',
        65 NUMPAD5: '\uE01F',
        66 NUMPAD6: '\uE020',
        67 NUMPAD7: '\uE021',
        68 NUMPAD8: '\uE022',
        69 NUMPAD9: '\uE023',
        70 MULTIPLY: '\uE024',
        71 ADD: '\uE025',
        72 SEPARATOR: '\uE026',
        73 SUBTRACT: '\uE027',
        74 DECIMAL: '\uE028',
        75 DIVIDE: '\uE029',
        76
        77 F1: '\uE031', // function keys
        78 F2: '\uE032',
        79 F3: '\uE033',
        80 F4: '\uE034',
        81 F5: '\uE035',
        82 F6: '\uE036',
        83 F7: '\uE037',
        84 F8: '\uE038',
        85 F9: '\uE039',
        86 F10: '\uE03A',
        87 F11: '\uE03B',
        88 F12: '\uE03C',
        89
        90 COMMAND: '\uE03D', // Apple command key
        91 META: '\uE03D' // alias for Windows key
        92};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/locators.js.src.html b/docs/source/lib/webdriver/locators.js.src.html index 6587c12..8cedcb3 100644 --- a/docs/source/lib/webdriver/locators.js.src.html +++ b/docs/source/lib/webdriver/locators.js.src.html @@ -1 +1 @@ -locators.js

        lib/webdriver/locators.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Factory methods for the supported locator strategies.
        17 */
        18
        19goog.provide('webdriver.By');
        20goog.provide('webdriver.Locator');
        21goog.provide('webdriver.Locator.Strategy');
        22
        23goog.require('goog.array');
        24goog.require('goog.object');
        25goog.require('goog.string');
        26
        27
        28
        29/**
        30 * An element locator.
        31 * @param {string} using The type of strategy to use for this locator.
        32 * @param {string} value The search target of this locator.
        33 * @constructor
        34 */
        35webdriver.Locator = function(using, value) {
        36
        37 /**
        38 * The search strategy to use when searching for an element.
        39 * @type {string}
        40 */
        41 this.using = using;
        42
        43 /**
        44 * The search target for this locator.
        45 * @type {string}
        46 */
        47 this.value = value;
        48};
        49
        50
        51/**
        52 * Creates a factory function for a {@link webdriver.Locator}.
        53 * @param {string} type The type of locator for the factory.
        54 * @return {function(string): !webdriver.Locator} The new factory function.
        55 * @private
        56 */
        57webdriver.Locator.factory_ = function(type) {
        58 return function(value) {
        59 return new webdriver.Locator(type, value);
        60 };
        61};
        62
        63
        64/**
        65 * A collection of factory functions for creating {@link webdriver.Locator}
        66 * instances.
        67 */
        68webdriver.By = {};
        69// Exported to the global scope for legacy reasons.
        70goog.exportSymbol('By', webdriver.By);
        71
        72
        73/**
        74 * Short-hand expressions for the primary element locator strategies.
        75 * For example the following two statements are equivalent:
        76 * <code><pre>
        77 * var e1 = driver.findElement(webdriver.By.id('foo'));
        78 * var e2 = driver.findElement({id: 'foo'});
        79 * </pre></code>
        80 *
        81 * <p>Care should be taken when using JavaScript minifiers (such as the
        82 * Closure compiler), as locator hashes will always be parsed using
        83 * the un-obfuscated properties listed below.
        84 *
        85 * @typedef {(
        86 * {className: string}|
        87 * {css: string}|
        88 * {id: string}|
        89 * {js: string}|
        90 * {linkText: string}|
        91 * {name: string}|
        92 * {partialLinkText: string}|
        93 * {tagName: string}|
        94 * {xpath: string})}
        95 */
        96webdriver.By.Hash;
        97
        98
        99/**
        100 * Locates elements that have a specific class name. The returned locator
        101 * is equivalent to searching for elements with the CSS selector ".clazz".
        102 *
        103 * @param {string} className The class name to search for.
        104 * @return {!webdriver.Locator} The new locator.
        105 * @see http://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
        106 * @see http://www.w3.org/TR/CSS2/selector.html#class-html
        107 */
        108webdriver.By.className = webdriver.Locator.factory_('class name');
        109
        110
        111/**
        112 * Locates elements using a CSS selector. For browsers that do not support
        113 * CSS selectors, WebDriver implementations may return an
        114 * {@link bot.Error.State.INVALID_SELECTOR invalid selector} error. An
        115 * implementation may, however, emulate the CSS selector API.
        116 *
        117 * @param {string} selector The CSS selector to use.
        118 * @return {!webdriver.Locator} The new locator.
        119 * @see http://www.w3.org/TR/CSS2/selector.html
        120 */
        121webdriver.By.css = webdriver.Locator.factory_('css selector');
        122
        123
        124/**
        125 * Locates an element by its ID.
        126 *
        127 * @param {string} id The ID to search for.
        128 * @return {!webdriver.Locator} The new locator.
        129 */
        130webdriver.By.id = webdriver.Locator.factory_('id');
        131
        132
        133/**
        134 * Locates link elements whose {@link webdriver.WebElement#getText visible
        135 * text} matches the given string.
        136 *
        137 * @param {string} text The link text to search for.
        138 * @return {!webdriver.Locator} The new locator.
        139 */
        140webdriver.By.linkText = webdriver.Locator.factory_('link text');
        141
        142
        143/**
        144 * Locates an elements by evaluating a
        145 * {@link webdriver.WebDriver#executeScript JavaScript expression}.
        146 * The result of this expression must be an element or list of elements.
        147 *
        148 * @param {!(string|Function)} script The script to execute.
        149 * @param {...*} var_args The arguments to pass to the script.
        150 * @return {function(!webdriver.WebDriver): !webdriver.promise.Promise} A new,
        151 * JavaScript-based locator function.
        152 */
        153webdriver.By.js = function(script, var_args) {
        154 var args = goog.array.slice(arguments, 0);
        155 return function(driver) {
        156 return driver.executeScript.apply(driver, args);
        157 };
        158};
        159
        160
        161/**
        162 * Locates elements whose {@code name} attribute has the given value.
        163 *
        164 * @param {string} name The name attribute to search for.
        165 * @return {!webdriver.Locator} The new locator.
        166 */
        167webdriver.By.name = webdriver.Locator.factory_('name');
        168
        169
        170/**
        171 * Locates link elements whose {@link webdriver.WebElement#getText visible
        172 * text} contains the given substring.
        173 *
        174 * @param {string} text The substring to check for in a link's visible text.
        175 * @return {!webdriver.Locator} The new locator.
        176 */
        177webdriver.By.partialLinkText = webdriver.Locator.factory_(
        178 'partial link text');
        179
        180
        181/**
        182 * Locates elements with a given tag name. The returned locator is
        183 * equivalent to using the {@code getElementsByTagName} DOM function.
        184 *
        185 * @param {string} text The substring to check for in a link's visible text.
        186 * @return {!webdriver.Locator} The new locator.
        187 * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html
        188 */
        189webdriver.By.tagName = webdriver.Locator.factory_('tag name');
        190
        191
        192/**
        193 * Locates elements matching a XPath selector. Care should be taken when
        194 * using an XPath selector with a {@link webdriver.WebElement} as WebDriver
        195 * will respect the context in the specified in the selector. For example,
        196 * given the selector {@code "//div"}, WebDriver will search from the
        197 * document root regardless of whether the locator was used with a
        198 * WebElement.
        199 *
        200 * @param {string} xpath The XPath selector to use.
        201 * @return {!webdriver.Locator} The new locator.
        202 * @see http://www.w3.org/TR/xpath/
        203 */
        204webdriver.By.xpath = webdriver.Locator.factory_('xpath');
        205
        206
        207/**
        208 * Maps {@link webdriver.By.Hash} keys to the appropriate factory function.
        209 * @type {!Object.<string, function(string): !(Function|webdriver.Locator)>}
        210 * @const
        211 */
        212webdriver.Locator.Strategy = {
        213 'className': webdriver.By.className,
        214 'css': webdriver.By.css,
        215 'id': webdriver.By.id,
        216 'js': webdriver.By.js,
        217 'linkText': webdriver.By.linkText,
        218 'name': webdriver.By.name,
        219 'partialLinkText': webdriver.By.partialLinkText,
        220 'tagName': webdriver.By.tagName,
        221 'xpath': webdriver.By.xpath
        222};
        223
        224
        225/**
        226 * Verifies that a {@code value} is a valid locator to use for searching for
        227 * elements on the page.
        228 *
        229 * @param {*} value The value to check is a valid locator.
        230 * @return {!(webdriver.Locator|Function)} A valid locator object or function.
        231 * @throws {TypeError} If the given value is an invalid locator.
        232 */
        233webdriver.Locator.checkLocator = function(value) {
        234 if (goog.isFunction(value) || value instanceof webdriver.Locator) {
        235 return value;
        236 }
        237 for (var key in value) {
        238 if (value.hasOwnProperty(key) &&
        239 webdriver.Locator.Strategy.hasOwnProperty(key)) {
        240 return webdriver.Locator.Strategy[key](value[key]);
        241 }
        242 }
        243 throw new TypeError('Invalid locator');
        244};
        245
        246
        247
        248/** @override */
        249webdriver.Locator.prototype.toString = function() {
        250 return 'By.' + this.using.replace(/ ([a-z])/g, function(all, match) {
        251 return match.toUpperCase();
        252 }) + '(' + goog.string.quote(this.value) + ')';
        253};
        \ No newline at end of file +locators.js

        lib/webdriver/locators.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Factory methods for the supported locator strategies.
        20 */
        21
        22goog.provide('webdriver.By');
        23goog.provide('webdriver.Locator');
        24goog.provide('webdriver.Locator.Strategy');
        25
        26goog.require('goog.array');
        27goog.require('goog.object');
        28goog.require('goog.string');
        29
        30
        31
        32/**
        33 * An element locator.
        34 * @param {string} using The type of strategy to use for this locator.
        35 * @param {string} value The search target of this locator.
        36 * @constructor
        37 */
        38webdriver.Locator = function(using, value) {
        39
        40 /**
        41 * The search strategy to use when searching for an element.
        42 * @type {string}
        43 */
        44 this.using = using;
        45
        46 /**
        47 * The search target for this locator.
        48 * @type {string}
        49 */
        50 this.value = value;
        51};
        52
        53
        54/**
        55 * Creates a factory function for a {@link webdriver.Locator}.
        56 * @param {string} type The type of locator for the factory.
        57 * @return {function(string): !webdriver.Locator} The new factory function.
        58 * @private
        59 */
        60webdriver.Locator.factory_ = function(type) {
        61 return function(value) {
        62 return new webdriver.Locator(type, value);
        63 };
        64};
        65
        66
        67/**
        68 * A collection of factory functions for creating {@link webdriver.Locator}
        69 * instances.
        70 */
        71webdriver.By = {};
        72// Exported to the global scope for legacy reasons.
        73goog.exportSymbol('By', webdriver.By);
        74
        75
        76/**
        77 * Short-hand expressions for the primary element locator strategies.
        78 * For example the following two statements are equivalent:
        79 *
        80 * var e1 = driver.findElement(webdriver.By.id('foo'));
        81 * var e2 = driver.findElement({id: 'foo'});
        82 *
        83 * Care should be taken when using JavaScript minifiers (such as the
        84 * Closure compiler), as locator hashes will always be parsed using
        85 * the un-obfuscated properties listed.
        86 *
        87 * @typedef {(
        88 * {className: string}|
        89 * {css: string}|
        90 * {id: string}|
        91 * {js: string}|
        92 * {linkText: string}|
        93 * {name: string}|
        94 * {partialLinkText: string}|
        95 * {tagName: string}|
        96 * {xpath: string})}
        97 */
        98webdriver.By.Hash;
        99
        100
        101/**
        102 * Locates elements that have a specific class name. The returned locator
        103 * is equivalent to searching for elements with the CSS selector ".clazz".
        104 *
        105 * @param {string} className The class name to search for.
        106 * @return {!webdriver.Locator} The new locator.
        107 * @see http://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
        108 * @see http://www.w3.org/TR/CSS2/selector.html#class-html
        109 */
        110webdriver.By.className = webdriver.Locator.factory_('class name');
        111
        112
        113/**
        114 * Locates elements using a CSS selector. For browsers that do not support
        115 * CSS selectors, WebDriver implementations may return an
        116 * {@linkplain bot.Error.State.INVALID_SELECTOR invalid selector} error. An
        117 * implementation may, however, emulate the CSS selector API.
        118 *
        119 * @param {string} selector The CSS selector to use.
        120 * @return {!webdriver.Locator} The new locator.
        121 * @see http://www.w3.org/TR/CSS2/selector.html
        122 */
        123webdriver.By.css = webdriver.Locator.factory_('css selector');
        124
        125
        126/**
        127 * Locates an element by its ID.
        128 *
        129 * @param {string} id The ID to search for.
        130 * @return {!webdriver.Locator} The new locator.
        131 */
        132webdriver.By.id = webdriver.Locator.factory_('id');
        133
        134
        135/**
        136 * Locates link elements whose {@linkplain webdriver.WebElement#getText visible
        137 * text} matches the given string.
        138 *
        139 * @param {string} text The link text to search for.
        140 * @return {!webdriver.Locator} The new locator.
        141 */
        142webdriver.By.linkText = webdriver.Locator.factory_('link text');
        143
        144
        145/**
        146 * Locates an elements by evaluating a
        147 * {@linkplain webdriver.WebDriver#executeScript JavaScript expression}.
        148 * The result of this expression must be an element or list of elements.
        149 *
        150 * @param {!(string|Function)} script The script to execute.
        151 * @param {...*} var_args The arguments to pass to the script.
        152 * @return {function(!webdriver.WebDriver): !webdriver.promise.Promise} A new,
        153 * JavaScript-based locator function.
        154 */
        155webdriver.By.js = function(script, var_args) {
        156 var args = goog.array.slice(arguments, 0);
        157 return function(driver) {
        158 return driver.executeScript.apply(driver, args);
        159 };
        160};
        161
        162
        163/**
        164 * Locates elements whose {@code name} attribute has the given value.
        165 *
        166 * @param {string} name The name attribute to search for.
        167 * @return {!webdriver.Locator} The new locator.
        168 */
        169webdriver.By.name = webdriver.Locator.factory_('name');
        170
        171
        172/**
        173 * Locates link elements whose {@linkplain webdriver.WebElement#getText visible
        174 * text} contains the given substring.
        175 *
        176 * @param {string} text The substring to check for in a link's visible text.
        177 * @return {!webdriver.Locator} The new locator.
        178 */
        179webdriver.By.partialLinkText = webdriver.Locator.factory_(
        180 'partial link text');
        181
        182
        183/**
        184 * Locates elements with a given tag name. The returned locator is
        185 * equivalent to using the
        186 * [getElementsByTagName](https://developer.mozilla.org/en-US/docs/Web/API/Element.getElementsByTagName)
        187 * DOM function.
        188 *
        189 * @param {string} text The substring to check for in a link's visible text.
        190 * @return {!webdriver.Locator} The new locator.
        191 * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html
        192 */
        193webdriver.By.tagName = webdriver.Locator.factory_('tag name');
        194
        195
        196/**
        197 * Locates elements matching a XPath selector. Care should be taken when
        198 * using an XPath selector with a {@link webdriver.WebElement} as WebDriver
        199 * will respect the context in the specified in the selector. For example,
        200 * given the selector {@code "//div"}, WebDriver will search from the
        201 * document root regardless of whether the locator was used with a
        202 * WebElement.
        203 *
        204 * @param {string} xpath The XPath selector to use.
        205 * @return {!webdriver.Locator} The new locator.
        206 * @see http://www.w3.org/TR/xpath/
        207 */
        208webdriver.By.xpath = webdriver.Locator.factory_('xpath');
        209
        210
        211/**
        212 * Maps {@link webdriver.By.Hash} keys to the appropriate factory function.
        213 * @type {!Object.<string, function(string): !(Function|webdriver.Locator)>}
        214 * @const
        215 */
        216webdriver.Locator.Strategy = {
        217 'className': webdriver.By.className,
        218 'css': webdriver.By.css,
        219 'id': webdriver.By.id,
        220 'js': webdriver.By.js,
        221 'linkText': webdriver.By.linkText,
        222 'name': webdriver.By.name,
        223 'partialLinkText': webdriver.By.partialLinkText,
        224 'tagName': webdriver.By.tagName,
        225 'xpath': webdriver.By.xpath
        226};
        227
        228
        229/**
        230 * Verifies that a {@code value} is a valid locator to use for searching for
        231 * elements on the page.
        232 *
        233 * @param {*} value The value to check is a valid locator.
        234 * @return {!(webdriver.Locator|Function)} A valid locator object or function.
        235 * @throws {TypeError} If the given value is an invalid locator.
        236 */
        237webdriver.Locator.checkLocator = function(value) {
        238 if (goog.isFunction(value) || value instanceof webdriver.Locator) {
        239 return value;
        240 }
        241 for (var key in value) {
        242 if (value.hasOwnProperty(key) &&
        243 webdriver.Locator.Strategy.hasOwnProperty(key)) {
        244 return webdriver.Locator.Strategy[key](value[key]);
        245 }
        246 }
        247 throw new TypeError('Invalid locator');
        248};
        249
        250
        251
        252/** @override */
        253webdriver.Locator.prototype.toString = function() {
        254 return 'By.' + this.using.replace(/ ([a-z])/g, function(all, match) {
        255 return match.toUpperCase();
        256 }) + '(' + goog.string.quote(this.value) + ')';
        257};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/logging.js.src.html b/docs/source/lib/webdriver/logging.js.src.html index b9153c0..5948e57 100644 --- a/docs/source/lib/webdriver/logging.js.src.html +++ b/docs/source/lib/webdriver/logging.js.src.html @@ -1 +1 @@ -logging.js

        lib/webdriver/logging.js

        1// Copyright 2013 Selenium comitters
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16goog.provide('webdriver.logging');
        17goog.provide('webdriver.logging.Preferences');
        18
        19goog.require('goog.object');
        20
        21
        22/**
        23 * Log level names from WebDriver's JSON wire protocol.
        24 * @enum {string}
        25 */
        26webdriver.logging.LevelName = {
        27 ALL: 'ALL',
        28 DEBUG: 'DEBUG',
        29 INFO: 'INFO',
        30 WARNING: 'WARNING',
        31 SEVERE: 'SEVERE',
        32 OFF: 'OFF'
        33};
        34
        35
        36/**
        37 * Logging levels.
        38 * @enum {{value: number, name: webdriver.logging.LevelName}}
        39 */
        40webdriver.logging.Level = {
        41 ALL: {value: Number.MIN_VALUE, name: webdriver.logging.LevelName.ALL},
        42 DEBUG: {value: 700, name: webdriver.logging.LevelName.DEBUG},
        43 INFO: {value: 800, name: webdriver.logging.LevelName.INFO},
        44 WARNING: {value: 900, name: webdriver.logging.LevelName.WARNING},
        45 SEVERE: {value: 1000, name: webdriver.logging.LevelName.SEVERE},
        46 OFF: {value: Number.MAX_VALUE, name: webdriver.logging.LevelName.OFF}
        47};
        48
        49
        50/**
        51 * Converts a level name or value to a {@link webdriver.logging.Level} value.
        52 * If the name/value is not recognized, {@link webdriver.logging.Level.ALL}
        53 * will be returned.
        54 * @param {(number|string)} nameOrValue The log level name, or value, to
        55 * convert .
        56 * @return {!webdriver.logging.Level} The converted level.
        57 */
        58webdriver.logging.getLevel = function(nameOrValue) {
        59 var predicate = goog.isString(nameOrValue) ?
        60 function(val) { return val.name === nameOrValue; } :
        61 function(val) { return val.value === nameOrValue; };
        62
        63 return goog.object.findValue(webdriver.logging.Level, predicate) ||
        64 webdriver.logging.Level.ALL;
        65};
        66
        67
        68/**
        69 * Common log types.
        70 * @enum {string}
        71 */
        72webdriver.logging.Type = {
        73 /** Logs originating from the browser. */
        74 BROWSER: 'browser',
        75 /** Logs from a WebDriver client. */
        76 CLIENT: 'client',
        77 /** Logs from a WebDriver implementation. */
        78 DRIVER: 'driver',
        79 /** Logs related to performance. */
        80 PERFORMANCE: 'performance',
        81 /** Logs from the remote server. */
        82 SERVER: 'server'
        83};
        84
        85
        86/**
        87 * A hash describing log preferences.
        88 * @typedef {Object.<webdriver.logging.Type, webdriver.logging.LevelName>}
        89 */
        90webdriver.logging.Preferences;
        91
        92
        93/**
        94 * A single log entry.
        95 * @param {(!webdriver.logging.Level|string)} level The entry level.
        96 * @param {string} message The log message.
        97 * @param {number=} opt_timestamp The time this entry was generated, in
        98 * milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the
        99 * current time will be used.
        100 * @param {string=} opt_type The log type, if known.
        101 * @constructor
        102 */
        103webdriver.logging.Entry = function(level, message, opt_timestamp, opt_type) {
        104
        105 /** @type {!webdriver.logging.Level} */
        106 this.level =
        107 goog.isString(level) ? webdriver.logging.getLevel(level) : level;
        108
        109 /** @type {string} */
        110 this.message = message;
        111
        112 /** @type {number} */
        113 this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now();
        114
        115 /** @type {string} */
        116 this.type = opt_type || '';
        117};
        118
        119
        120/**
        121 * @return {{level: string, message: string, timestamp: number,
        122 * type: string}} The JSON representation of this entry.
        123 */
        124webdriver.logging.Entry.prototype.toJSON = function() {
        125 return {
        126 'level': this.level.name,
        127 'message': this.message,
        128 'timestamp': this.timestamp,
        129 'type': this.type
        130 };
        131};
        132
        133
        134/**
        135 * Converts a {@link goog.debug.LogRecord} into a
        136 * {@link webdriver.logging.Entry}.
        137 * @param {!goog.debug.LogRecord} logRecord The record to convert.
        138 * @param {string=} opt_type The log type.
        139 * @return {!webdriver.logging.Entry} The converted entry.
        140 */
        141webdriver.logging.Entry.fromClosureLogRecord = function(logRecord, opt_type) {
        142 var closureLevel = logRecord.getLevel();
        143 var level = webdriver.logging.Level.SEVERE;
        144
        145 if (closureLevel.value <= webdriver.logging.Level.DEBUG.value) {
        146 level = webdriver.logging.Level.DEBUG;
        147 } else if (closureLevel.value <= webdriver.logging.Level.INFO.value) {
        148 level = webdriver.logging.Level.INFO;
        149 } else if (closureLevel.value <= webdriver.logging.Level.WARNING.value) {
        150 level = webdriver.logging.Level.WARNING;
        151 }
        152
        153 return new webdriver.logging.Entry(
        154 level,
        155 '[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(),
        156 logRecord.getMillis(),
        157 opt_type);
        158};
        \ No newline at end of file +logging.js

        lib/webdriver/logging.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines WebDriver's logging system. The logging system is
        20 * broken into major components: local and remote logging.
        21 *
        22 * The local logging API, which is anchored by the
        23 * {@link webdriver.logging.Logger Logger} class, is similar to Java's
        24 * logging API. Loggers, retrieved by {@link webdriver.logging.getLogger}, use
        25 * hierarchical, dot-delimited namespaces
        26 * (e.g. "" > "webdriver" > "webdriver.logging"). Recorded log messages are
        27 * represented by the {@link webdriver.logging.LogRecord LogRecord} class. You
        28 * can capture log records by
        29 * {@linkplain webdriver.logging.Logger#addHandler attaching} a handler function
        30 * to the desired logger. For convenience, you can quickly enable logging to
        31 * the console by simply calling
        32 * {@link webdriver.logging.installConsoleHandler()}.
        33 *
        34 * The [remote logging API](https://github.com/SeleniumHQ/selenium/wiki/Logging)
        35 * allows you to retrieve logs from a remote WebDriver server. This API uses the
        36 * {@link Preferences} class to define desired log levels prior to create a
        37 * WebDriver session:
        38 *
        39 * var prefs = new webdriver.logging.Preferences();
        40 * prefs.setLevel(webdriver.logging.Type.BROWSER,
        41 * webdriver.logging.Level.DEBUG);
        42 *
        43 * var caps = webdriver.Capabilities.chrome();
        44 * caps.setLoggingPrefs(prefs);
        45 * // ...
        46 *
        47 * Remote log entries are represented by the {@link Entry} class and may be
        48 * retrieved via {@link webdriver.WebDriver.Logs}:
        49 *
        50 * driver.manage().logs().get(webdriver.logging.Type.BROWSER)
        51 * .then(function(entries) {
        52 * entries.forEach(function(entry) {
        53 * console.log('[%s] %s', entry.level.name, entry.message);
        54 * });
        55 * });
        56 *
        57 * **NOTE:** Only a few browsers support the remote logging API (notably
        58 * Firefox and Chrome). Firefox supports basic logging functionality, while
        59 * Chrome exposes robust
        60 * [performance logging](https://sites.google.com/a/chromium.org/chromedriver/logging)
        61 * options. Remote logging is still considered a non-standard feature, and the
        62 * APIs exposed by this module for it are non-frozen. Once logging is officially
        63 * defined by the [W3C WebDriver spec](http://www.w3.org/TR/webdriver/), this
        64 * module will be updated to use a consistent API for local and remote logging.
        65 */
        66
        67goog.module('webdriver.logging');
        68goog.module.declareLegacyNamespace();
        69
        70var LogManager = goog.require('goog.debug.LogManager');
        71var LogRecord = goog.require('goog.debug.LogRecord');
        72var Logger = goog.require('goog.debug.Logger');
        73var Objects = goog.require('goog.object');
        74var padNumber = goog.require('goog.string').padNumber;
        75
        76
        77/** @const */
        78exports.LogRecord = LogRecord;
        79
        80
        81/** @const */
        82exports.Logger = Logger;
        83
        84
        85/** @const */
        86exports.Level = Logger.Level;
        87
        88
        89/**
        90 * DEBUG is a message level for debugging messages and has the same log level
        91 * as the {@link Logger.Level.CONFIG} message level.
        92 * @const {!Logger.Level}
        93 */
        94Logger.Level.DEBUG = new Logger.Level('DEBUG', Logger.Level.CONFIG.value);
        95
        96
        97/**
        98 * Finds a named logger.
        99 *
        100 * @param {string=} opt_name The dot-delimited logger name, such as
        101 * "webdriver.logging.Logger". Defaults to the name of the root logger.
        102 * @return {!Logger} The named logger.
        103 */
        104function getLogger(opt_name) {
        105 return LogManager.getLogger(opt_name || Logger.ROOT_LOGGER_NAME);
        106}
        107exports.getLogger = getLogger;
        108
        109
        110/**
        111 * Logs all messages to the Console API.
        112 */
        113function consoleHandler(record) {
        114 if (typeof console === 'undefined') {
        115 return;
        116 }
        117 record = /** @type {!LogRecord} */(record);
        118 var timestamp = new Date(record.getMillis());
        119 var msg =
        120 '[' + timestamp.getUTCFullYear() + '-' +
        121 padNumber(timestamp.getUTCMonth() + 1, 2) + '-' +
        122 padNumber(timestamp.getUTCDate(), 2) + 'T' +
        123 padNumber(timestamp.getUTCHours(), 2) + ':' +
        124 padNumber(timestamp.getUTCMinutes(), 2) + ':' +
        125 padNumber(timestamp.getUTCSeconds(), 2) + 'Z]' +
        126 '[' + record.getLevel().name + ']' +
        127 '[' + record.getLoggerName() + '] ' +
        128 record.getMessage();
        129
        130 var level = record.getLevel().value;
        131 if (level >= Logger.Level.SEVERE.value) {
        132 console.error(msg);
        133 } else if (level >= Logger.Level.WARNING.value) {
        134 console.warn(msg);
        135 } else {
        136 console.log(msg);
        137 }
        138}
        139
        140
        141/**
        142 * Adds the console handler to the given logger. The console handler will log
        143 * all messages using the JavaScript Console API.
        144 *
        145 * @param {Logger=} opt_logger The logger to add the handler to; defaults
        146 * to the root logger.
        147 * @see exports.removeConsoleHandler
        148 */
        149exports.addConsoleHandler = function(opt_logger) {
        150 var logger = opt_logger || getLogger();
        151 logger.addHandler(consoleHandler);
        152};
        153
        154
        155/**
        156 * Installs the console log handler on the root logger.
        157 * @see exports.addConsoleHandler
        158 */
        159exports.installConsoleHandler = function() {
        160 exports.addConsoleHandler();
        161};
        162
        163
        164/**
        165 * Removes the console log handler from the given logger.
        166 *
        167 * @param {Logger=} opt_logger The logger to remove the handler from; defaults
        168 * to the root logger.
        169 * @see exports.addConsoleHandler
        170 */
        171exports.removeConsoleHandler = function(opt_logger) {
        172 var logger = opt_logger || getLogger();
        173 logger.removeHandler(consoleHandler);
        174};
        175
        176
        177/**
        178 * Converts a level name or value to a {@link webdriver.logging.Level} value.
        179 * If the name/value is not recognized, {@link webdriver.logging.Level.ALL}
        180 * will be returned.
        181 * @param {(number|string)} nameOrValue The log level name, or value, to
        182 * convert .
        183 * @return {!Logger.Level} The converted level.
        184 */
        185function getLevel(nameOrValue) {
        186 // DEBUG is not a predefined Closure log level, but maps to CONFIG. Since
        187 // DEBUG is a predefined level for the WebDriver protocol, we prefer it over
        188 // CONFIG.
        189 if ('DEBUG' === nameOrValue || Logger.Level.DEBUG.value === nameOrValue) {
        190 return Logger.Level.DEBUG;
        191 } else if (goog.isString(nameOrValue)) {
        192 return Logger.Level.getPredefinedLevel(/** @type {string} */(nameOrValue))
        193 || Logger.Level.ALL;
        194 } else {
        195 return Logger.Level.getPredefinedLevelByValue(
        196 /** @type {number} */(nameOrValue)) || Logger.Level.ALL;
        197 }
        198}
        199exports.getLevel = getLevel;
        200
        201
        202/**
        203 * Normalizes a {@link Logger.Level} to one of the distinct values recognized
        204 * by WebDriver's wire protocol.
        205 * @param {!Logger.Level} level The input level.
        206 * @return {!Logger.Level} The normalized level.
        207 */
        208function normalizeLevel(level) {
        209 if (level.value <= Logger.Level.ALL.value) { // ALL is 0.
        210 return Logger.Level.ALL;
        211
        212 } else if (level.value === Logger.Level.OFF.value) { // OFF is Infinity
        213 return Logger.Level.OFF;
        214
        215 } else if (level.value < Logger.Level.INFO.value) {
        216 return Logger.Level.DEBUG;
        217
        218 } else if (level.value < Logger.Level.WARNING.value) {
        219 return Logger.Level.INFO;
        220
        221 } else if (level.value < Logger.Level.SEVERE.value) {
        222 return Logger.Level.WARNING;
        223
        224 } else {
        225 return Logger.Level.SEVERE;
        226 }
        227}
        228
        229
        230/**
        231 * Common log types.
        232 * @enum {string}
        233 */
        234var Type = {
        235 /** Logs originating from the browser. */
        236 BROWSER: 'browser',
        237 /** Logs from a WebDriver client. */
        238 CLIENT: 'client',
        239 /** Logs from a WebDriver implementation. */
        240 DRIVER: 'driver',
        241 /** Logs related to performance. */
        242 PERFORMANCE: 'performance',
        243 /** Logs from the remote server. */
        244 SERVER: 'server'
        245};
        246exports.Type = Type;
        247
        248
        249/**
        250 * Describes the log preferences for a WebDriver session.
        251 * @final
        252 */
        253var Preferences = goog.defineClass(null, {
        254 /** @constructor */
        255 constructor: function() {
        256 /** @private {!Object.<string, Logger.Level>} */
        257 this.prefs_ = {};
        258 },
        259
        260 /**
        261 * Sets the desired logging level for a particular log type.
        262 * @param {(string|Type)} type The log type.
        263 * @param {!Logger.Level} level The desired log level.
        264 */
        265 setLevel: function(type, level) {
        266 this.prefs_[type] = normalizeLevel(level);
        267 },
        268
        269 /**
        270 * Converts this instance to its JSON representation.
        271 * @return {!Object.<string, string>} The JSON representation of this set of
        272 * preferences.
        273 */
        274 toJSON: function() {
        275 var obj = {};
        276 for (var type in this.prefs_) {
        277 if (this.prefs_.hasOwnProperty(type)) {
        278 obj[type] = this.prefs_[type].name;
        279 }
        280 }
        281 return obj;
        282 }
        283});
        284exports.Preferences = Preferences;
        285
        286
        287/**
        288 * A single log entry recorded by a WebDriver component, such as a remote
        289 * WebDriver server.
        290 * @final
        291 */
        292var Entry = goog.defineClass(null, {
        293 /**
        294 * @param {(!Logger.Level|string)} level The entry level.
        295 * @param {string} message The log message.
        296 * @param {number=} opt_timestamp The time this entry was generated, in
        297 * milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the
        298 * current time will be used.
        299 * @param {string=} opt_type The log type, if known.
        300 * @constructor
        301 */
        302 constructor: function(level, message, opt_timestamp, opt_type) {
        303
        304 /** @type {!Logger.Level} */
        305 this.level = goog.isString(level) ? getLevel(level) : level;
        306
        307 /** @type {string} */
        308 this.message = message;
        309
        310 /** @type {number} */
        311 this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now();
        312
        313 /** @type {string} */
        314 this.type = opt_type || '';
        315 },
        316
        317 statics: {
        318 /**
        319 * Converts a {@link goog.debug.LogRecord} into a
        320 * {@link webdriver.logging.Entry}.
        321 * @param {!goog.debug.LogRecord} logRecord The record to convert.
        322 * @param {string=} opt_type The log type.
        323 * @return {!Entry} The converted entry.
        324 */
        325 fromClosureLogRecord: function(logRecord, opt_type) {
        326 return new Entry(
        327 normalizeLevel(/** @type {!Logger.Level} */(logRecord.getLevel())),
        328 '[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(),
        329 logRecord.getMillis(),
        330 opt_type);
        331 }
        332 },
        333
        334 /**
        335 * @return {{level: string, message: string, timestamp: number,
        336 * type: string}} The JSON representation of this entry.
        337 */
        338 toJSON: function() {
        339 return {
        340 'level': this.level.name,
        341 'message': this.message,
        342 'timestamp': this.timestamp,
        343 'type': this.type
        344 };
        345 }
        346});
        347exports.Entry = Entry;
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/process.js.src.html b/docs/source/lib/webdriver/process.js.src.html index e0d858c..3d53566 100644 --- a/docs/source/lib/webdriver/process.js.src.html +++ b/docs/source/lib/webdriver/process.js.src.html @@ -1 +1 @@ -process.js

        lib/webdriver/process.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides access to the current process' environment variables.
        17 * When running in node, this is simply a wrapper for {@code process.env}.
        18 * When running in a browser, environment variables are loaded by parsing the
        19 * current URL's query string. Variables that have more than one variable will
        20 * be initialized to the JSON representation of the array of all values,
        21 * otherwise the variable will be initialized to a sole string value. If a
        22 * variable does not have any values, but is nonetheless present in the query
        23 * string, it will be initialized to an empty string.
        24 * After the initial parsing, environment variables must be queried and set
        25 * through the API defined in this file.
        26 */
        27
        28goog.provide('webdriver.process');
        29
        30goog.require('goog.Uri');
        31goog.require('goog.array');
        32goog.require('goog.json');
        33
        34
        35/**
        36 * @return {boolean} Whether the current process is Node's native process
        37 * object.
        38 */
        39webdriver.process.isNative = function() {
        40 return webdriver.process.IS_NATIVE_PROCESS_;
        41};
        42
        43
        44/**
        45 * Queries for a named environment variable.
        46 * @param {string} name The name of the environment variable to look up.
        47 * @param {string=} opt_default The default value if the named variable is not
        48 * defined.
        49 * @return {string} The queried environment variable.
        50 */
        51webdriver.process.getEnv = function(name, opt_default) {
        52 var value = webdriver.process.PROCESS_.env[name];
        53 return goog.isDefAndNotNull(value) ? value : opt_default;
        54};
        55
        56
        57/**
        58 * Sets an environment value. If the new value is either null or undefined, the
        59 * environment variable will be cleared.
        60 * @param {string} name The value to set.
        61 * @param {*} value The new value; will be coerced to a string.
        62 */
        63webdriver.process.setEnv = function(name, value) {
        64 webdriver.process.PROCESS_.env[name] =
        65 goog.isDefAndNotNull(value) ? value + '' : null;
        66};
        67
        68
        69/**
        70 * Whether the current environment is using Node's native process object.
        71 * @private {boolean}
        72 * @const
        73 */
        74webdriver.process.IS_NATIVE_PROCESS_ = typeof process !== 'undefined';
        75
        76
        77/**
        78 * Initializes a process object for use in a browser window.
        79 * @param {!Window=} opt_window The window object to initialize the process
        80 * from; if not specified, will default to the current window. Should only
        81 * be set for unit testing.
        82 * @return {!Object} The new process object.
        83 * @private
        84 */
        85webdriver.process.initBrowserProcess_ = function(opt_window) {
        86 var process = {'env': {}};
        87
        88 var win = opt_window;
        89 if (!win && typeof window != 'undefined') {
        90 win = window;
        91 }
        92
        93 // Initialize the global error handler.
        94 if (win) {
        95 // Initialize the environment variable map by parsing the current URL query
        96 // string.
        97 if (win.location) {
        98 var data = new goog.Uri(win.location).getQueryData();
        99 goog.array.forEach(data.getKeys(), function(key) {
        100 var values = data.getValues(key);
        101 process.env[key] = values.length == 0 ? '' :
        102 values.length == 1 ? values[0] :
        103 goog.json.serialize(values);
        104 });
        105 }
        106 }
        107
        108 return process;
        109};
        110
        111
        112/**
        113 * The global process object to use. Will either be Node's global
        114 * {@code process} object, or an approximation of it for use in a browser
        115 * environment.
        116 * @private {!Object}
        117 * @const
        118 */
        119webdriver.process.PROCESS_ = webdriver.process.IS_NATIVE_PROCESS_ ? process :
        120 webdriver.process.initBrowserProcess_();
        \ No newline at end of file +process.js

        lib/webdriver/process.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Provides access to the current process' environment variables.
        17 * When running in node, this is simply a wrapper for {@code process.env}.
        18 * When running in a browser, environment variables are loaded by parsing the
        19 * current URL's query string. Variables that have more than one variable will
        20 * be initialized to the JSON representation of the array of all values,
        21 * otherwise the variable will be initialized to a sole string value. If a
        22 * variable does not have any values, but is nonetheless present in the query
        23 * string, it will be initialized to an empty string.
        24 * After the initial parsing, environment variables must be queried and set
        25 * through the API defined in this file.
        26 */
        27
        28goog.provide('webdriver.process');
        29
        30goog.require('goog.Uri');
        31goog.require('goog.array');
        32goog.require('goog.json');
        33
        34
        35/**
        36 * @return {boolean} Whether the current process is Node's native process
        37 * object.
        38 */
        39webdriver.process.isNative = function() {
        40 return webdriver.process.IS_NATIVE_PROCESS_;
        41};
        42
        43
        44/**
        45 * Queries for a named environment variable.
        46 * @param {string} name The name of the environment variable to look up.
        47 * @param {string=} opt_default The default value if the named variable is not
        48 * defined.
        49 * @return {string} The queried environment variable.
        50 */
        51webdriver.process.getEnv = function(name, opt_default) {
        52 var value = webdriver.process.PROCESS_.env[name];
        53 return goog.isDefAndNotNull(value) ? value : opt_default;
        54};
        55
        56
        57/**
        58 * Sets an environment value. If the new value is either null or undefined, the
        59 * environment variable will be cleared.
        60 * @param {string} name The value to set.
        61 * @param {*} value The new value; will be coerced to a string.
        62 */
        63webdriver.process.setEnv = function(name, value) {
        64 webdriver.process.PROCESS_.env[name] =
        65 goog.isDefAndNotNull(value) ? value + '' : null;
        66};
        67
        68
        69/**
        70 * Whether the current environment is using Node's native process object.
        71 * @private {boolean}
        72 * @const
        73 */
        74webdriver.process.IS_NATIVE_PROCESS_ = typeof process !== 'undefined';
        75
        76
        77/**
        78 * Initializes a process object for use in a browser window.
        79 * @param {!Window=} opt_window The window object to initialize the process
        80 * from; if not specified, will default to the current window. Should only
        81 * be set for unit testing.
        82 * @return {!Object} The new process object.
        83 * @private
        84 */
        85webdriver.process.initBrowserProcess_ = function(opt_window) {
        86 var process = {'env': {}};
        87
        88 var win = opt_window;
        89 if (!win && typeof window != 'undefined') {
        90 win = window;
        91 }
        92
        93 // Initialize the global error handler.
        94 if (win) {
        95 // Initialize the environment variable map by parsing the current URL query
        96 // string.
        97 if (win.location) {
        98 var data = new goog.Uri(win.location).getQueryData();
        99 goog.array.forEach(data.getKeys(), function(key) {
        100 var values = data.getValues(key);
        101 process.env[key] = values.length == 0 ? '' :
        102 values.length == 1 ? values[0] :
        103 goog.json.serialize(values);
        104 });
        105 }
        106 }
        107
        108 return process;
        109};
        110
        111
        112/**
        113 * The global process object to use. Will either be Node's global
        114 * {@code process} object, or an approximation of it for use in a browser
        115 * environment.
        116 * @private {!Object}
        117 * @const
        118 */
        119webdriver.process.PROCESS_ = webdriver.process.IS_NATIVE_PROCESS_ ? process :
        120 webdriver.process.initBrowserProcess_();
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/promise.js.src.html b/docs/source/lib/webdriver/promise.js.src.html index 3867895..dee8fdb 100644 --- a/docs/source/lib/webdriver/promise.js.src.html +++ b/docs/source/lib/webdriver/promise.js.src.html @@ -1 +1 @@ -promise.js

        lib/webdriver/promise.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @license Portions of this code are from the Dojo toolkit, received under the
        17 * BSD License:
        18 * Redistribution and use in source and binary forms, with or without
        19 * modification, are permitted provided that the following conditions are met:
        20 *
        21 * * Redistributions of source code must retain the above copyright notice,
        22 * this list of conditions and the following disclaimer.
        23 * * Redistributions in binary form must reproduce the above copyright notice,
        24 * this list of conditions and the following disclaimer in the documentation
        25 * and/or other materials provided with the distribution.
        26 * * Neither the name of the Dojo Foundation nor the names of its contributors
        27 * may be used to endorse or promote products derived from this software
        28 * without specific prior written permission.
        29 *
        30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        31 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
        33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
        34 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
        35 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
        36 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
        37 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
        38 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        39 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        40 * POSSIBILITY OF SUCH DAMAGE.
        41 */
        42
        43/**
        44 * @fileoverview A promise implementation based on the CommonJS promise/A and
        45 * promise/B proposals. For more information, see
        46 * http://wiki.commonjs.org/wiki/Promises.
        47 */
        48
        49goog.provide('webdriver.promise');
        50goog.provide('webdriver.promise.ControlFlow');
        51goog.provide('webdriver.promise.ControlFlow.Timer');
        52goog.provide('webdriver.promise.Deferred');
        53goog.provide('webdriver.promise.Promise');
        54
        55goog.require('goog.array');
        56goog.require('goog.debug.Error');
        57goog.require('goog.object');
        58goog.require('webdriver.EventEmitter');
        59goog.require('webdriver.stacktrace.Snapshot');
        60
        61
        62
        63/**
        64 * Represents the eventual value of a completed operation. Each promise may be
        65 * in one of three states: pending, resolved, or rejected. Each promise starts
        66 * in the pending state and may make a single transition to either a
        67 * fulfilled or failed state.
        68 *
        69 * <p/>This class is based on the Promise/A proposal from CommonJS. Additional
        70 * functions are provided for API compatibility with Dojo Deferred objects.
        71 *
        72 * @constructor
        73 * @see http://wiki.commonjs.org/wiki/Promises/A
        74 */
        75webdriver.promise.Promise = function() {
        76};
        77
        78
        79/**
        80 * Cancels the computation of this promise's value, rejecting the promise in the
        81 * process.
        82 * @param {*} reason The reason this promise is being cancelled. If not an
        83 * {@code Error}, one will be created using the value's string
        84 * representation.
        85 */
        86webdriver.promise.Promise.prototype.cancel = function(reason) {
        87 throw new TypeError('Unimplemented function: "cancel"');
        88};
        89
        90
        91/** @return {boolean} Whether this promise's value is still being computed. */
        92webdriver.promise.Promise.prototype.isPending = function() {
        93 throw new TypeError('Unimplemented function: "isPending"');
        94};
        95
        96
        97/**
        98 * Registers listeners for when this instance is resolved. This function most
        99 * overridden by subtypes.
        100 *
        101 * @param {Function=} opt_callback The function to call if this promise is
        102 * successfully resolved. The function should expect a single argument: the
        103 * promise's resolved value.
        104 * @param {Function=} opt_errback The function to call if this promise is
        105 * rejected. The function should expect a single argument: the rejection
        106 * reason.
        107 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        108 * with the result of the invoked callback.
        109 */
        110webdriver.promise.Promise.prototype.then = function(
        111 opt_callback, opt_errback) {
        112 throw new TypeError('Unimplemented function: "then"');
        113};
        114
        115
        116/**
        117 * Registers a listener for when this promise is rejected. This is synonymous
        118 * with the {@code catch} clause in a synchronous API:
        119 * <pre><code>
        120 * // Synchronous API:
        121 * try {
        122 * doSynchronousWork();
        123 * } catch (ex) {
        124 * console.error(ex);
        125 * }
        126 *
        127 * // Asynchronous promise API:
        128 * doAsynchronousWork().thenCatch(function(ex) {
        129 * console.error(ex);
        130 * });
        131 * </code></pre>
        132 *
        133 * @param {!Function} errback The function to call if this promise is
        134 * rejected. The function should expect a single argument: the rejection
        135 * reason.
        136 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        137 * with the result of the invoked callback.
        138 */
        139webdriver.promise.Promise.prototype.thenCatch = function(errback) {
        140 return this.then(null, errback);
        141};
        142
        143
        144/**
        145 * Registers a listener to invoke when this promise is resolved, regardless
        146 * of whether the promise's value was successfully computed. This function
        147 * is synonymous with the {@code finally} clause in a synchronous API:
        148 * <pre><code>
        149 * // Synchronous API:
        150 * try {
        151 * doSynchronousWork();
        152 * } finally {
        153 * cleanUp();
        154 * }
        155 *
        156 * // Asynchronous promise API:
        157 * doAsynchronousWork().thenFinally(cleanUp);
        158 * </code></pre>
        159 *
        160 * <b>Note:</b> similar to the {@code finally} clause, if the registered
        161 * callback returns a rejected promise or throws an error, it will silently
        162 * replace the rejection error (if any) from this promise:
        163 * <pre><code>
        164 * try {
        165 * throw Error('one');
        166 * } finally {
        167 * throw Error('two'); // Hides Error: one
        168 * }
        169 *
        170 * webdriver.promise.rejected(Error('one'))
        171 * .thenFinally(function() {
        172 * throw Error('two'); // Hides Error: one
        173 * });
        174 * </code></pre>
        175 *
        176 *
        177 * @param callback
        178 * @returns {!webdriver.promise.Promise}
        179 */
        180webdriver.promise.Promise.prototype.thenFinally = function(callback) {
        181 return this.then(callback, callback);
        182};
        183
        184
        185/**
        186 * Registers a function to be invoked when this promise is successfully
        187 * resolved. This function is provided for backwards compatibility with the
        188 * Dojo Deferred API.
        189 *
        190 * @param {Function} callback The function to call if this promise is
        191 * successfully resolved. The function should expect a single argument: the
        192 * promise's resolved value.
        193 * @param {!Object=} opt_self The object which |this| should refer to when the
        194 * function is invoked.
        195 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        196 * with the result of the invoked callback.
        197 * @deprecated Use {@link #then()} instead.
        198 */
        199webdriver.promise.Promise.prototype.addCallback = function(callback, opt_self) {
        200 return this.then(goog.bind(callback, opt_self));
        201};
        202
        203
        204/**
        205 * Registers a function to be invoked when this promise is rejected.
        206 * This function is provided for backwards compatibility with the
        207 * Dojo Deferred API.
        208 *
        209 * @param {Function} errback The function to call if this promise is
        210 * rejected. The function should expect a single argument: the rejection
        211 * reason.
        212 * @param {!Object=} opt_self The object which |this| should refer to when the
        213 * function is invoked.
        214 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        215 * with the result of the invoked callback.
        216 * @deprecated Use {@link #thenCatch()} instead.
        217 */
        218webdriver.promise.Promise.prototype.addErrback = function(errback, opt_self) {
        219 return this.thenCatch(goog.bind(errback, opt_self));
        220};
        221
        222
        223/**
        224 * Registers a function to be invoked when this promise is either rejected or
        225 * resolved. This function is provided for backwards compatibility with the
        226 * Dojo Deferred API.
        227 *
        228 * @param {Function} callback The function to call when this promise is
        229 * either resolved or rejected. The function should expect a single
        230 * argument: the resolved value or rejection error.
        231 * @param {!Object=} opt_self The object which |this| should refer to when the
        232 * function is invoked.
        233 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        234 * with the result of the invoked callback.
        235 * @deprecated Use {@link #thenFinally()} instead.
        236 */
        237webdriver.promise.Promise.prototype.addBoth = function(callback, opt_self) {
        238 return this.thenFinally(goog.bind(callback, opt_self));
        239};
        240
        241
        242/**
        243 * An alias for {@code webdriver.promise.Promise.prototype.then} that permits
        244 * the scope of the invoked function to be specified. This function is provided
        245 * for backwards compatibility with the Dojo Deferred API.
        246 *
        247 * @param {Function} callback The function to call if this promise is
        248 * successfully resolved. The function should expect a single argument: the
        249 * promise's resolved value.
        250 * @param {Function} errback The function to call if this promise is
        251 * rejected. The function should expect a single argument: the rejection
        252 * reason.
        253 * @param {!Object=} opt_self The object which |this| should refer to when the
        254 * function is invoked.
        255 * @return {!webdriver.promise.Promise} A new promise which will be resolved
        256 * with the result of the invoked callback.
        257 * @deprecated Use {@link #then()} instead.
        258 */
        259webdriver.promise.Promise.prototype.addCallbacks = function(
        260 callback, errback, opt_self) {
        261 return this.then(goog.bind(callback, opt_self),
        262 goog.bind(errback, opt_self));
        263};
        264
        265
        266
        267/**
        268 * Represents a value that will be resolved at some point in the future. This
        269 * class represents the protected "producer" half of a Promise - each Deferred
        270 * has a {@code promise} property that may be returned to consumers for
        271 * registering callbacks, reserving the ability to resolve the deferred to the
        272 * producer.
        273 *
        274 * <p>If this Deferred is rejected and there are no listeners registered before
        275 * the next turn of the event loop, the rejection will be passed to the
        276 * {@link webdriver.promise.ControlFlow} as an unhandled failure.
        277 *
        278 * <p>If this Deferred is cancelled, the cancellation reason will be forward to
        279 * the Deferred's canceller function (if provided). The canceller may return a
        280 * truth-y value to override the reason provided for rejection.
        281 *
        282 * @param {Function=} opt_canceller Function to call when cancelling the
        283 * computation of this instance's value.
        284 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow
        285 * this instance was created under. This should only be provided during
        286 * unit tests.
        287 * @constructor
        288 * @extends {webdriver.promise.Promise}
        289 */
        290webdriver.promise.Deferred = function(opt_canceller, opt_flow) {
        291 /* NOTE: This class's implementation diverges from the prototypical style
        292 * used in the rest of the atoms library. This was done intentionally to
        293 * protect the internal Deferred state from consumers, as outlined by
        294 * http://wiki.commonjs.org/wiki/Promises
        295 */
        296 goog.base(this);
        297
        298 var flow = opt_flow || webdriver.promise.controlFlow();
        299
        300 /**
        301 * The listeners registered with this Deferred. Each element in the list will
        302 * be a 3-tuple of the callback function, errback function, and the
        303 * corresponding deferred object.
        304 * @type {!Array.<!webdriver.promise.Deferred.Listener_>}
        305 */
        306 var listeners = [];
        307
        308 /**
        309 * Whether this Deferred's resolution was ever handled by a listener.
        310 * If the Deferred is rejected and its value is not handled by a listener
        311 * before the next turn of the event loop, the error will be passed to the
        312 * global error handler.
        313 * @type {boolean}
        314 */
        315 var handled = false;
        316
        317 /**
        318 * Key for the timeout used to delay reproting an unhandled rejection to the
        319 * parent {@link webdriver.promise.ControlFlow}.
        320 * @type {?number}
        321 */
        322 var pendingRejectionKey = null;
        323
        324 /**
        325 * This Deferred's current state.
        326 * @type {!webdriver.promise.Deferred.State_}
        327 */
        328 var state = webdriver.promise.Deferred.State_.PENDING;
        329
        330 /**
        331 * This Deferred's resolved value; set when the state transitions from
        332 * {@code webdriver.promise.Deferred.State_.PENDING}.
        333 * @type {*}
        334 */
        335 var value;
        336
        337 /** @return {boolean} Whether this promise's value is still pending. */
        338 function isPending() {
        339 return state == webdriver.promise.Deferred.State_.PENDING;
        340 }
        341
        342 /**
        343 * Removes all of the listeners previously registered on this deferred.
        344 * @throws {Error} If this deferred has already been resolved.
        345 */
        346 function removeAll() {
        347 listeners = [];
        348 }
        349
        350 /**
        351 * Resolves this deferred. If the new value is a promise, this function will
        352 * wait for it to be resolved before notifying the registered listeners.
        353 * @param {!webdriver.promise.Deferred.State_} newState The deferred's new
        354 * state.
        355 * @param {*} newValue The deferred's new value.
        356 */
        357 function resolve(newState, newValue) {
        358 if (webdriver.promise.Deferred.State_.PENDING !== state) {
        359 return;
        360 }
        361
        362 state = webdriver.promise.Deferred.State_.BLOCKED;
        363
        364 if (webdriver.promise.isPromise(newValue) && newValue !== self) {
        365 var onFulfill = goog.partial(notifyAll, newState);
        366 var onReject = goog.partial(
        367 notifyAll, webdriver.promise.Deferred.State_.REJECTED);
        368 if (newValue instanceof webdriver.promise.Deferred) {
        369 newValue.then(onFulfill, onReject);
        370 } else {
        371 webdriver.promise.asap(newValue, onFulfill, onReject);
        372 }
        373
        374 } else {
        375 notifyAll(newState, newValue);
        376 }
        377 }
        378
        379 /**
        380 * Notifies all of the listeners registered with this Deferred that its state
        381 * has changed.
        382 * @param {!webdriver.promise.Deferred.State_} newState The deferred's new
        383 * state.
        384 * @param {*} newValue The deferred's new value.
        385 */
        386 function notifyAll(newState, newValue) {
        387 if (newState === webdriver.promise.Deferred.State_.REJECTED &&
        388 // We cannot check instanceof Error since the object may have been
        389 // created in a different JS context.
        390 goog.isObject(newValue) && goog.isString(newValue.message)) {
        391 newValue = flow.annotateError(/** @type {!Error} */(newValue));
        392 }
        393
        394 state = newState;
        395 value = newValue;
        396 while (listeners.length) {
        397 notify(listeners.shift());
        398 }
        399
        400 if (!handled && state == webdriver.promise.Deferred.State_.REJECTED) {
        401 pendingRejectionKey = propagateError(value);
        402 }
        403 }
        404
        405 /**
        406 * Propagates an unhandled rejection to the parent ControlFlow in a
        407 * future turn of the JavaScript event loop.
        408 * @param {*} error The error value to report.
        409 * @return {number} The key for the registered timeout.
        410 */
        411 function propagateError(error) {
        412 flow.pendingRejections_ += 1;
        413 return flow.timer.setTimeout(function() {
        414 flow.pendingRejections_ -= 1;
        415 flow.abortFrame_(error);
        416 }, 0);
        417 }
        418
        419 /**
        420 * Notifies a single listener of this Deferred's change in state.
        421 * @param {!webdriver.promise.Deferred.Listener_} listener The listener to
        422 * notify.
        423 */
        424 function notify(listener) {
        425 var func = state == webdriver.promise.Deferred.State_.RESOLVED ?
        426 listener.callback : listener.errback;
        427 if (func) {
        428 flow.runInNewFrame_(goog.partial(func, value),
        429 listener.fulfill, listener.reject);
        430 } else if (state == webdriver.promise.Deferred.State_.REJECTED) {
        431 listener.reject(value);
        432 } else {
        433 listener.fulfill(value);
        434 }
        435 }
        436
        437 /**
        438 * The consumer promise for this instance. Provides protected access to the
        439 * callback registering functions.
        440 * @type {!webdriver.promise.Promise}
        441 */
        442 var promise = new webdriver.promise.Promise();
        443
        444 /**
        445 * Registers a callback on this Deferred.
        446 * @param {Function=} opt_callback The callback.
        447 * @param {Function=} opt_errback The errback.
        448 * @return {!webdriver.promise.Promise} A new promise representing the result
        449 * of the callback.
        450 * @see webdriver.promise.Promise#then
        451 */
        452 function then(opt_callback, opt_errback) {
        453 // Avoid unnecessary allocations if we weren't given any callback functions.
        454 if (!opt_callback && !opt_errback) {
        455 return promise;
        456 }
        457
        458 // The moment a listener is registered, we consider this deferred to be
        459 // handled; the callback must handle any rejection errors.
        460 handled = true;
        461 if (pendingRejectionKey) {
        462 flow.pendingRejections_ -= 1;
        463 flow.timer.clearTimeout(pendingRejectionKey);
        464 }
        465
        466 var deferred = new webdriver.promise.Deferred(cancel, flow);
        467 var listener = {
        468 callback: opt_callback,
        469 errback: opt_errback,
        470 fulfill: deferred.fulfill,
        471 reject: deferred.reject
        472 };
        473
        474 if (state == webdriver.promise.Deferred.State_.PENDING ||
        475 state == webdriver.promise.Deferred.State_.BLOCKED) {
        476 listeners.push(listener);
        477 } else {
        478 notify(listener);
        479 }
        480
        481 return deferred.promise;
        482 }
        483
        484 var self = this;
        485
        486 /**
        487 * Resolves this promise with the given value. If the value is itself a
        488 * promise and not a reference to this deferred, this instance will wait for
        489 * it before resolving.
        490 * @param {*=} opt_value The resolved value.
        491 */
        492 function fulfill(opt_value) {
        493 resolve(webdriver.promise.Deferred.State_.RESOLVED, opt_value);
        494 }
        495
        496 /**
        497 * Rejects this promise. If the error is itself a promise, this instance will
        498 * be chained to it and be rejected with the error's resolved value.
        499 * @param {*=} opt_error The rejection reason, typically either a
        500 * {@code Error} or a {@code string}.
        501 */
        502 function reject(opt_error) {
        503 resolve(webdriver.promise.Deferred.State_.REJECTED, opt_error);
        504 }
        505
        506 /**
        507 * Attempts to cancel the computation of this instance's value. This attempt
        508 * will silently fail if this instance has already resolved.
        509 * @param {*=} opt_reason The reason for cancelling this promise.
        510 */
        511 function cancel(opt_reason) {
        512 if (!isPending()) {
        513 return;
        514 }
        515
        516 if (opt_canceller) {
        517 opt_reason = opt_canceller(opt_reason) || opt_reason;
        518 }
        519
        520 reject(opt_reason);
        521 }
        522
        523 this.promise = promise;
        524 this.promise.then = this.then = then;
        525 this.promise.cancel = this.cancel = cancel;
        526 this.promise.isPending = this.isPending = isPending;
        527 this.fulfill = fulfill;
        528 this.reject = this.errback = reject;
        529
        530 // Only expose this function to our internal classes.
        531 // TODO: find a cleaner way of handling this.
        532 if (this instanceof webdriver.promise.Task_) {
        533 this.removeAll = removeAll;
        534 }
        535
        536 // Export symbols necessary for the contract on this object to work in
        537 // compiled mode.
        538 goog.exportProperty(this, 'then', this.then);
        539 goog.exportProperty(this, 'cancel', cancel);
        540 goog.exportProperty(this, 'fulfill', fulfill);
        541 goog.exportProperty(this, 'reject', reject);
        542 goog.exportProperty(this, 'isPending', isPending);
        543 goog.exportProperty(this, 'promise', this.promise);
        544 goog.exportProperty(this.promise, 'then', this.then);
        545 goog.exportProperty(this.promise, 'cancel', cancel);
        546 goog.exportProperty(this.promise, 'isPending', isPending);
        547};
        548goog.inherits(webdriver.promise.Deferred, webdriver.promise.Promise);
        549
        550
        551/**
        552 * Type definition for a listener registered on a Deferred object.
        553 * @typedef {{callback:(Function|undefined),
        554 * errback:(Function|undefined),
        555 * fulfill: function(*), reject: function(*)}}
        556 * @private
        557 */
        558webdriver.promise.Deferred.Listener_;
        559
        560
        561/**
        562 * The three states a {@link webdriver.promise.Deferred} object may be in.
        563 * @enum {number}
        564 * @private
        565 */
        566webdriver.promise.Deferred.State_ = {
        567 REJECTED: -1,
        568 PENDING: 0,
        569 BLOCKED: 1,
        570 RESOLVED: 2
        571};
        572
        573
        574/**
        575 * Tests if a value is an Error-like object. This is more than an straight
        576 * instanceof check since the value may originate from another context.
        577 * @param {*} value The value to test.
        578 * @return {boolean} Whether the value is an error.
        579 * @private
        580 */
        581webdriver.promise.isError_ = function(value) {
        582 return value instanceof Error ||
        583 goog.isObject(value) &&
        584 (Object.prototype.toString.call(value) === '[object Error]' ||
        585 // A special test for goog.testing.JsUnitException.
        586 value.isJsUnitException);
        587
        588};
        589
        590
        591/**
        592 * Determines whether a {@code value} should be treated as a promise.
        593 * Any object whose "then" property is a function will be considered a promise.
        594 *
        595 * @param {*} value The value to test.
        596 * @return {boolean} Whether the value is a promise.
        597 */
        598webdriver.promise.isPromise = function(value) {
        599 return !!value && goog.isObject(value) &&
        600 // Use array notation so the Closure compiler does not obfuscate away our
        601 // contract.
        602 goog.isFunction(value['then']);
        603};
        604
        605
        606/**
        607 * Creates a promise that will be resolved at a set time in the future.
        608 * @param {number} ms The amount of time, in milliseconds, to wait before
        609 * resolving the promise.
        610 * @return {!webdriver.promise.Promise} The promise.
        611 */
        612webdriver.promise.delayed = function(ms) {
        613 var timer = webdriver.promise.controlFlow().timer;
        614 var key;
        615 var deferred = new webdriver.promise.Deferred(function() {
        616 timer.clearTimeout(key);
        617 });
        618 key = timer.setTimeout(deferred.fulfill, ms);
        619 return deferred.promise;
        620};
        621
        622
        623/**
        624 * Creates a new deferred object.
        625 * @param {Function=} opt_canceller Function to call when cancelling the
        626 * computation of this instance's value.
        627 * @return {!webdriver.promise.Deferred} The new deferred object.
        628 */
        629webdriver.promise.defer = function(opt_canceller) {
        630 return new webdriver.promise.Deferred(opt_canceller);
        631};
        632
        633
        634/**
        635 * Creates a promise that has been resolved with the given value.
        636 * @param {*=} opt_value The resolved value.
        637 * @return {!webdriver.promise.Promise} The resolved promise.
        638 */
        639webdriver.promise.fulfilled = function(opt_value) {
        640 if (opt_value instanceof webdriver.promise.Promise) {
        641 return opt_value;
        642 }
        643 var deferred = new webdriver.promise.Deferred();
        644 deferred.fulfill(opt_value);
        645 return deferred.promise;
        646};
        647
        648
        649/**
        650 * Creates a promise that has been rejected with the given reason.
        651 * @param {*=} opt_reason The rejection reason; may be any value, but is
        652 * usually an Error or a string.
        653 * @return {!webdriver.promise.Promise} The rejected promise.
        654 */
        655webdriver.promise.rejected = function(opt_reason) {
        656 var deferred = new webdriver.promise.Deferred();
        657 deferred.reject(opt_reason);
        658 return deferred.promise;
        659};
        660
        661
        662/**
        663 * Wraps a function that is assumed to be a node-style callback as its final
        664 * argument. This callback takes two arguments: an error value (which will be
        665 * null if the call succeeded), and the success value as the second argument.
        666 * If the call fails, the returned promise will be rejected, otherwise it will
        667 * be resolved with the result.
        668 * @param {!Function} fn The function to wrap.
        669 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        670 * result of the provided function's callback.
        671 */
        672webdriver.promise.checkedNodeCall = function(fn) {
        673 var deferred = new webdriver.promise.Deferred(function() {
        674 throw Error('This Deferred may not be cancelled');
        675 });
        676 try {
        677 fn(function(error, value) {
        678 error ? deferred.reject(error) : deferred.fulfill(value);
        679 });
        680 } catch (ex) {
        681 deferred.reject(ex);
        682 }
        683 return deferred.promise;
        684};
        685
        686
        687/**
        688 * Registers an observer on a promised {@code value}, returning a new promise
        689 * that will be resolved when the value is. If {@code value} is not a promise,
        690 * then the return promise will be immediately resolved.
        691 * @param {*} value The value to observe.
        692 * @param {Function=} opt_callback The function to call when the value is
        693 * resolved successfully.
        694 * @param {Function=} opt_errback The function to call when the value is
        695 * rejected.
        696 * @return {!webdriver.promise.Promise} A new promise.
        697 */
        698webdriver.promise.when = function(value, opt_callback, opt_errback) {
        699 if (value instanceof webdriver.promise.Promise) {
        700 return value.then(opt_callback, opt_errback);
        701 }
        702
        703 var deferred = new webdriver.promise.Deferred();
        704
        705 webdriver.promise.asap(value, deferred.fulfill, deferred.reject);
        706
        707 return deferred.then(opt_callback, opt_errback);
        708};
        709
        710
        711/**
        712 * Invokes the appropriate callback function as soon as a promised
        713 * {@code value} is resolved. This function is similar to
        714 * {@link webdriver.promise.when}, except it does not return a new promise.
        715 * @param {*} value The value to observe.
        716 * @param {Function} callback The function to call when the value is
        717 * resolved successfully.
        718 * @param {Function=} opt_errback The function to call when the value is
        719 * rejected.
        720 */
        721webdriver.promise.asap = function(value, callback, opt_errback) {
        722 if (webdriver.promise.isPromise(value)) {
        723 value.then(callback, opt_errback);
        724
        725 // Maybe a Dojo-like deferred object?
        726 } else if (!!value && goog.isObject(value) &&
        727 goog.isFunction(value.addCallbacks)) {
        728 value.addCallbacks(callback, opt_errback);
        729
        730 // A raw value, return a resolved promise.
        731 } else if (callback) {
        732 callback(value);
        733 }
        734};
        735
        736
        737/**
        738 * Given an array of promises, will return a promise that will be fulfilled
        739 * with the fulfillment values of the input array's values. If any of the
        740 * input array's promises are rejected, the returned promise will be rejected
        741 * with the same reason.
        742 *
        743 * @param {!Array.<(T|!webdriver.promise.Promise.<T>)>} arr An array of
        744 * promises to wait on.
        745 * @return {!webdriver.promise.Promise.<!Array.<T>>} A promise that is
        746 * fulfilled with an array containing the fulfilled values of the
        747 * input array, or rejected with the same reason as the first
        748 * rejected value.
        749 * @template T
        750 */
        751webdriver.promise.all = function(arr) {
        752 var n = arr.length;
        753 if (!n) {
        754 return webdriver.promise.fulfilled([]);
        755 }
        756
        757 var toFulfill = n;
        758 var result = webdriver.promise.defer();
        759 var values = [];
        760
        761 var onFulfill = function(index, value) {
        762 values[index] = value;
        763 toFulfill--;
        764 if (toFulfill == 0) {
        765 result.fulfill(values);
        766 }
        767 };
        768
        769 for (var i = 0; i < n; ++i) {
        770 webdriver.promise.asap(
        771 arr[i], goog.partial(onFulfill, i), result.reject);
        772 }
        773
        774 return result.promise;
        775};
        776
        777
        778/**
        779 * Calls a function for each element in an array and inserts the result into a
        780 * new array, which is used as the fulfillment value of the promise returned
        781 * by this function.
        782 *
        783 * <p>If the return value of the mapping function is a promise, this function
        784 * will wait for it to be fulfilled before inserting it into the new array.
        785 *
        786 * <p>If the mapping function throws or returns a rejected promise, the
        787 * promise returned by this function will be rejected with the same reason.
        788 * Only the first failure will be reported; all subsequent errors will be
        789 * silently ignored.
        790 *
        791 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The
        792 * array to iterator over, or a promise that will resolve to said array.
        793 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): ?} fn The
        794 * function to call for each element in the array. This function should
        795 * expect three arguments (the element, the index, and the array itself.
        796 * @param {SELF=} opt_self The object to be used as the value of 'this' within
        797 * {@code fn}.
        798 * @template TYPE, SELF
        799 */
        800webdriver.promise.map = function(arr, fn, opt_self) {
        801 return webdriver.promise.when(arr, function(arr) {
        802 var result = goog.array.map(arr, fn, opt_self);
        803 return webdriver.promise.all(result);
        804 });
        805};
        806
        807
        808/**
        809 * Calls a function for each element in an array, and if the function returns
        810 * true adds the element to a new array.
        811 *
        812 * <p>If the return value of the filter function is a promise, this function
        813 * will wait for it to be fulfilled before determining whether to insert the
        814 * element into the new array.
        815 *
        816 * <p>If the filter function throws or returns a rejected promise, the promise
        817 * returned by this function will be rejected with the same reason. Only the
        818 * first failure will be reported; all subsequent errors will be silently
        819 * ignored.
        820 *
        821 * @param {!(Array.<TYPE>|webdriver.promise.Promise.<!Array.<TYPE>>)} arr The
        822 * array to iterator over, or a promise that will resolve to said array.
        823 * @param {function(this: SELF, TYPE, number, !Array.<TYPE>): (
        824 * boolean|webdriver.promise.Promise.<boolean>)} fn The function
        825 * to call for each element in the array.
        826 * @param {SELF=} opt_self The object to be used as the value of 'this' within
        827 * {@code fn}.
        828 * @template TYPE, SELF
        829 */
        830webdriver.promise.filter = function(arr, fn, opt_self) {
        831 return webdriver.promise.when(arr, function(arr) {
        832 var originalValues = goog.array.clone(arr);
        833 return webdriver.promise.map(arr, fn, opt_self).then(function(include) {
        834 return goog.array.filter(originalValues, function(value, index) {
        835 return include[index];
        836 });
        837 });
        838 });
        839};
        840
        841
        842/**
        843 * Returns a promise that will be resolved with the input value in a
        844 * fully-resolved state. If the value is an array, each element will be fully
        845 * resolved. Likewise, if the value is an object, all keys will be fully
        846 * resolved. In both cases, all nested arrays and objects will also be
        847 * fully resolved. All fields are resolved in place; the returned promise will
        848 * resolve on {@code value} and not a copy.
        849 *
        850 * Warning: This function makes no checks against objects that contain
        851 * cyclical references:
        852 * <pre><code>
        853 * var value = {};
        854 * value['self'] = value;
        855 * webdriver.promise.fullyResolved(value); // Stack overflow.
        856 * </code></pre>
        857 *
        858 * @param {*} value The value to fully resolve.
        859 * @return {!webdriver.promise.Promise} A promise for a fully resolved version
        860 * of the input value.
        861 */
        862webdriver.promise.fullyResolved = function(value) {
        863 if (webdriver.promise.isPromise(value)) {
        864 return webdriver.promise.when(value, webdriver.promise.fullyResolveValue_);
        865 }
        866 return webdriver.promise.fullyResolveValue_(value);
        867};
        868
        869
        870/**
        871 * @param {*} value The value to fully resolve. If a promise, assumed to
        872 * already be resolved.
        873 * @return {!webdriver.promise.Promise} A promise for a fully resolved version
        874 * of the input value.
        875 * @private
        876 */
        877webdriver.promise.fullyResolveValue_ = function(value) {
        878 switch (goog.typeOf(value)) {
        879 case 'array':
        880 return webdriver.promise.fullyResolveKeys_(
        881 /** @type {!Array} */ (value));
        882
        883 case 'object':
        884 if (webdriver.promise.isPromise(value)) {
        885 // We get here when the original input value is a promise that
        886 // resolves to itself. When the user provides us with such a promise,
        887 // trust that it counts as a "fully resolved" value and return it.
        888 // Of course, since it's already a promise, we can just return it
        889 // to the user instead of wrapping it in another promise.
        890 return /** @type {!webdriver.promise.Promise} */ (value);
        891 }
        892
        893 if (goog.isNumber(value.nodeType) &&
        894 goog.isObject(value.ownerDocument) &&
        895 goog.isNumber(value.ownerDocument.nodeType)) {
        896 // DOM node; return early to avoid infinite recursion. Should we
        897 // only support objects with a certain level of nesting?
        898 return webdriver.promise.fulfilled(value);
        899 }
        900
        901 return webdriver.promise.fullyResolveKeys_(
        902 /** @type {!Object} */ (value));
        903
        904 default: // boolean, function, null, number, string, undefined
        905 return webdriver.promise.fulfilled(value);
        906 }
        907};
        908
        909
        910/**
        911 * @param {!(Array|Object)} obj the object to resolve.
        912 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        913 * input object once all of its values have been fully resolved.
        914 * @private
        915 */
        916webdriver.promise.fullyResolveKeys_ = function(obj) {
        917 var isArray = goog.isArray(obj);
        918 var numKeys = isArray ? obj.length : goog.object.getCount(obj);
        919 if (!numKeys) {
        920 return webdriver.promise.fulfilled(obj);
        921 }
        922
        923 var numResolved = 0;
        924 var deferred = new webdriver.promise.Deferred();
        925
        926 // In pre-IE9, goog.array.forEach will not iterate properly over arrays
        927 // containing undefined values because "index in array" returns false
        928 // when array[index] === undefined (even for x = [undefined, 1]). To get
        929 // around this, we need to use our own forEach implementation.
        930 // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be
        931 // reproduced in IE9 by changing the browser/document modes, it requires an
        932 // actual pre-IE9 browser. Yay, IE!
        933 var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {
        934 var n = arr.length;
        935 for (var i = 0; i < n; ++i) {
        936 fn.call(null, arr[i], i, arr);
        937 }
        938 };
        939
        940 forEachKey(obj, function(partialValue, key) {
        941 var type = goog.typeOf(partialValue);
        942 if (type != 'array' && type != 'object') {
        943 maybeResolveValue();
        944 return;
        945 }
        946
        947 webdriver.promise.fullyResolved(partialValue).then(
        948 function(resolvedValue) {
        949 obj[key] = resolvedValue;
        950 maybeResolveValue();
        951 },
        952 deferred.reject);
        953 });
        954
        955 return deferred.promise;
        956
        957 function maybeResolveValue() {
        958 if (++numResolved == numKeys) {
        959 deferred.fulfill(obj);
        960 }
        961 }
        962};
        963
        964
        965//////////////////////////////////////////////////////////////////////////////
        966//
        967// webdriver.promise.ControlFlow
        968//
        969//////////////////////////////////////////////////////////////////////////////
        970
        971
        972
        973/**
        974 * Handles the execution of scheduled tasks, each of which may be an
        975 * asynchronous operation. The control flow will ensure tasks are executed in
        976 * the ordered scheduled, starting each task only once those before it have
        977 * completed.
        978 *
        979 * <p>Each task scheduled within this flow may return a
        980 * {@link webdriver.promise.Promise} to indicate it is an asynchronous
        981 * operation. The ControlFlow will wait for such promises to be resolved before
        982 * marking the task as completed.
        983 *
        984 * <p>Tasks and each callback registered on a {@link webdriver.promise.Deferred}
        985 * will be run in their own ControlFlow frame. Any tasks scheduled within a
        986 * frame will have priority over previously scheduled tasks. Furthermore, if
        987 * any of the tasks in the frame fails, the remainder of the tasks in that frame
        988 * will be discarded and the failure will be propagated to the user through the
        989 * callback/task's promised result.
        990 *
        991 * <p>Each time a ControlFlow empties its task queue, it will fire an
        992 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Conversely,
        993 * whenever the flow terminates due to an unhandled error, it will remove all
        994 * remaining tasks in its queue and fire an
        995 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event. If
        996 * there are no listeners registered with the flow, the error will be
        997 * rethrown to the global error handler.
        998 *
        999 * @param {webdriver.promise.ControlFlow.Timer=} opt_timer The timer object
        1000 * to use. Should only be set for testing.
        1001 * @constructor
        1002 * @extends {webdriver.EventEmitter}
        1003 */
        1004webdriver.promise.ControlFlow = function(opt_timer) {
        1005 webdriver.EventEmitter.call(this);
        1006
        1007 /**
        1008 * The timer used by this instance.
        1009 * @type {webdriver.promise.ControlFlow.Timer}
        1010 */
        1011 this.timer = opt_timer || webdriver.promise.ControlFlow.defaultTimer;
        1012
        1013 /**
        1014 * A list of recent tasks. Each time a new task is started, or a frame is
        1015 * completed, the previously recorded task is removed from this list. If
        1016 * there are multiple tasks, task N+1 is considered a sub-task of task
        1017 * N.
        1018 * @private {!Array.<!webdriver.promise.Task_>}
        1019 */
        1020 this.history_ = [];
        1021};
        1022goog.inherits(webdriver.promise.ControlFlow, webdriver.EventEmitter);
        1023
        1024
        1025/**
        1026 * @typedef {{clearInterval: function(number),
        1027 * clearTimeout: function(number),
        1028 * setInterval: function(!Function, number): number,
        1029 * setTimeout: function(!Function, number): number}}
        1030 */
        1031webdriver.promise.ControlFlow.Timer;
        1032
        1033
        1034/**
        1035 * The default timer object, which uses the global timer functions.
        1036 * @type {webdriver.promise.ControlFlow.Timer}
        1037 */
        1038webdriver.promise.ControlFlow.defaultTimer = (function() {
        1039 // The default timer functions may be defined as free variables for the
        1040 // current context, so do not reference them using "window" or
        1041 // "goog.global". Also, we must invoke them in a closure, and not using
        1042 // bind(), so we do not get "TypeError: Illegal invocation" (WebKit) or
        1043 // "Invalid calling object" (IE) errors.
        1044 return {
        1045 clearInterval: wrap(clearInterval),
        1046 clearTimeout: wrap(clearTimeout),
        1047 setInterval: wrap(setInterval),
        1048 setTimeout: wrap(setTimeout)
        1049 };
        1050
        1051 function wrap(fn) {
        1052 return function() {
        1053 // Cannot use .call() or .apply() since we do not know which variable
        1054 // the function is bound to, and using the wrong one will generate
        1055 // an error.
        1056 return fn(arguments[0], arguments[1]);
        1057 };
        1058 }
        1059})();
        1060
        1061
        1062/**
        1063 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.
        1064 * @enum {string}
        1065 */
        1066webdriver.promise.ControlFlow.EventType = {
        1067
        1068 /** Emitted when all tasks have been successfully executed. */
        1069 IDLE: 'idle',
        1070
        1071 /** Emitted whenever a new task has been scheduled. */
        1072 SCHEDULE_TASK: 'scheduleTask',
        1073
        1074 /**
        1075 * Emitted whenever a control flow aborts due to an unhandled promise
        1076 * rejection. This event will be emitted along with the offending rejection
        1077 * reason. Upon emitting this event, the control flow will empty its task
        1078 * queue and revert to its initial state.
        1079 */
        1080 UNCAUGHT_EXCEPTION: 'uncaughtException'
        1081};
        1082
        1083
        1084/**
        1085 * How often, in milliseconds, the event loop should run.
        1086 * @type {number}
        1087 * @const
        1088 */
        1089webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY = 10;
        1090
        1091
        1092/**
        1093 * Tracks the active execution frame for this instance. Lazily initialized
        1094 * when the first task is scheduled.
        1095 * @private {webdriver.promise.Frame_}
        1096 */
        1097webdriver.promise.ControlFlow.prototype.activeFrame_ = null;
        1098
        1099
        1100/**
        1101 * A reference to the frame in which new tasks should be scheduled. If
        1102 * {@code null}, tasks will be scheduled within the active frame. When forcing
        1103 * a function to run in the context of a new frame, this pointer is used to
        1104 * ensure tasks are scheduled within the newly created frame, even though it
        1105 * won't be active yet.
        1106 * @private {webdriver.promise.Frame_}
        1107 * @see {#runInNewFrame_}
        1108 */
        1109webdriver.promise.ControlFlow.prototype.schedulingFrame_ = null;
        1110
        1111
        1112/**
        1113 * Timeout ID set when the flow is about to shutdown without any errors
        1114 * being detected. Upon shutting down, the flow will emit an
        1115 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Idle events
        1116 * always follow a brief timeout in order to catch latent errors from the last
        1117 * completed task. If this task had a callback registered, but no errback, and
        1118 * the task fails, the unhandled failure would not be reported by the promise
        1119 * system until the next turn of the event loop:
        1120 *
        1121 * // Schedule 1 task that fails.
        1122 * var result = webriver.promise.controlFlow().schedule('example',
        1123 * function() { return webdriver.promise.rejected('failed'); });
        1124 * // Set a callback on the result. This delays reporting the unhandled
        1125 * // failure for 1 turn of the event loop.
        1126 * result.then(goog.nullFunction);
        1127 *
        1128 * @private {?number}
        1129 */
        1130webdriver.promise.ControlFlow.prototype.shutdownId_ = null;
        1131
        1132
        1133/**
        1134 * Interval ID for this instance's event loop.
        1135 * @private {?number}
        1136 */
        1137webdriver.promise.ControlFlow.prototype.eventLoopId_ = null;
        1138
        1139
        1140/**
        1141 * The number of "pending" promise rejections.
        1142 *
        1143 * <p>Each time a promise is rejected and is not handled by a listener, it will
        1144 * schedule a 0-based timeout to check if it is still unrejected in the next
        1145 * turn of the JS-event loop. This allows listeners to attach to, and handle,
        1146 * the rejected promise at any point in same turn of the event loop that the
        1147 * promise was rejected.
        1148 *
        1149 * <p>When this flow's own event loop triggers, it will not run if there
        1150 * are any outstanding promise rejections. This allows unhandled promises to
        1151 * be reported before a new task is started, ensuring the error is reported to
        1152 * the current task queue.
        1153 *
        1154 * @private {number}
        1155 */
        1156webdriver.promise.ControlFlow.prototype.pendingRejections_ = 0;
        1157
        1158
        1159/**
        1160 * The number of aborted frames since the last time a task was executed or a
        1161 * frame completed successfully.
        1162 * @private {number}
        1163 */
        1164webdriver.promise.ControlFlow.prototype.numAbortedFrames_ = 0;
        1165
        1166
        1167/**
        1168 * Resets this instance, clearing its queue and removing all event listeners.
        1169 */
        1170webdriver.promise.ControlFlow.prototype.reset = function() {
        1171 this.activeFrame_ = null;
        1172 this.clearHistory();
        1173 this.removeAllListeners();
        1174 this.cancelShutdown_();
        1175 this.cancelEventLoop_();
        1176};
        1177
        1178
        1179/**
        1180 * Returns a summary of the recent task activity for this instance. This
        1181 * includes the most recently completed task, as well as any parent tasks. In
        1182 * the returned summary, the task at index N is considered a sub-task of the
        1183 * task at index N+1.
        1184 * @return {!Array.<string>} A summary of this instance's recent task
        1185 * activity.
        1186 */
        1187webdriver.promise.ControlFlow.prototype.getHistory = function() {
        1188 var pendingTasks = [];
        1189 var currentFrame = this.activeFrame_;
        1190 while (currentFrame) {
        1191 var task = currentFrame.getPendingTask();
        1192 if (task) {
        1193 pendingTasks.push(task);
        1194 }
        1195 // A frame's parent node will always be another frame.
        1196 currentFrame =
        1197 /** @type {webdriver.promise.Frame_} */ (currentFrame.getParent());
        1198 }
        1199
        1200 var fullHistory = goog.array.concat(this.history_, pendingTasks);
        1201 return goog.array.map(fullHistory, function(task) {
        1202 return task.toString();
        1203 });
        1204};
        1205
        1206
        1207/** Clears this instance's task history. */
        1208webdriver.promise.ControlFlow.prototype.clearHistory = function() {
        1209 this.history_ = [];
        1210};
        1211
        1212
        1213/**
        1214 * Removes a completed task from this instance's history record. If any
        1215 * tasks remain from aborted frames, those will be removed as well.
        1216 * @private
        1217 */
        1218webdriver.promise.ControlFlow.prototype.trimHistory_ = function() {
        1219 if (this.numAbortedFrames_) {
        1220 goog.array.splice(this.history_,
        1221 this.history_.length - this.numAbortedFrames_,
        1222 this.numAbortedFrames_);
        1223 this.numAbortedFrames_ = 0;
        1224 }
        1225 this.history_.pop();
        1226};
        1227
        1228
        1229/**
        1230 * Property used to track whether an error has been annotated by
        1231 * {@link webdriver.promise.ControlFlow#annotateError}.
        1232 * @private {string}
        1233 * @const
        1234 */
        1235webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_ =
        1236 'webdriver_promise_error_';
        1237
        1238
        1239/**
        1240 * Appends a summary of this instance's recent task history to the given
        1241 * error's stack trace. This function will also ensure the error's stack trace
        1242 * is in canonical form.
        1243 * @param {!(Error|goog.testing.JsUnitException)} e The error to annotate.
        1244 * @return {!(Error|goog.testing.JsUnitException)} The annotated error.
        1245 */
        1246webdriver.promise.ControlFlow.prototype.annotateError = function(e) {
        1247 if (!!e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_]) {
        1248 return e;
        1249 }
        1250
        1251 var history = this.getHistory();
        1252 if (history.length) {
        1253 e = webdriver.stacktrace.format(e);
        1254
        1255 /** @type {!Error} */(e).stack += [
        1256 '\n==== async task ====\n',
        1257 history.join('\n==== async task ====\n')
        1258 ].join('');
        1259
        1260 e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_] = true;
        1261 }
        1262
        1263 return e;
        1264};
        1265
        1266
        1267/**
        1268 * @return {string} The scheduled tasks still pending with this instance.
        1269 */
        1270webdriver.promise.ControlFlow.prototype.getSchedule = function() {
        1271 return this.activeFrame_ ? this.activeFrame_.getRoot().toString() : '[]';
        1272};
        1273
        1274
        1275/**
        1276 * Schedules a task for execution. If there is nothing currently in the
        1277 * queue, the task will be executed in the next turn of the event loop.
        1278 *
        1279 * @param {!Function} fn The function to call to start the task. If the
        1280 * function returns a {@link webdriver.promise.Promise}, this instance
        1281 * will wait for it to be resolved before starting the next task.
        1282 * @param {string=} opt_description A description of the task.
        1283 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1284 * the result of the action.
        1285 */
        1286webdriver.promise.ControlFlow.prototype.execute = function(
        1287 fn, opt_description) {
        1288 this.cancelShutdown_();
        1289
        1290 if (!this.activeFrame_) {
        1291 this.activeFrame_ = new webdriver.promise.Frame_(this);
        1292 }
        1293
        1294 // Trim an extra frame off the generated stack trace for the call to this
        1295 // function.
        1296 var snapshot = new webdriver.stacktrace.Snapshot(1);
        1297 var task = new webdriver.promise.Task_(
        1298 this, fn, opt_description || '', snapshot);
        1299 var scheduleIn = this.schedulingFrame_ || this.activeFrame_;
        1300 scheduleIn.addChild(task);
        1301
        1302 this.emit(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK);
        1303
        1304 this.scheduleEventLoopStart_();
        1305 return task.promise;
        1306};
        1307
        1308
        1309/**
        1310 * Inserts a {@code setTimeout} into the command queue. This is equivalent to
        1311 * a thread sleep in a synchronous programming language.
        1312 *
        1313 * @param {number} ms The timeout delay, in milliseconds.
        1314 * @param {string=} opt_description A description to accompany the timeout.
        1315 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1316 * the result of the action.
        1317 */
        1318webdriver.promise.ControlFlow.prototype.timeout = function(
        1319 ms, opt_description) {
        1320 return this.execute(function() {
        1321 return webdriver.promise.delayed(ms);
        1322 }, opt_description);
        1323};
        1324
        1325
        1326/**
        1327 * Schedules a task that shall wait for a condition to hold. Each condition
        1328 * function may return any value, but it will always be evaluated as a boolean.
        1329 *
        1330 * <p>Condition functions may schedule sub-tasks with this instance, however,
        1331 * their execution time will be factored into whether a wait has timed out.
        1332 *
        1333 * <p>In the event a condition returns a Promise, the polling loop will wait for
        1334 * it to be resolved before evaluating whether the condition has been satisfied.
        1335 * The resolution time for a promise is factored into whether a wait has timed
        1336 * out.
        1337 *
        1338 * <p>If the condition function throws, or returns a rejected promise, the
        1339 * wait task will fail.
        1340 *
        1341 * @param {!Function} condition The condition function to poll.
        1342 * @param {number} timeout How long to wait, in milliseconds, for the condition
        1343 * to hold before timing out.
        1344 * @param {string=} opt_message An optional error message to include if the
        1345 * wait times out; defaults to the empty string.
        1346 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1347 * condition has been satisified. The promise shall be rejected if the wait
        1348 * times out waiting for the condition.
        1349 */
        1350webdriver.promise.ControlFlow.prototype.wait = function(
        1351 condition, timeout, opt_message) {
        1352 var sleep = Math.min(timeout, 100);
        1353 var self = this;
        1354
        1355 return this.execute(function() {
        1356 var startTime = goog.now();
        1357 var waitResult = new webdriver.promise.Deferred();
        1358 var waitFrame = self.activeFrame_;
        1359 waitFrame.isWaiting = true;
        1360 pollCondition();
        1361 return waitResult.promise;
        1362
        1363 function pollCondition() {
        1364 self.runInNewFrame_(condition, function(value) {
        1365 var elapsed = goog.now() - startTime;
        1366 if (!!value) {
        1367 waitFrame.isWaiting = false;
        1368 waitResult.fulfill(value);
        1369 } else if (elapsed >= timeout) {
        1370 waitResult.reject(new Error((opt_message ? opt_message + '\n' : '') +
        1371 'Wait timed out after ' + elapsed + 'ms'));
        1372 } else {
        1373 self.timer.setTimeout(pollCondition, sleep);
        1374 }
        1375 }, waitResult.reject, true);
        1376 }
        1377 }, opt_message);
        1378};
        1379
        1380
        1381/**
        1382 * Schedules a task that will wait for another promise to resolve. The resolved
        1383 * promise's value will be returned as the task result.
        1384 * @param {!webdriver.promise.Promise} promise The promise to wait on.
        1385 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        1386 * task has completed.
        1387 */
        1388webdriver.promise.ControlFlow.prototype.await = function(promise) {
        1389 return this.execute(function() {
        1390 return promise;
        1391 });
        1392};
        1393
        1394
        1395/**
        1396 * Schedules the interval for this instance's event loop, if necessary.
        1397 * @private
        1398 */
        1399webdriver.promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() {
        1400 if (!this.eventLoopId_) {
        1401 this.eventLoopId_ = this.timer.setInterval(
        1402 goog.bind(this.runEventLoop_, this),
        1403 webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY);
        1404 }
        1405};
        1406
        1407
        1408/**
        1409 * Cancels the event loop, if necessary.
        1410 * @private
        1411 */
        1412webdriver.promise.ControlFlow.prototype.cancelEventLoop_ = function() {
        1413 if (this.eventLoopId_) {
        1414 this.timer.clearInterval(this.eventLoopId_);
        1415 this.eventLoopId_ = null;
        1416 }
        1417};
        1418
        1419
        1420/**
        1421 * Executes the next task for the current frame. If the current frame has no
        1422 * more tasks, the frame's result will be resolved, returning control to the
        1423 * frame's creator. This will terminate the flow if the completed frame was at
        1424 * the top of the stack.
        1425 * @private
        1426 */
        1427webdriver.promise.ControlFlow.prototype.runEventLoop_ = function() {
        1428 // If we get here and there are pending promise rejections, then those
        1429 // promises are queued up to run as soon as this (JS) event loop terminates.
        1430 // Short-circuit our loop to give those promises a chance to run. Otherwise,
        1431 // we might start a new task only to have it fail because of one of these
        1432 // pending rejections.
        1433 if (this.pendingRejections_) {
        1434 return;
        1435 }
        1436
        1437 // If the flow aborts due to an unhandled exception after we've scheduled
        1438 // another turn of the execution loop, we can end up in here with no tasks
        1439 // left. This is OK, just quietly return.
        1440 if (!this.activeFrame_) {
        1441 this.commenceShutdown_();
        1442 return;
        1443 }
        1444
        1445 var task;
        1446 if (this.activeFrame_.getPendingTask() || !(task = this.getNextTask_())) {
        1447 // Either the current frame is blocked on a pending task, or we don't have
        1448 // a task to finish because we've completed a frame. When completing a
        1449 // frame, we must abort the event loop to allow the frame's promise's
        1450 // callbacks to execute.
        1451 return;
        1452 }
        1453
        1454 var activeFrame = this.activeFrame_;
        1455 activeFrame.setPendingTask(task);
        1456 var markTaskComplete = goog.bind(function() {
        1457 this.history_.push(/** @type {!webdriver.promise.Task_} */ (task));
        1458 activeFrame.setPendingTask(null);
        1459 }, this);
        1460
        1461 this.trimHistory_();
        1462 var self = this;
        1463 this.runInNewFrame_(task.execute, function(result) {
        1464 markTaskComplete();
        1465 task.fulfill(result);
        1466 }, function(error) {
        1467 markTaskComplete();
        1468
        1469 if (!webdriver.promise.isError_(error) &&
        1470 !webdriver.promise.isPromise(error)) {
        1471 error = Error(error);
        1472 }
        1473
        1474 task.reject(self.annotateError(/** @type {!Error} */ (error)));
        1475 }, true);
        1476};
        1477
        1478
        1479/**
        1480 * @return {webdriver.promise.Task_} The next task to execute, or
        1481 * {@code null} if a frame was resolved.
        1482 * @private
        1483 */
        1484webdriver.promise.ControlFlow.prototype.getNextTask_ = function() {
        1485 var firstChild = this.activeFrame_.getFirstChild();
        1486 if (!firstChild) {
        1487 if (!this.activeFrame_.isWaiting) {
        1488 this.resolveFrame_(this.activeFrame_);
        1489 }
        1490 return null;
        1491 }
        1492
        1493 if (firstChild instanceof webdriver.promise.Frame_) {
        1494 this.activeFrame_ = firstChild;
        1495 return this.getNextTask_();
        1496 }
        1497
        1498 firstChild.getParent().removeChild(firstChild);
        1499 return firstChild;
        1500};
        1501
        1502
        1503/**
        1504 * @param {!webdriver.promise.Frame_} frame The frame to resolve.
        1505 * @private
        1506 */
        1507webdriver.promise.ControlFlow.prototype.resolveFrame_ = function(frame) {
        1508 if (this.activeFrame_ === frame) {
        1509 // Frame parent is always another frame, but the compiler is not smart
        1510 // enough to recognize this.
        1511 this.activeFrame_ =
        1512 /** @type {webdriver.promise.Frame_} */ (frame.getParent());
        1513 }
        1514
        1515 if (frame.getParent()) {
        1516 frame.getParent().removeChild(frame);
        1517 }
        1518 this.trimHistory_();
        1519 frame.fulfill();
        1520
        1521 if (!this.activeFrame_) {
        1522 this.commenceShutdown_();
        1523 }
        1524};
        1525
        1526
        1527/**
        1528 * Aborts the current frame. The frame, and all of the tasks scheduled within it
        1529 * will be discarded. If this instance does not have an active frame, it will
        1530 * immediately terminate all execution.
        1531 * @param {*} error The reason the frame is being aborted; typically either
        1532 * an Error or string.
        1533 * @private
        1534 */
        1535webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) {
        1536 // Annotate the error value if it is Error-like.
        1537 if (webdriver.promise.isError_(error)) {
        1538 this.annotateError(/** @type {!Error} */ (error));
        1539 }
        1540 this.numAbortedFrames_++;
        1541
        1542 if (!this.activeFrame_) {
        1543 this.abortNow_(error);
        1544 return;
        1545 }
        1546
        1547 // Frame parent is always another frame, but the compiler is not smart
        1548 // enough to recognize this.
        1549 var parent = /** @type {webdriver.promise.Frame_} */ (
        1550 this.activeFrame_.getParent());
        1551 if (parent) {
        1552 parent.removeChild(this.activeFrame_);
        1553 }
        1554
        1555 var frame = this.activeFrame_;
        1556 this.activeFrame_ = parent;
        1557 frame.reject(error);
        1558};
        1559
        1560
        1561/**
        1562 * Executes a function in a new frame. If the function does not schedule any new
        1563 * tasks, the frame will be discarded and the function's result returned
        1564 * immediately. Otherwise, a promise will be returned. This promise will be
        1565 * resolved with the function's result once all of the tasks scheduled within
        1566 * the function have been completed. If the function's frame is aborted, the
        1567 * returned promise will be rejected.
        1568 *
        1569 * @param {!Function} fn The function to execute.
        1570 * @param {function(*)} callback The function to call with a successful result.
        1571 * @param {function(*)} errback The function to call if there is an error.
        1572 * @param {boolean=} opt_activate Whether the active frame should be updated to
        1573 * the newly created frame so tasks are treated as sub-tasks.
        1574 * @private
        1575 */
        1576webdriver.promise.ControlFlow.prototype.runInNewFrame_ = function(
        1577 fn, callback, errback, opt_activate) {
        1578 var newFrame = new webdriver.promise.Frame_(this),
        1579 self = this,
        1580 oldFrame = this.activeFrame_;
        1581
        1582 try {
        1583 if (!this.activeFrame_) {
        1584 this.activeFrame_ = newFrame;
        1585 } else {
        1586 this.activeFrame_.addChild(newFrame);
        1587 }
        1588
        1589 // Activate the new frame to force tasks to be treated as sub-tasks of
        1590 // the parent frame.
        1591 if (opt_activate) {
        1592 this.activeFrame_ = newFrame;
        1593 }
        1594
        1595 try {
        1596 this.schedulingFrame_ = newFrame;
        1597 webdriver.promise.pushFlow_(this);
        1598 var result = fn();
        1599 } finally {
        1600 webdriver.promise.popFlow_();
        1601 this.schedulingFrame_ = null;
        1602 }
        1603 newFrame.lockFrame();
        1604
        1605 // If there was nothing scheduled in the new frame we can discard the
        1606 // frame and return immediately.
        1607 if (!newFrame.children_.length) {
        1608 removeNewFrame();
        1609 webdriver.promise.asap(result, callback, errback);
        1610 return;
        1611 }
        1612
        1613 newFrame.then(function() {
        1614 webdriver.promise.asap(result, callback, errback);
        1615 }, function(e) {
        1616 if (result instanceof webdriver.promise.Promise && result.isPending()) {
        1617 result.cancel(e);
        1618 e = result;
        1619 }
        1620 errback(e);
        1621 });
        1622 } catch (ex) {
        1623 removeNewFrame(new webdriver.promise.CanceledTaskError_(ex));
        1624 errback(ex);
        1625 }
        1626
        1627 /**
        1628 * @param {webdriver.promise.CanceledTaskError_=} opt_err If provided, the
        1629 * error that triggered the removal of this frame.
        1630 */
        1631 function removeNewFrame(opt_err) {
        1632 var parent = newFrame.getParent();
        1633 if (parent) {
        1634 parent.removeChild(newFrame);
        1635 }
        1636
        1637 if (opt_err) {
        1638 newFrame.cancelRemainingTasks(opt_err);
        1639 }
        1640 self.activeFrame_ = oldFrame;
        1641 }
        1642};
        1643
        1644
        1645/**
        1646 * Commences the shutdown sequence for this instance. After one turn of the
        1647 * event loop, this object will emit the
        1648 * {@link webdriver.promise.ControlFlow.EventType.IDLE} event to signal
        1649 * listeners that it has completed. During this wait, if another task is
        1650 * scheduled, the shutdown will be aborted.
        1651 * @private
        1652 */
        1653webdriver.promise.ControlFlow.prototype.commenceShutdown_ = function() {
        1654 if (!this.shutdownId_) {
        1655 // Go ahead and stop the event loop now. If we're in here, then there are
        1656 // no more frames with tasks to execute. If we waited to cancel the event
        1657 // loop in our timeout below, the event loop could trigger *before* the
        1658 // timeout, generating an error from there being no frames.
        1659 // If #execute is called before the timeout below fires, it will cancel
        1660 // the timeout and restart the event loop.
        1661 this.cancelEventLoop_();
        1662
        1663 var self = this;
        1664 self.shutdownId_ = self.timer.setTimeout(function() {
        1665 self.shutdownId_ = null;
        1666 self.emit(webdriver.promise.ControlFlow.EventType.IDLE);
        1667 }, 0);
        1668 }
        1669};
        1670
        1671
        1672/**
        1673 * Cancels the shutdown sequence if it is currently scheduled.
        1674 * @private
        1675 */
        1676webdriver.promise.ControlFlow.prototype.cancelShutdown_ = function() {
        1677 if (this.shutdownId_) {
        1678 this.timer.clearTimeout(this.shutdownId_);
        1679 this.shutdownId_ = null;
        1680 }
        1681};
        1682
        1683
        1684/**
        1685 * Aborts this flow, abandoning all remaining tasks. If there are
        1686 * listeners registered, an {@code UNCAUGHT_EXCEPTION} will be emitted with the
        1687 * offending {@code error}, otherwise, the {@code error} will be rethrown to the
        1688 * global error handler.
        1689 * @param {*} error Object describing the error that caused the flow to
        1690 * abort; usually either an Error or string value.
        1691 * @private
        1692 */
        1693webdriver.promise.ControlFlow.prototype.abortNow_ = function(error) {
        1694 this.activeFrame_ = null;
        1695 this.cancelShutdown_();
        1696 this.cancelEventLoop_();
        1697
        1698 var listeners = this.listeners(
        1699 webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
        1700 if (!listeners.length) {
        1701 this.timer.setTimeout(function() {
        1702 throw error;
        1703 }, 0);
        1704 } else {
        1705 this.emit(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION,
        1706 error);
        1707 }
        1708};
        1709
        1710
        1711
        1712/**
        1713 * A single node in an {@link webdriver.promise.ControlFlow}'s task tree.
        1714 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs
        1715 * to.
        1716 * @constructor
        1717 * @extends {webdriver.promise.Deferred}
        1718 * @private
        1719 */
        1720webdriver.promise.Node_ = function(flow) {
        1721 webdriver.promise.Deferred.call(this, null, flow);
        1722};
        1723goog.inherits(webdriver.promise.Node_, webdriver.promise.Deferred);
        1724
        1725
        1726/**
        1727 * This node's parent.
        1728 * @private {webdriver.promise.Node_}
        1729 */
        1730webdriver.promise.Node_.prototype.parent_ = null;
        1731
        1732
        1733/** @return {webdriver.promise.Node_} This node's parent. */
        1734webdriver.promise.Node_.prototype.getParent = function() {
        1735 return this.parent_;
        1736};
        1737
        1738
        1739/**
        1740 * @param {webdriver.promise.Node_} parent This node's new parent.
        1741 */
        1742webdriver.promise.Node_.prototype.setParent = function(parent) {
        1743 this.parent_ = parent;
        1744};
        1745
        1746
        1747/**
        1748 * @return {!webdriver.promise.Node_} The root of this node's tree.
        1749 */
        1750webdriver.promise.Node_.prototype.getRoot = function() {
        1751 var root = this;
        1752 while (root.parent_) {
        1753 root = root.parent_;
        1754 }
        1755 return root;
        1756};
        1757
        1758
        1759
        1760/**
        1761 * An execution frame within a {@link webdriver.promise.ControlFlow}. Each
        1762 * frame represents the execution context for either a
        1763 * {@link webdriver.promise.Task_} or a callback on a
        1764 * {@link webdriver.promise.Deferred}.
        1765 *
        1766 * <p>Each frame may contain sub-frames. If child N is a sub-frame, then the
        1767 * items queued within it are given priority over child N+1.
        1768 *
        1769 * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs
        1770 * to.
        1771 * @constructor
        1772 * @extends {webdriver.promise.Node_}
        1773 * @private
        1774 */
        1775webdriver.promise.Frame_ = function(flow) {
        1776 webdriver.promise.Node_.call(this, flow);
        1777
        1778 var reject = goog.bind(this.reject, this);
        1779 var cancelRemainingTasks = goog.bind(this.cancelRemainingTasks, this);
        1780
        1781 /** @override */
        1782 this.reject = function(e) {
        1783 cancelRemainingTasks(new webdriver.promise.CanceledTaskError_(e));
        1784 reject(e);
        1785 };
        1786
        1787 /**
        1788 * @private {!Array.<!(webdriver.promise.Frame_|webdriver.promise.Task_)>}
        1789 */
        1790 this.children_ = [];
        1791};
        1792goog.inherits(webdriver.promise.Frame_, webdriver.promise.Node_);
        1793
        1794
        1795/**
        1796 * The task currently being executed within this frame.
        1797 * @private {webdriver.promise.Task_}
        1798 */
        1799webdriver.promise.Frame_.prototype.pendingTask_ = null;
        1800
        1801
        1802/**
        1803 * Whether this frame is active. A frame is considered active once one of its
        1804 * descendants has been removed for execution.
        1805 *
        1806 * Adding a sub-frame as a child to an active frame is an indication that
        1807 * a callback to a {@link webdriver.promise.Deferred} is being invoked and any
        1808 * tasks scheduled within it should have priority over previously scheduled
        1809 * tasks:
        1810 * <code><pre>
        1811 * var flow = webdriver.promise.controlFlow();
        1812 * flow.execute('start here', goog.nullFunction).then(function() {
        1813 * flow.execute('this should execute 2nd', goog.nullFunction);
        1814 * });
        1815 * flow.execute('this should execute last', goog.nullFunction);
        1816 * </pre></code>
        1817 *
        1818 * @private {boolean}
        1819 */
        1820webdriver.promise.Frame_.prototype.isActive_ = false;
        1821
        1822
        1823/**
        1824 * Whether this frame is currently locked. A locked frame represents a callback
        1825 * or task function which has run to completion and scheduled all of its tasks.
        1826 *
        1827 * <p>Once a frame becomes {@link #isActive_ active}, any new frames which are
        1828 * added represent callbacks on a {@link webdriver.promise.Deferred}, whose
        1829 * tasks must be given priority over previously scheduled tasks.
        1830 *
        1831 * @private {boolean}
        1832 */
        1833webdriver.promise.Frame_.prototype.isLocked_ = false;
        1834
        1835
        1836/**
        1837 * A reference to the last node inserted in this frame.
        1838 * @private {webdriver.promise.Node_}
        1839 */
        1840webdriver.promise.Frame_.prototype.lastInsertedChild_ = null;
        1841
        1842
        1843/**
        1844 * Marks all of the tasks that are descendants of this frame in the execution
        1845 * tree as cancelled. This is necessary for callbacks scheduled asynchronous.
        1846 * For example:
        1847 *
        1848 * var someResult;
        1849 * webdriver.promise.createFlow(function(flow) {
        1850 * someResult = flow.execute(function() {});
        1851 * throw Error();
        1852 * }).addErrback(function(err) {
        1853 * console.log('flow failed: ' + err);
        1854 * someResult.then(function() {
        1855 * console.log('task succeeded!');
        1856 * }, function(err) {
        1857 * console.log('task failed! ' + err);
        1858 * });
        1859 * });
        1860 * // flow failed: Error: boom
        1861 * // task failed! CanceledTaskError: Task discarded due to a previous
        1862 * // task failure: Error: boom
        1863 *
        1864 * @param {!webdriver.promise.CanceledTaskError_} error The cancellation
        1865 * error.
        1866 */
        1867webdriver.promise.Frame_.prototype.cancelRemainingTasks = function(error) {
        1868 goog.array.forEach(this.children_, function(child) {
        1869 if (child instanceof webdriver.promise.Frame_) {
        1870 child.cancelRemainingTasks(error);
        1871 } else {
        1872 // None of the previously registered listeners should be notified that
        1873 // the task is being canceled, however, we need at least one errback
        1874 // to prevent the cancellation from bubbling up.
        1875 child.removeAll();
        1876 child.thenCatch(goog.nullFunction);
        1877 child.cancel(error);
        1878 }
        1879 });
        1880};
        1881
        1882
        1883/**
        1884 * @return {webdriver.promise.Task_} The task currently executing
        1885 * within this frame, if any.
        1886 */
        1887webdriver.promise.Frame_.prototype.getPendingTask = function() {
        1888 return this.pendingTask_;
        1889};
        1890
        1891
        1892/**
        1893 * @param {webdriver.promise.Task_} task The task currently
        1894 * executing within this frame, if any.
        1895 */
        1896webdriver.promise.Frame_.prototype.setPendingTask = function(task) {
        1897 this.pendingTask_ = task;
        1898};
        1899
        1900
        1901/** Locks this frame. */
        1902webdriver.promise.Frame_.prototype.lockFrame = function() {
        1903 this.isLocked_ = true;
        1904};
        1905
        1906
        1907/**
        1908 * Adds a new node to this frame.
        1909 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} node
        1910 * The node to insert.
        1911 */
        1912webdriver.promise.Frame_.prototype.addChild = function(node) {
        1913 if (this.lastInsertedChild_ &&
        1914 this.lastInsertedChild_ instanceof webdriver.promise.Frame_ &&
        1915 !this.lastInsertedChild_.isLocked_) {
        1916 this.lastInsertedChild_.addChild(node);
        1917 return;
        1918 }
        1919
        1920 node.setParent(this);
        1921
        1922 if (this.isActive_ && node instanceof webdriver.promise.Frame_) {
        1923 var index = 0;
        1924 if (this.lastInsertedChild_ instanceof
        1925 webdriver.promise.Frame_) {
        1926 index = goog.array.indexOf(this.children_, this.lastInsertedChild_) + 1;
        1927 }
        1928 goog.array.insertAt(this.children_, node, index);
        1929 this.lastInsertedChild_ = node;
        1930 return;
        1931 }
        1932
        1933 this.lastInsertedChild_ = node;
        1934 this.children_.push(node);
        1935};
        1936
        1937
        1938/**
        1939 * @return {(webdriver.promise.Frame_|webdriver.promise.Task_)} This frame's
        1940 * fist child.
        1941 */
        1942webdriver.promise.Frame_.prototype.getFirstChild = function() {
        1943 this.isActive_ = true;
        1944 this.lastInsertedChild_ = null;
        1945 return this.children_[0];
        1946};
        1947
        1948
        1949/**
        1950 * Removes a child from this frame.
        1951 * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} child
        1952 * The child to remove.
        1953 */
        1954webdriver.promise.Frame_.prototype.removeChild = function(child) {
        1955 var index = goog.array.indexOf(this.children_, child);
        1956 child.setParent(null);
        1957 goog.array.removeAt(this.children_, index);
        1958 if (this.lastInsertedChild_ === child) {
        1959 this.lastInsertedChild_ = null;
        1960 }
        1961};
        1962
        1963
        1964/** @override */
        1965webdriver.promise.Frame_.prototype.toString = function() {
        1966 return '[' + goog.array.map(this.children_, function(child) {
        1967 return child.toString();
        1968 }).join(', ') + ']';
        1969};
        1970
        1971
        1972
        1973/**
        1974 * A task to be executed by a {@link webdriver.promise.ControlFlow}.
        1975 *
        1976 * @param {!webdriver.promise.ControlFlow} flow The flow this instances belongs
        1977 * to.
        1978 * @param {!Function} fn The function to call when the task executes. If it
        1979 * returns a {@code webdriver.promise.Promise}, the flow will wait
        1980 * for it to be resolved before starting the next task.
        1981 * @param {string} description A description of the task for debugging.
        1982 * @param {!webdriver.stacktrace.Snapshot} snapshot A snapshot of the stack
        1983 * when this task was scheduled.
        1984 * @constructor
        1985 * @extends {webdriver.promise.Node_}
        1986 * @private
        1987 */
        1988webdriver.promise.Task_ = function(flow, fn, description, snapshot) {
        1989 webdriver.promise.Node_.call(this, flow);
        1990
        1991 /**
        1992 * Executes this task.
        1993 * @type {!Function}
        1994 */
        1995 this.execute = fn;
        1996
        1997 /** @private {string} */
        1998 this.description_ = description;
        1999
        2000 /** @private {!webdriver.stacktrace.Snapshot} */
        2001 this.snapshot_ = snapshot;
        2002};
        2003goog.inherits(webdriver.promise.Task_, webdriver.promise.Node_);
        2004
        2005
        2006/** @return {string} This task's description. */
        2007webdriver.promise.Task_.prototype.getDescription = function() {
        2008 return this.description_;
        2009};
        2010
        2011
        2012/** @override */
        2013webdriver.promise.Task_.prototype.toString = function() {
        2014 var stack = this.snapshot_.getStacktrace();
        2015 var ret = this.description_;
        2016 if (stack.length) {
        2017 if (this.description_) {
        2018 ret += '\n';
        2019 }
        2020 ret += stack.join('\n');
        2021 }
        2022 return ret;
        2023};
        2024
        2025
        2026
        2027/**
        2028 * Special error used to signal when a task is canceled because a previous
        2029 * task in the same frame failed.
        2030 * @param {*} err The error that caused the task cancellation.
        2031 * @constructor
        2032 * @extends {goog.debug.Error}
        2033 * @private
        2034 */
        2035webdriver.promise.CanceledTaskError_ = function(err) {
        2036 goog.base(this, 'Task discarded due to a previous task failure: ' + err);
        2037};
        2038goog.inherits(webdriver.promise.CanceledTaskError_, goog.debug.Error);
        2039
        2040
        2041/** @override */
        2042webdriver.promise.CanceledTaskError_.prototype.name = 'CanceledTaskError';
        2043
        2044
        2045
        2046/**
        2047 * The default flow to use if no others are active.
        2048 * @private {!webdriver.promise.ControlFlow}
        2049 */
        2050webdriver.promise.defaultFlow_ = new webdriver.promise.ControlFlow();
        2051
        2052
        2053/**
        2054 * A stack of active control flows, with the top of the stack used to schedule
        2055 * commands. When there are multiple flows on the stack, the flow at index N
        2056 * represents a callback triggered within a task owned by the flow at index
        2057 * N-1.
        2058 * @private {!Array.<!webdriver.promise.ControlFlow>}
        2059 */
        2060webdriver.promise.activeFlows_ = [];
        2061
        2062
        2063/**
        2064 * Changes the default flow to use when no others are active.
        2065 * @param {!webdriver.promise.ControlFlow} flow The new default flow.
        2066 * @throws {Error} If the default flow is not currently active.
        2067 */
        2068webdriver.promise.setDefaultFlow = function(flow) {
        2069 if (webdriver.promise.activeFlows_.length) {
        2070 throw Error('You may only change the default flow while it is active');
        2071 }
        2072 webdriver.promise.defaultFlow_ = flow;
        2073};
        2074
        2075
        2076/**
        2077 * @return {!webdriver.promise.ControlFlow} The currently active control flow.
        2078 */
        2079webdriver.promise.controlFlow = function() {
        2080 return /** @type {!webdriver.promise.ControlFlow} */ (
        2081 goog.array.peek(webdriver.promise.activeFlows_) ||
        2082 webdriver.promise.defaultFlow_);
        2083};
        2084
        2085
        2086/**
        2087 * @param {!webdriver.promise.ControlFlow} flow The new flow.
        2088 * @private
        2089 */
        2090webdriver.promise.pushFlow_ = function(flow) {
        2091 webdriver.promise.activeFlows_.push(flow);
        2092};
        2093
        2094
        2095/** @private */
        2096webdriver.promise.popFlow_ = function() {
        2097 webdriver.promise.activeFlows_.pop();
        2098};
        2099
        2100
        2101/**
        2102 * Creates a new control flow. The provided callback will be invoked as the
        2103 * first task within the new flow, with the flow as its sole argument. Returns
        2104 * a promise that resolves to the callback result.
        2105 * @param {function(!webdriver.promise.ControlFlow)} callback The entry point
        2106 * to the newly created flow.
        2107 * @return {!webdriver.promise.Promise} A promise that resolves to the callback
        2108 * result.
        2109 */
        2110webdriver.promise.createFlow = function(callback) {
        2111 var flow = new webdriver.promise.ControlFlow(
        2112 webdriver.promise.defaultFlow_.timer);
        2113 return flow.execute(function() {
        2114 return callback(flow);
        2115 });
        2116};
        \ No newline at end of file +promise.js

        lib/webdriver/promise.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @license Portions of this code are from the Dojo toolkit, received under the
        20 * BSD License:
        21 * Redistribution and use in source and binary forms, with or without
        22 * modification, are permitted provided that the following conditions are met:
        23 *
        24 * * Redistributions of source code must retain the above copyright notice,
        25 * this list of conditions and the following disclaimer.
        26 * * Redistributions in binary form must reproduce the above copyright notice,
        27 * this list of conditions and the following disclaimer in the documentation
        28 * and/or other materials provided with the distribution.
        29 * * Neither the name of the Dojo Foundation nor the names of its contributors
        30 * may be used to endorse or promote products derived from this software
        31 * without specific prior written permission.
        32 *
        33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        34 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        35 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
        36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
        37 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
        38 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
        39 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
        40 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
        41 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
        42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        43 * POSSIBILITY OF SUCH DAMAGE.
        44 */
        45
        46/**
        47 * @fileoverview A promise implementation based on the CommonJS promise/A and
        48 * promise/B proposals. For more information, see
        49 * http://wiki.commonjs.org/wiki/Promises.
        50 */
        51
        52goog.module('webdriver.promise');
        53goog.module.declareLegacyNamespace();
        54
        55var Arrays = goog.require('goog.array');
        56var asserts = goog.require('goog.asserts');
        57var asyncRun = goog.require('goog.async.run');
        58var throwException = goog.require('goog.async.throwException');
        59var DebugError = goog.require('goog.debug.Error');
        60var Objects = goog.require('goog.object');
        61var EventEmitter = goog.require('webdriver.EventEmitter');
        62var stacktrace = goog.require('webdriver.stacktrace');
        63
        64
        65
        66/**
        67 * @define {boolean} Whether to append traces of {@code then} to rejection
        68 * errors.
        69 */
        70goog.define('webdriver.promise.LONG_STACK_TRACES', false);
        71
        72/** @const */
        73var promise = exports;
        74
        75
        76/**
        77 * Generates an error to capture the current stack trace.
        78 * @param {string} name Error name for this stack trace.
        79 * @param {string} msg Message to record.
        80 * @param {!Function} topFn The function that should appear at the top of the
        81 * stack; only applicable in V8.
        82 * @return {!Error} The generated error.
        83 */
        84promise.captureStackTrace = function(name, msg, topFn) {
        85 var e = Error(msg);
        86 e.name = name;
        87 if (Error.captureStackTrace) {
        88 Error.captureStackTrace(e, topFn);
        89 } else {
        90 var stack = stacktrace.getStack(e);
        91 e.stack = e.toString();
        92 if (stack) {
        93 e.stack += '\n' + stack;
        94 }
        95 }
        96 return e;
        97};
        98
        99
        100/**
        101 * Error used when the computation of a promise is cancelled.
        102 *
        103 * @param {string=} opt_msg The cancellation message.
        104 * @constructor
        105 * @extends {DebugError}
        106 * @final
        107 */
        108promise.CancellationError = function(opt_msg) {
        109 DebugError.call(this, opt_msg);
        110
        111 /** @override */
        112 this.name = 'CancellationError';
        113};
        114goog.inherits(promise.CancellationError, DebugError);
        115
        116
        117/**
        118 * Wraps the given error in a CancellationError. Will trivially return
        119 * the error itself if it is an instanceof CancellationError.
        120 *
        121 * @param {*} error The error to wrap.
        122 * @param {string=} opt_msg The prefix message to use.
        123 * @return {!promise.CancellationError} A cancellation error.
        124 */
        125promise.CancellationError.wrap = function(error, opt_msg) {
        126 if (error instanceof promise.CancellationError) {
        127 return /** @type {!promise.CancellationError} */(error);
        128 } else if (opt_msg) {
        129 var message = opt_msg;
        130 if (error) {
        131 message += ': ' + error;
        132 }
        133 return new promise.CancellationError(message);
        134 }
        135 var message;
        136 if (error) {
        137 message = error + '';
        138 }
        139 return new promise.CancellationError(message);
        140};
        141
        142
        143
        144/**
        145 * Thenable is a promise-like object with a {@code then} method which may be
        146 * used to schedule callbacks on a promised value.
        147 *
        148 * @interface
        149 * @extends {IThenable<T>}
        150 * @template T
        151 */
        152promise.Thenable = function() {};
        153
        154
        155/**
        156 * Cancels the computation of this promise's value, rejecting the promise in the
        157 * process. This method is a no-op if the promise has already been resolved.
        158 *
        159 * @param {(string|promise.CancellationError)=} opt_reason The reason this
        160 * promise is being cancelled.
        161 */
        162promise.Thenable.prototype.cancel = function(opt_reason) {};
        163
        164
        165/** @return {boolean} Whether this promise's value is still being computed. */
        166promise.Thenable.prototype.isPending = function() {};
        167
        168
        169/**
        170 * Registers listeners for when this instance is resolved.
        171 *
        172 * @param {?(function(T): (R|IThenable<R>))=} opt_callback The
        173 * function to call if this promise is successfully resolved. The function
        174 * should expect a single argument: the promise's resolved value.
        175 * @param {?(function(*): (R|IThenable<R>))=} opt_errback
        176 * The function to call if this promise is rejected. The function should
        177 * expect a single argument: the rejection reason.
        178 * @return {!promise.Promise<R>} A new promise which will be
        179 * resolved with the result of the invoked callback.
        180 * @template R
        181 */
        182promise.Thenable.prototype.then = function(opt_callback, opt_errback) {};
        183
        184
        185/**
        186 * Registers a listener for when this promise is rejected. This is synonymous
        187 * with the {@code catch} clause in a synchronous API:
        188 *
        189 * // Synchronous API:
        190 * try {
        191 * doSynchronousWork();
        192 * } catch (ex) {
        193 * console.error(ex);
        194 * }
        195 *
        196 * // Asynchronous promise API:
        197 * doAsynchronousWork().thenCatch(function(ex) {
        198 * console.error(ex);
        199 * });
        200 *
        201 * @param {function(*): (R|IThenable<R>)} errback The
        202 * function to call if this promise is rejected. The function should
        203 * expect a single argument: the rejection reason.
        204 * @return {!promise.Promise<R>} A new promise which will be
        205 * resolved with the result of the invoked callback.
        206 * @template R
        207 */
        208promise.Thenable.prototype.thenCatch = function(errback) {};
        209
        210
        211/**
        212 * Registers a listener to invoke when this promise is resolved, regardless
        213 * of whether the promise's value was successfully computed. This function
        214 * is synonymous with the {@code finally} clause in a synchronous API:
        215 *
        216 * // Synchronous API:
        217 * try {
        218 * doSynchronousWork();
        219 * } finally {
        220 * cleanUp();
        221 * }
        222 *
        223 * // Asynchronous promise API:
        224 * doAsynchronousWork().thenFinally(cleanUp);
        225 *
        226 * __Note:__ similar to the {@code finally} clause, if the registered
        227 * callback returns a rejected promise or throws an error, it will silently
        228 * replace the rejection error (if any) from this promise:
        229 *
        230 * try {
        231 * throw Error('one');
        232 * } finally {
        233 * throw Error('two'); // Hides Error: one
        234 * }
        235 *
        236 * promise.rejected(Error('one'))
        237 * .thenFinally(function() {
        238 * throw Error('two'); // Hides Error: one
        239 * });
        240 *
        241 * @param {function(): (R|IThenable<R>)} callback The function
        242 * to call when this promise is resolved.
        243 * @return {!promise.Promise<R>} A promise that will be fulfilled
        244 * with the callback result.
        245 * @template R
        246 */
        247promise.Thenable.prototype.thenFinally = function(callback) {};
        248
        249
        250/**
        251 * Property used to flag constructor's as implementing the Thenable interface
        252 * for runtime type checking.
        253 * @type {string}
        254 * @const
        255 */
        256var IMPLEMENTED_BY_PROP = '$webdriver_Thenable';
        257
        258
        259/**
        260 * Adds a property to a class prototype to allow runtime checks of whether
        261 * instances of that class implement the Thenable interface. This function will
        262 * also ensure the prototype's {@code then} function is exported from compiled
        263 * code.
        264 * @param {function(new: promise.Thenable, ...?)} ctor The
        265 * constructor whose prototype to modify.
        266 */
        267promise.Thenable.addImplementation = function(ctor) {
        268 // Based on goog.promise.Thenable.isImplementation.
        269 ctor.prototype['then'] = ctor.prototype.then;
        270 try {
        271 // Old IE7 does not support defineProperty; IE8 only supports it for
        272 // DOM elements.
        273 Object.defineProperty(
        274 ctor.prototype,
        275 IMPLEMENTED_BY_PROP,
        276 {'value': true, 'enumerable': false});
        277 } catch (ex) {
        278 ctor.prototype[IMPLEMENTED_BY_PROP] = true;
        279 }
        280};
        281
        282
        283/**
        284 * Checks if an object has been tagged for implementing the Thenable interface
        285 * as defined by {@link webdriver.promise.Thenable.addImplementation}.
        286 * @param {*} object The object to test.
        287 * @return {boolean} Whether the object is an implementation of the Thenable
        288 * interface.
        289 */
        290promise.Thenable.isImplementation = function(object) {
        291 // Based on goog.promise.Thenable.isImplementation.
        292 if (!object) {
        293 return false;
        294 }
        295 try {
        296 return !!object[IMPLEMENTED_BY_PROP];
        297 } catch (e) {
        298 return false; // Property access seems to be forbidden.
        299 }
        300};
        301
        302
        303
        304/**
        305 * @enum {string}
        306 */
        307var PromiseState = {
        308 PENDING: 'pending',
        309 BLOCKED: 'blocked',
        310 REJECTED: 'rejected',
        311 FULFILLED: 'fulfilled'
        312};
        313
        314
        315
        316/**
        317 * Represents the eventual value of a completed operation. Each promise may be
        318 * in one of three states: pending, fulfilled, or rejected. Each promise starts
        319 * in the pending state and may make a single transition to either a
        320 * fulfilled or rejected state, at which point the promise is considered
        321 * resolved.
        322 *
        323 * @param {function(
        324 * function((T|IThenable<T>|Thenable)=),
        325 * function(*=))} resolver
        326 * Function that is invoked immediately to begin computation of this
        327 * promise's value. The function should accept a pair of callback functions,
        328 * one for fulfilling the promise and another for rejecting it.
        329 * @param {promise.ControlFlow=} opt_flow The control flow
        330 * this instance was created under. Defaults to the currently active flow.
        331 * @constructor
        332 * @implements {promise.Thenable<T>}
        333 * @template T
        334 * @see http://promises-aplus.github.io/promises-spec/
        335 */
        336promise.Promise = function(resolver, opt_flow) {
        337 goog.getUid(this);
        338
        339 /** @private {!promise.ControlFlow} */
        340 this.flow_ = opt_flow || promise.controlFlow();
        341
        342 /** @private {Error} */
        343 this.stack_ = null;
        344 if (promise.LONG_STACK_TRACES) {
        345 this.stack_ = promise.captureStackTrace('Promise', 'new', promise.Promise);
        346 }
        347
        348 /** @private {promise.Promise<?>} */
        349 this.parent_ = null;
        350
        351 /** @private {Array<!Callback>} */
        352 this.callbacks_ = null;
        353
        354 /** @private {PromiseState} */
        355 this.state_ = PromiseState.PENDING;
        356
        357 /** @private {boolean} */
        358 this.handled_ = false;
        359
        360 /** @private {boolean} */
        361 this.pendingNotifications_ = false;
        362
        363 /** @private {*} */
        364 this.value_ = undefined;
        365
        366 try {
        367 var self = this;
        368 resolver(function(value) {
        369 self.resolve_(PromiseState.FULFILLED, value);
        370 }, function(reason) {
        371 self.resolve_(PromiseState.REJECTED, reason);
        372 });
        373 } catch (ex) {
        374 this.resolve_(PromiseState.REJECTED, ex);
        375 }
        376};
        377promise.Thenable.addImplementation(promise.Promise);
        378
        379
        380/** @override */
        381promise.Promise.prototype.toString = function() {
        382 return 'Promise::' + goog.getUid(this) +
        383 ' {[[PromiseStatus]]: "' + this.state_ + '"}';
        384};
        385
        386
        387/**
        388 * Resolves this promise. If the new value is itself a promise, this function
        389 * will wait for it to be resolved before notifying the registered listeners.
        390 * @param {PromiseState} newState The promise's new state.
        391 * @param {*} newValue The promise's new value.
        392 * @throws {TypeError} If {@code newValue === this}.
        393 * @private
        394 */
        395promise.Promise.prototype.resolve_ = function(newState, newValue) {
        396 if (PromiseState.PENDING !== this.state_) {
        397 return;
        398 }
        399
        400 if (newValue === this) {
        401 // See promise a+, 2.3.1
        402 // http://promises-aplus.github.io/promises-spec/#point-48
        403 throw new TypeError('A promise may not resolve to itself');
        404 }
        405
        406 this.parent_ = null;
        407 this.state_ = PromiseState.BLOCKED;
        408
        409 if (promise.Thenable.isImplementation(newValue)) {
        410 // 2.3.2
        411 newValue = /** @type {!promise.Thenable} */(newValue);
        412 newValue.then(
        413 this.unblockAndResolve_.bind(this, PromiseState.FULFILLED),
        414 this.unblockAndResolve_.bind(this, PromiseState.REJECTED));
        415 return;
        416
        417 } else if (goog.isObject(newValue)) {
        418 // 2.3.3
        419
        420 try {
        421 // 2.3.3.1
        422 var then = newValue['then'];
        423 } catch (e) {
        424 // 2.3.3.2
        425 this.state_ = PromiseState.REJECTED;
        426 this.value_ = e;
        427 this.scheduleNotifications_();
        428 return;
        429 }
        430
        431 // NB: goog.isFunction is loose and will accept instanceof Function.
        432 if (typeof then === 'function') {
        433 // 2.3.3.3
        434 this.invokeThen_(newValue, then);
        435 return;
        436 }
        437 }
        438
        439 if (newState === PromiseState.REJECTED &&
        440 isError(newValue) && newValue.stack && this.stack_) {
        441 newValue.stack += '\nFrom: ' + (this.stack_.stack || this.stack_);
        442 }
        443
        444 // 2.3.3.4 and 2.3.4
        445 this.state_ = newState;
        446 this.value_ = newValue;
        447 this.scheduleNotifications_();
        448};
        449
        450
        451/**
        452 * Invokes a thenable's "then" method according to 2.3.3.3 of the promise
        453 * A+ spec.
        454 * @param {!Object} x The thenable object.
        455 * @param {!Function} then The "then" function to invoke.
        456 * @private
        457 */
        458promise.Promise.prototype.invokeThen_ = function(x, then) {
        459 var called = false;
        460 var self = this;
        461
        462 var resolvePromise = function(value) {
        463 if (!called) { // 2.3.3.3.3
        464 called = true;
        465 // 2.3.3.3.1
        466 self.unblockAndResolve_(PromiseState.FULFILLED, value);
        467 }
        468 };
        469
        470 var rejectPromise = function(reason) {
        471 if (!called) { // 2.3.3.3.3
        472 called = true;
        473 // 2.3.3.3.2
        474 self.unblockAndResolve_(PromiseState.REJECTED, reason);
        475 }
        476 };
        477
        478 try {
        479 // 2.3.3.3
        480 then.call(x, resolvePromise, rejectPromise);
        481 } catch (e) {
        482 // 2.3.3.3.4.2
        483 rejectPromise(e);
        484 }
        485};
        486
        487
        488/**
        489 * @param {PromiseState} newState The promise's new state.
        490 * @param {*} newValue The promise's new value.
        491 * @private
        492 */
        493promise.Promise.prototype.unblockAndResolve_ = function(newState, newValue) {
        494 if (this.state_ === PromiseState.BLOCKED) {
        495 this.state_ = PromiseState.PENDING;
        496 this.resolve_(newState, newValue);
        497 }
        498};
        499
        500
        501/**
        502 * @private
        503 */
        504promise.Promise.prototype.scheduleNotifications_ = function() {
        505 if (!this.pendingNotifications_) {
        506 this.pendingNotifications_ = true;
        507 this.flow_.suspend_();
        508
        509 var activeFrame;
        510
        511 if (!this.handled_ &&
        512 this.state_ === PromiseState.REJECTED &&
        513 !(this.value_ instanceof promise.CancellationError)) {
        514 activeFrame = this.flow_.getActiveFrame_();
        515 activeFrame.pendingRejection = true;
        516 }
        517
        518 if (this.callbacks_ && this.callbacks_.length) {
        519 activeFrame = this.flow_.getRunningFrame_();
        520 var self = this;
        521 this.callbacks_.forEach(function(callback) {
        522 if (!callback.frame_.getParent()) {
        523 activeFrame.addChild(callback.frame_);
        524 }
        525 });
        526 }
        527
        528 asyncRun(goog.bind(this.notifyAll_, this, activeFrame));
        529 }
        530};
        531
        532
        533/**
        534 * Notifies all of the listeners registered with this promise that its state
        535 * has changed.
        536 * @param {Frame} frame The active frame from when this round of
        537 * notifications were scheduled.
        538 * @private
        539 */
        540promise.Promise.prototype.notifyAll_ = function(frame) {
        541 this.flow_.resume_();
        542 this.pendingNotifications_ = false;
        543
        544 if (!this.handled_ &&
        545 this.state_ === PromiseState.REJECTED &&
        546 !(this.value_ instanceof promise.CancellationError)) {
        547 this.flow_.abortFrame_(this.value_, frame);
        548 }
        549
        550 if (this.callbacks_) {
        551 var callbacks = this.callbacks_;
        552 this.callbacks_ = null;
        553 callbacks.forEach(this.notify_, this);
        554 }
        555};
        556
        557
        558/**
        559 * Notifies a single callback of this promise's change ins tate.
        560 * @param {Callback} callback The callback to notify.
        561 * @private
        562 */
        563promise.Promise.prototype.notify_ = function(callback) {
        564 callback.notify(this.state_, this.value_);
        565};
        566
        567
        568/** @override */
        569promise.Promise.prototype.cancel = function(opt_reason) {
        570 if (!this.isPending()) {
        571 return;
        572 }
        573
        574 if (this.parent_) {
        575 this.parent_.cancel(opt_reason);
        576 } else {
        577 this.resolve_(
        578 PromiseState.REJECTED,
        579 promise.CancellationError.wrap(opt_reason));
        580 }
        581};
        582
        583
        584/** @override */
        585promise.Promise.prototype.isPending = function() {
        586 return this.state_ === PromiseState.PENDING;
        587};
        588
        589
        590/** @override */
        591promise.Promise.prototype.then = function(opt_callback, opt_errback) {
        592 return this.addCallback_(
        593 opt_callback, opt_errback, 'then', promise.Promise.prototype.then);
        594};
        595
        596
        597/** @override */
        598promise.Promise.prototype.thenCatch = function(errback) {
        599 return this.addCallback_(
        600 null, errback, 'thenCatch', promise.Promise.prototype.thenCatch);
        601};
        602
        603
        604/** @override */
        605promise.Promise.prototype.thenFinally = function(callback) {
        606 var error;
        607 var mustThrow = false;
        608 return this.then(function() {
        609 return callback();
        610 }, function(err) {
        611 error = err;
        612 mustThrow = true;
        613 return callback();
        614 }).then(function() {
        615 if (mustThrow) {
        616 throw error;
        617 }
        618 });
        619};
        620
        621
        622/**
        623 * Registers a new callback with this promise
        624 * @param {(function(T): (R|IThenable<R>)|null|undefined)} callback The
        625 * fulfillment callback.
        626 * @param {(function(*): (R|IThenable<R>)|null|undefined)} errback The
        627 * rejection callback.
        628 * @param {string} name The callback name.
        629 * @param {!Function} fn The function to use as the top of the stack when
        630 * recording the callback's creation point.
        631 * @return {!promise.Promise<R>} A new promise which will be resolved with the
        632 * esult of the invoked callback.
        633 * @template R
        634 * @private
        635 */
        636promise.Promise.prototype.addCallback_ = function(callback, errback, name, fn) {
        637 if (!goog.isFunction(callback) && !goog.isFunction(errback)) {
        638 return this;
        639 }
        640
        641 this.handled_ = true;
        642 var cb = new Callback(this, callback, errback, name, fn);
        643
        644 if (!this.callbacks_) {
        645 this.callbacks_ = [];
        646 }
        647 this.callbacks_.push(cb);
        648
        649 if (this.state_ !== PromiseState.PENDING &&
        650 this.state_ !== PromiseState.BLOCKED) {
        651 this.flow_.getSchedulingFrame_().addChild(cb.frame_);
        652 this.scheduleNotifications_();
        653 }
        654 return cb.promise;
        655};
        656
        657
        658/**
        659 * Represents a value that will be resolved at some point in the future. This
        660 * class represents the protected "producer" half of a Promise - each Deferred
        661 * has a {@code promise} property that may be returned to consumers for
        662 * registering callbacks, reserving the ability to resolve the deferred to the
        663 * producer.
        664 *
        665 * If this Deferred is rejected and there are no listeners registered before
        666 * the next turn of the event loop, the rejection will be passed to the
        667 * {@link webdriver.promise.ControlFlow} as an unhandled failure.
        668 *
        669 * @param {promise.ControlFlow=} opt_flow The control flow
        670 * this instance was created under. This should only be provided during
        671 * unit tests.
        672 * @constructor
        673 * @implements {promise.Thenable<T>}
        674 * @template T
        675 */
        676promise.Deferred = function(opt_flow) {
        677 var fulfill, reject;
        678
        679 /** @type {!promise.Promise<T>} */
        680 this.promise = new promise.Promise(function(f, r) {
        681 fulfill = f;
        682 reject = r;
        683 }, opt_flow);
        684
        685 var self = this;
        686 var checkNotSelf = function(value) {
        687 if (value === self) {
        688 throw new TypeError('May not resolve a Deferred with itself');
        689 }
        690 };
        691
        692 /**
        693 * Resolves this deferred with the given value. It is safe to call this as a
        694 * normal function (with no bound "this").
        695 * @param {(T|IThenable<T>|Thenable)=} opt_value The fulfilled value.
        696 */
        697 this.fulfill = function(opt_value) {
        698 checkNotSelf(opt_value);
        699 fulfill(opt_value);
        700 };
        701
        702 /**
        703 * Rejects this promise with the given reason. It is safe to call this as a
        704 * normal function (with no bound "this").
        705 * @param {*=} opt_reason The rejection reason.
        706 */
        707 this.reject = function(opt_reason) {
        708 checkNotSelf(opt_reason);
        709 reject(opt_reason);
        710 };
        711};
        712promise.Thenable.addImplementation(promise.Deferred);
        713
        714
        715/** @override */
        716promise.Deferred.prototype.isPending = function() {
        717 return this.promise.isPending();
        718};
        719
        720
        721/** @override */
        722promise.Deferred.prototype.cancel = function(opt_reason) {
        723 this.promise.cancel(opt_reason);
        724};
        725
        726
        727/**
        728 * @override
        729 * @deprecated Use {@code then} from the promise property directly.
        730 */
        731promise.Deferred.prototype.then = function(opt_cb, opt_eb) {
        732 return this.promise.then(opt_cb, opt_eb);
        733};
        734
        735
        736/**
        737 * @override
        738 * @deprecated Use {@code thenCatch} from the promise property directly.
        739 */
        740promise.Deferred.prototype.thenCatch = function(opt_eb) {
        741 return this.promise.thenCatch(opt_eb);
        742};
        743
        744
        745/**
        746 * @override
        747 * @deprecated Use {@code thenFinally} from the promise property directly.
        748 */
        749promise.Deferred.prototype.thenFinally = function(opt_cb) {
        750 return this.promise.thenFinally(opt_cb);
        751};
        752
        753
        754/**
        755 * Tests if a value is an Error-like object. This is more than an straight
        756 * instanceof check since the value may originate from another context.
        757 * @param {*} value The value to test.
        758 * @return {boolean} Whether the value is an error.
        759 */
        760function isError(value) {
        761 return value instanceof Error ||
        762 goog.isObject(value) &&
        763 (goog.isString(value.message) ||
        764 // A special test for goog.testing.JsUnitException.
        765 value.isJsUnitException);
        766
        767};
        768
        769
        770/**
        771 * Determines whether a {@code value} should be treated as a promise.
        772 * Any object whose "then" property is a function will be considered a promise.
        773 *
        774 * @param {*} value The value to test.
        775 * @return {boolean} Whether the value is a promise.
        776 */
        777promise.isPromise = function(value) {
        778 return !!value && goog.isObject(value) &&
        779 // Use array notation so the Closure compiler does not obfuscate away our
        780 // contract. Use typeof rather than goog.isFunction because
        781 // goog.isFunction accepts instanceof Function, which the promise spec
        782 // does not.
        783 typeof value['then'] === 'function';
        784};
        785
        786
        787/**
        788 * Creates a promise that will be resolved at a set time in the future.
        789 * @param {number} ms The amount of time, in milliseconds, to wait before
        790 * resolving the promise.
        791 * @return {!promise.Promise} The promise.
        792 */
        793promise.delayed = function(ms) {
        794 var key;
        795 return new promise.Promise(function(fulfill) {
        796 key = setTimeout(function() {
        797 key = null;
        798 fulfill();
        799 }, ms);
        800 }).thenCatch(function(e) {
        801 clearTimeout(key);
        802 key = null;
        803 throw e;
        804 });
        805};
        806
        807
        808/**
        809 * Creates a new deferred object.
        810 * @return {!promise.Deferred<T>} The new deferred object.
        811 * @template T
        812 */
        813promise.defer = function() {
        814 return new promise.Deferred();
        815};
        816
        817
        818/**
        819 * Creates a promise that has been resolved with the given value.
        820 * @param {T=} opt_value The resolved value.
        821 * @return {!promise.Promise<T>} The resolved promise.
        822 * @template T
        823 */
        824promise.fulfilled = function(opt_value) {
        825 if (opt_value instanceof promise.Promise) {
        826 return opt_value;
        827 }
        828 return new promise.Promise(function(fulfill) {
        829 fulfill(opt_value);
        830 });
        831};
        832
        833
        834/**
        835 * Creates a promise that has been rejected with the given reason.
        836 * @param {*=} opt_reason The rejection reason; may be any value, but is
        837 * usually an Error or a string.
        838 * @return {!promise.Promise<T>} The rejected promise.
        839 * @template T
        840 */
        841promise.rejected = function(opt_reason) {
        842 if (opt_reason instanceof promise.Promise) {
        843 return opt_reason;
        844 }
        845 return new promise.Promise(function(_, reject) {
        846 reject(opt_reason);
        847 });
        848};
        849
        850
        851/**
        852 * Wraps a function that expects a node-style callback as its final
        853 * argument. This callback expects two arguments: an error value (which will be
        854 * null if the call succeeded), and the success value as the second argument.
        855 * The callback will the resolve or reject the returned promise, based on its arguments.
        856 * @param {!Function} fn The function to wrap.
        857 * @param {...?} var_args The arguments to apply to the function, excluding the
        858 * final callback.
        859 * @return {!promise.Promise} A promise that will be resolved with the
        860 * result of the provided function's callback.
        861 */
        862promise.checkedNodeCall = function(fn, var_args) {
        863 var args = Arrays.slice(arguments, 1);
        864 return new promise.Promise(function(fulfill, reject) {
        865 try {
        866 args.push(function(error, value) {
        867 error ? reject(error) : fulfill(value);
        868 });
        869 fn.apply(undefined, args);
        870 } catch (ex) {
        871 reject(ex);
        872 }
        873 });
        874};
        875
        876
        877/**
        878 * Registers an observer on a promised {@code value}, returning a new promise
        879 * that will be resolved when the value is. If {@code value} is not a promise,
        880 * then the return promise will be immediately resolved.
        881 * @param {*} value The value to observe.
        882 * @param {Function=} opt_callback The function to call when the value is
        883 * resolved successfully.
        884 * @param {Function=} opt_errback The function to call when the value is
        885 * rejected.
        886 * @return {!promise.Promise} A new promise.
        887 */
        888promise.when = function(value, opt_callback, opt_errback) {
        889 if (promise.Thenable.isImplementation(value)) {
        890 return value.then(opt_callback, opt_errback);
        891 }
        892
        893 return new promise.Promise(function(fulfill, reject) {
        894 promise.asap(value, fulfill, reject);
        895 }).then(opt_callback, opt_errback);
        896};
        897
        898
        899/**
        900 * Invokes the appropriate callback function as soon as a promised
        901 * {@code value} is resolved. This function is similar to
        902 * {@link webdriver.promise.when}, except it does not return a new promise.
        903 * @param {*} value The value to observe.
        904 * @param {Function} callback The function to call when the value is
        905 * resolved successfully.
        906 * @param {Function=} opt_errback The function to call when the value is
        907 * rejected.
        908 */
        909promise.asap = function(value, callback, opt_errback) {
        910 if (promise.isPromise(value)) {
        911 value.then(callback, opt_errback);
        912
        913 // Maybe a Dojo-like deferred object?
        914 } else if (!!value && goog.isObject(value) &&
        915 goog.isFunction(value.addCallbacks)) {
        916 value.addCallbacks(callback, opt_errback);
        917
        918 // A raw value, return a resolved promise.
        919 } else if (callback) {
        920 callback(value);
        921 }
        922};
        923
        924
        925/**
        926 * Given an array of promises, will return a promise that will be fulfilled
        927 * with the fulfillment values of the input array's values. If any of the
        928 * input array's promises are rejected, the returned promise will be rejected
        929 * with the same reason.
        930 *
        931 * @param {!Array<(T|!promise.Promise<T>)>} arr An array of
        932 * promises to wait on.
        933 * @return {!promise.Promise<!Array<T>>} A promise that is
        934 * fulfilled with an array containing the fulfilled values of the
        935 * input array, or rejected with the same reason as the first
        936 * rejected value.
        937 * @template T
        938 */
        939promise.all = function(arr) {
        940 return new promise.Promise(function(fulfill, reject) {
        941 var n = arr.length;
        942 var values = [];
        943
        944 if (!n) {
        945 fulfill(values);
        946 return;
        947 }
        948
        949 var toFulfill = n;
        950 var onFulfilled = function(index, value) {
        951 values[index] = value;
        952 toFulfill--;
        953 if (toFulfill == 0) {
        954 fulfill(values);
        955 }
        956 };
        957
        958 for (var i = 0; i < n; ++i) {
        959 promise.asap(arr[i], goog.partial(onFulfilled, i), reject);
        960 }
        961 });
        962};
        963
        964
        965/**
        966 * Calls a function for each element in an array and inserts the result into a
        967 * new array, which is used as the fulfillment value of the promise returned
        968 * by this function.
        969 *
        970 * If the return value of the mapping function is a promise, this function
        971 * will wait for it to be fulfilled before inserting it into the new array.
        972 *
        973 * If the mapping function throws or returns a rejected promise, the
        974 * promise returned by this function will be rejected with the same reason.
        975 * Only the first failure will be reported; all subsequent errors will be
        976 * silently ignored.
        977 *
        978 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
        979 * array to iterator over, or a promise that will resolve to said array.
        980 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): ?} fn The
        981 * function to call for each element in the array. This function should
        982 * expect three arguments (the element, the index, and the array itself.
        983 * @param {SELF=} opt_self The object to be used as the value of 'this' within
        984 * {@code fn}.
        985 * @template TYPE, SELF
        986 */
        987promise.map = function(arr, fn, opt_self) {
        988 return promise.fulfilled(arr).then(function(arr) {
        989 goog.asserts.assertNumber(arr.length, 'not an array like value');
        990 return new promise.Promise(function(fulfill, reject) {
        991 var n = arr.length;
        992 var values = new Array(n);
        993 (function processNext(i) {
        994 for (; i < n; i++) {
        995 if (i in arr) {
        996 break;
        997 }
        998 }
        999 if (i >= n) {
        1000 fulfill(values);
        1001 return;
        1002 }
        1003 try {
        1004 promise.asap(
        1005 fn.call(opt_self, arr[i], i, /** @type {!Array} */(arr)),
        1006 function(value) {
        1007 values[i] = value;
        1008 processNext(i + 1);
        1009 },
        1010 reject);
        1011 } catch (ex) {
        1012 reject(ex);
        1013 }
        1014 })(0);
        1015 });
        1016 });
        1017};
        1018
        1019
        1020/**
        1021 * Calls a function for each element in an array, and if the function returns
        1022 * true adds the element to a new array.
        1023 *
        1024 * If the return value of the filter function is a promise, this function
        1025 * will wait for it to be fulfilled before determining whether to insert the
        1026 * element into the new array.
        1027 *
        1028 * If the filter function throws or returns a rejected promise, the promise
        1029 * returned by this function will be rejected with the same reason. Only the
        1030 * first failure will be reported; all subsequent errors will be silently
        1031 * ignored.
        1032 *
        1033 * @param {!(Array<TYPE>|promise.Promise<!Array<TYPE>>)} arr The
        1034 * array to iterator over, or a promise that will resolve to said array.
        1035 * @param {function(this: SELF, TYPE, number, !Array<TYPE>): (
        1036 * boolean|promise.Promise<boolean>)} fn The function
        1037 * to call for each element in the array.
        1038 * @param {SELF=} opt_self The object to be used as the value of 'this' within
        1039 * {@code fn}.
        1040 * @template TYPE, SELF
        1041 */
        1042promise.filter = function(arr, fn, opt_self) {
        1043 return promise.fulfilled(arr).then(function(arr) {
        1044 goog.asserts.assertNumber(arr.length, 'not an array like value');
        1045 return new promise.Promise(function(fulfill, reject) {
        1046 var n = arr.length;
        1047 var values = [];
        1048 var valuesLength = 0;
        1049 (function processNext(i) {
        1050 for (; i < n; i++) {
        1051 if (i in arr) {
        1052 break;
        1053 }
        1054 }
        1055 if (i >= n) {
        1056 fulfill(values);
        1057 return;
        1058 }
        1059 try {
        1060 var value = arr[i];
        1061 var include = fn.call(opt_self, value, i, /** @type {!Array} */(arr));
        1062 promise.asap(include, function(include) {
        1063 if (include) {
        1064 values[valuesLength++] = value;
        1065 }
        1066 processNext(i + 1);
        1067 }, reject);
        1068 } catch (ex) {
        1069 reject(ex);
        1070 }
        1071 })(0);
        1072 });
        1073 });
        1074};
        1075
        1076
        1077/**
        1078 * Returns a promise that will be resolved with the input value in a
        1079 * fully-resolved state. If the value is an array, each element will be fully
        1080 * resolved. Likewise, if the value is an object, all keys will be fully
        1081 * resolved. In both cases, all nested arrays and objects will also be
        1082 * fully resolved. All fields are resolved in place; the returned promise will
        1083 * resolve on {@code value} and not a copy.
        1084 *
        1085 * Warning: This function makes no checks against objects that contain
        1086 * cyclical references:
        1087 *
        1088 * var value = {};
        1089 * value['self'] = value;
        1090 * promise.fullyResolved(value); // Stack overflow.
        1091 *
        1092 * @param {*} value The value to fully resolve.
        1093 * @return {!promise.Promise} A promise for a fully resolved version
        1094 * of the input value.
        1095 */
        1096promise.fullyResolved = function(value) {
        1097 if (promise.isPromise(value)) {
        1098 return promise.when(value, fullyResolveValue);
        1099 }
        1100 return fullyResolveValue(value);
        1101};
        1102
        1103
        1104/**
        1105 * @param {*} value The value to fully resolve. If a promise, assumed to
        1106 * already be resolved.
        1107 * @return {!promise.Promise} A promise for a fully resolved version
        1108 * of the input value.
        1109 */
        1110 function fullyResolveValue(value) {
        1111 switch (goog.typeOf(value)) {
        1112 case 'array':
        1113 return fullyResolveKeys(/** @type {!Array} */ (value));
        1114
        1115 case 'object':
        1116 if (promise.isPromise(value)) {
        1117 // We get here when the original input value is a promise that
        1118 // resolves to itself. When the user provides us with such a promise,
        1119 // trust that it counts as a "fully resolved" value and return it.
        1120 // Of course, since it's already a promise, we can just return it
        1121 // to the user instead of wrapping it in another promise.
        1122 return /** @type {!promise.Promise} */ (value);
        1123 }
        1124
        1125 if (goog.isNumber(value.nodeType) &&
        1126 goog.isObject(value.ownerDocument) &&
        1127 goog.isNumber(value.ownerDocument.nodeType)) {
        1128 // DOM node; return early to avoid infinite recursion. Should we
        1129 // only support objects with a certain level of nesting?
        1130 return promise.fulfilled(value);
        1131 }
        1132
        1133 return fullyResolveKeys(/** @type {!Object} */ (value));
        1134
        1135 default: // boolean, function, null, number, string, undefined
        1136 return promise.fulfilled(value);
        1137 }
        1138};
        1139
        1140
        1141/**
        1142 * @param {!(Array|Object)} obj the object to resolve.
        1143 * @return {!promise.Promise} A promise that will be resolved with the
        1144 * input object once all of its values have been fully resolved.
        1145 */
        1146 function fullyResolveKeys(obj) {
        1147 var isArray = goog.isArray(obj);
        1148 var numKeys = isArray ? obj.length : Objects.getCount(obj);
        1149 if (!numKeys) {
        1150 return promise.fulfilled(obj);
        1151 }
        1152
        1153 var numResolved = 0;
        1154 return new promise.Promise(function(fulfill, reject) {
        1155 // In pre-IE9, goog.array.forEach will not iterate properly over arrays
        1156 // containing undefined values because "index in array" returns false
        1157 // when array[index] === undefined (even for x = [undefined, 1]). To get
        1158 // around this, we need to use our own forEach implementation.
        1159 // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be
        1160 // reproduced in IE9 by changing the browser/document modes, it requires an
        1161 // actual pre-IE9 browser. Yay, IE!
        1162 var forEachKey = !isArray ? Objects.forEach : function(arr, fn) {
        1163 var n = arr.length;
        1164 for (var i = 0; i < n; ++i) {
        1165 fn.call(null, arr[i], i, arr);
        1166 }
        1167 };
        1168
        1169 forEachKey(obj, function(partialValue, key) {
        1170 var type = goog.typeOf(partialValue);
        1171 if (type != 'array' && type != 'object') {
        1172 maybeResolveValue();
        1173 return;
        1174 }
        1175
        1176 promise.fullyResolved(partialValue).then(
        1177 function(resolvedValue) {
        1178 obj[key] = resolvedValue;
        1179 maybeResolveValue();
        1180 },
        1181 reject);
        1182 });
        1183
        1184 function maybeResolveValue() {
        1185 if (++numResolved == numKeys) {
        1186 fulfill(obj);
        1187 }
        1188 }
        1189 });
        1190};
        1191
        1192
        1193//////////////////////////////////////////////////////////////////////////////
        1194//
        1195// promise.ControlFlow
        1196//
        1197//////////////////////////////////////////////////////////////////////////////
        1198
        1199
        1200
        1201/**
        1202 * Handles the execution of scheduled tasks, each of which may be an
        1203 * asynchronous operation. The control flow will ensure tasks are executed in
        1204 * the ordered scheduled, starting each task only once those before it have
        1205 * completed.
        1206 *
        1207 * Each task scheduled within this flow may return a
        1208 * {@link webdriver.promise.Promise} to indicate it is an asynchronous
        1209 * operation. The ControlFlow will wait for such promises to be resolved before
        1210 * marking the task as completed.
        1211 *
        1212 * Tasks and each callback registered on a {@link webdriver.promise.Promise}
        1213 * will be run in their own ControlFlow frame. Any tasks scheduled within a
        1214 * frame will take priority over previously scheduled tasks. Furthermore, if any
        1215 * of the tasks in the frame fail, the remainder of the tasks in that frame will
        1216 * be discarded and the failure will be propagated to the user through the
        1217 * callback/task's promised result.
        1218 *
        1219 * Each time a ControlFlow empties its task queue, it will fire an
        1220 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event. Conversely,
        1221 * whenever the flow terminates due to an unhandled error, it will remove all
        1222 * remaining tasks in its queue and fire an
        1223 * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION
        1224 * UNCAUGHT_EXCEPTION} event. If there are no listeners registered with the
        1225 * flow, the error will be rethrown to the global error handler.
        1226 *
        1227 * @constructor
        1228 * @extends {EventEmitter}
        1229 * @final
        1230 */
        1231promise.ControlFlow = function() {
        1232 EventEmitter.call(this);
        1233 goog.getUid(this);
        1234
        1235 /**
        1236 * Tracks the active execution frame for this instance. Lazily initialized
        1237 * when the first task is scheduled.
        1238 * @private {Frame}
        1239 */
        1240 this.activeFrame_ = null;
        1241
        1242 /**
        1243 * A reference to the frame which is currently top of the stack in
        1244 * {@link #runInFrame_}. The {@link #activeFrame_} will always be an ancestor
        1245 * of the {@link #runningFrame_}, but the two will often not be the same. The
        1246 * active frame represents which frame is currently executing a task while the
        1247 * running frame represents either the task itself or a promise callback which
        1248 * has fired asynchronously.
        1249 * @private {Frame}
        1250 */
        1251 this.runningFrame_ = null;
        1252
        1253 /**
        1254 * A reference to the frame in which new tasks should be scheduled. If
        1255 * {@code null}, tasks will be scheduled within the active frame. When forcing
        1256 * a function to run in the context of a new frame, this pointer is used to
        1257 * ensure tasks are scheduled within the newly created frame, even though it
        1258 * won't be active yet.
        1259 * @private {Frame}
        1260 * @see {#runInFrame_}
        1261 */
        1262 this.schedulingFrame_ = null;
        1263
        1264 /**
        1265 * Micro task that controls shutting down the control flow. Upon shut down,
        1266 * the flow will emit an {@link webdriver.promise.ControlFlow.EventType.IDLE}
        1267 * event. Idle events always follow a brief timeout in order to catch latent
        1268 * errors from the last completed task. If this task had a callback
        1269 * registered, but no errback, and the task fails, the unhandled failure would
        1270 * not be reported by the promise system until the next turn of the event
        1271 * loop:
        1272 *
        1273 * // Schedule 1 task that fails.
        1274 * var result = promise.controlFlow().schedule('example',
        1275 * function() { return promise.rejected('failed'); });
        1276 * // Set a callback on the result. This delays reporting the unhandled
        1277 * // failure for 1 turn of the event loop.
        1278 * result.then(goog.nullFunction);
        1279 *
        1280 * @private {MicroTask}
        1281 */
        1282 this.shutdownTask_ = null;
        1283
        1284 /**
        1285 * Micro task used to trigger execution of this instance's event loop.
        1286 * @private {MicroTask}
        1287 */
        1288 this.eventLoopTask_ = null;
        1289
        1290 /**
        1291 * ID for a long running interval used to keep a Node.js process running
        1292 * while a control flow's event loop has yielded. This is a cheap hack
        1293 * required since the {@link #runEventLoop_} is only scheduled to run when
        1294 * there is _actually_ something to run. When a control flow is waiting on
        1295 * a task, there will be nothing in the JS event loop and the process would
        1296 * terminate without this.
        1297 *
        1298 * An alternative solution would be to change {@link #runEventLoop_} to run
        1299 * as an interval rather than as on-demand micro-tasks. While this approach
        1300 * (which was previously used) requires fewer micro-task allocations, it
        1301 * results in many unnecessary invocations of {@link #runEventLoop_}.
        1302 *
        1303 * @private {?number}
        1304 */
        1305 this.hold_ = null;
        1306
        1307 /**
        1308 * The number of holds placed on this flow. These represent points where the
        1309 * flow must not execute any further actions so an asynchronous action may
        1310 * run first. One such example are notifications fired by a
        1311 * {@link webdriver.promise.Promise}: the Promise spec requires that callbacks
        1312 * are invoked in a turn of the event loop after they are scheduled. To ensure
        1313 * tasks within a callback are scheduled in the correct frame, a promise will
        1314 * make the parent flow yield before its notifications are fired.
        1315 * @private {number}
        1316 */
        1317 this.yieldCount_ = 0;
        1318};
        1319goog.inherits(promise.ControlFlow, EventEmitter);
        1320
        1321
        1322/**
        1323 * Events that may be emitted by an {@link webdriver.promise.ControlFlow}.
        1324 * @enum {string}
        1325 */
        1326promise.ControlFlow.EventType = {
        1327
        1328 /** Emitted when all tasks have been successfully executed. */
        1329 IDLE: 'idle',
        1330
        1331 /** Emitted when a ControlFlow has been reset. */
        1332 RESET: 'reset',
        1333
        1334 /** Emitted whenever a new task has been scheduled. */
        1335 SCHEDULE_TASK: 'scheduleTask',
        1336
        1337 /**
        1338 * Emitted whenever a control flow aborts due to an unhandled promise
        1339 * rejection. This event will be emitted along with the offending rejection
        1340 * reason. Upon emitting this event, the control flow will empty its task
        1341 * queue and revert to its initial state.
        1342 */
        1343 UNCAUGHT_EXCEPTION: 'uncaughtException'
        1344};
        1345
        1346
        1347/**
        1348 * Returns a string representation of this control flow, which is its current
        1349 * {@link #getSchedule() schedule}, sans task stack traces.
        1350 * @return {string} The string representation of this contorl flow.
        1351 * @override
        1352 */
        1353promise.ControlFlow.prototype.toString = function() {
        1354 return this.getSchedule();
        1355};
        1356
        1357
        1358/**
        1359 * Resets this instance, clearing its queue and removing all event listeners.
        1360 */
        1361promise.ControlFlow.prototype.reset = function() {
        1362 this.activeFrame_ = null;
        1363 this.schedulingFrame_ = null;
        1364 this.emit(promise.ControlFlow.EventType.RESET);
        1365 this.removeAllListeners();
        1366 this.cancelShutdown_();
        1367 this.cancelEventLoop_();
        1368};
        1369
        1370
        1371/**
        1372 * Generates an annotated string describing the internal state of this control
        1373 * flow, including the currently executing as well as pending tasks. If
        1374 * {@code opt_includeStackTraces === true}, the string will include the
        1375 * stack trace from when each task was scheduled.
        1376 * @param {string=} opt_includeStackTraces Whether to include the stack traces
        1377 * from when each task was scheduled. Defaults to false.
        1378 * @return {string} String representation of this flow's internal state.
        1379 */
        1380promise.ControlFlow.prototype.getSchedule = function(opt_includeStackTraces) {
        1381 var ret = 'ControlFlow::' + goog.getUid(this);
        1382 var activeFrame = this.activeFrame_;
        1383 var runningFrame = this.runningFrame_;
        1384 if (!activeFrame) {
        1385 return ret;
        1386 }
        1387 var childIndent = '| ';
        1388 return ret + '\n' + toStringHelper(activeFrame.getRoot(), childIndent);
        1389
        1390 /**
        1391 * @param {!(Frame|Task)} node .
        1392 * @param {string} indent .
        1393 * @param {boolean=} opt_isPending .
        1394 * @return {string} .
        1395 */
        1396 function toStringHelper(node, indent, opt_isPending) {
        1397 var ret = node.toString();
        1398 if (opt_isPending) {
        1399 ret = '(pending) ' + ret;
        1400 }
        1401 if (node === activeFrame) {
        1402 ret = '(active) ' + ret;
        1403 }
        1404 if (node === runningFrame) {
        1405 ret = '(running) ' + ret;
        1406 }
        1407 if (node instanceof Frame) {
        1408 if (node.getPendingTask()) {
        1409 ret += '\n' + toStringHelper(
        1410 /** @type {!Task} */(node.getPendingTask()),
        1411 childIndent,
        1412 true);
        1413 }
        1414 if (node.children_) {
        1415 node.children_.forEach(function(child) {
        1416 if (!node.getPendingTask() ||
        1417 node.getPendingTask().getFrame() !== child) {
        1418 ret += '\n' + toStringHelper(child, childIndent);
        1419 }
        1420 });
        1421 }
        1422 } else {
        1423 var task = /** @type {!Task} */(node);
        1424 if (opt_includeStackTraces && task.promise.stack_) {
        1425 ret += '\n' + childIndent +
        1426 (task.promise.stack_.stack || task.promise.stack_).
        1427 replace(/\n/g, '\n' + childIndent);
        1428 }
        1429 if (task.getFrame()) {
        1430 ret += '\n' + toStringHelper(
        1431 /** @type {!Frame} */(task.getFrame()),
        1432 childIndent);
        1433 }
        1434 }
        1435 return indent + ret.replace(/\n/g, '\n' + indent);
        1436 }
        1437};
        1438
        1439
        1440/**
        1441 * @return {!Frame} The active frame for this flow.
        1442 * @private
        1443 */
        1444promise.ControlFlow.prototype.getActiveFrame_ = function() {
        1445 this.cancelShutdown_();
        1446 if (!this.activeFrame_) {
        1447 this.activeFrame_ = new Frame(this);
        1448 this.activeFrame_.once(Frame.ERROR_EVENT, this.abortNow_, this);
        1449 this.scheduleEventLoopStart_();
        1450 }
        1451 return this.activeFrame_;
        1452};
        1453
        1454
        1455/**
        1456 * @return {!Frame} The frame that new items should be added to.
        1457 * @private
        1458 */
        1459promise.ControlFlow.prototype.getSchedulingFrame_ = function() {
        1460 return this.schedulingFrame_ || this.getActiveFrame_();
        1461};
        1462
        1463
        1464/**
        1465 * @return {!Frame} The frame that is current executing.
        1466 * @private
        1467 */
        1468promise.ControlFlow.prototype.getRunningFrame_ = function() {
        1469 return this.runningFrame_ || this.getActiveFrame_();
        1470};
        1471
        1472
        1473/**
        1474 * Schedules a task for execution. If there is nothing currently in the
        1475 * queue, the task will be executed in the next turn of the event loop. If
        1476 * the task function is a generator, the task will be executed using
        1477 * {@link webdriver.promise.consume}.
        1478 *
        1479 * @param {function(): (T|promise.Promise<T>)} fn The function to
        1480 * call to start the task. If the function returns a
        1481 * {@link webdriver.promise.Promise}, this instance will wait for it to be
        1482 * resolved before starting the next task.
        1483 * @param {string=} opt_description A description of the task.
        1484 * @return {!promise.Promise<T>} A promise that will be resolved
        1485 * with the result of the action.
        1486 * @template T
        1487 */
        1488promise.ControlFlow.prototype.execute = function(fn, opt_description) {
        1489 if (promise.isGenerator(fn)) {
        1490 fn = goog.partial(promise.consume, fn);
        1491 }
        1492
        1493 if (!this.hold_) {
        1494 var holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js
        1495 this.hold_ = setInterval(goog.nullFunction, holdIntervalMs);
        1496 }
        1497
        1498 var description = opt_description || '<anonymous>';
        1499 var task = new Task(this, fn, description);
        1500 task.promise.stack_ = promise.captureStackTrace('Task', description,
        1501 promise.ControlFlow.prototype.execute);
        1502
        1503 this.getSchedulingFrame_().addChild(task);
        1504 this.emit(promise.ControlFlow.EventType.SCHEDULE_TASK, opt_description);
        1505 this.scheduleEventLoopStart_();
        1506 return task.promise;
        1507};
        1508
        1509
        1510/**
        1511 * Inserts a {@code setTimeout} into the command queue. This is equivalent to
        1512 * a thread sleep in a synchronous programming language.
        1513 *
        1514 * @param {number} ms The timeout delay, in milliseconds.
        1515 * @param {string=} opt_description A description to accompany the timeout.
        1516 * @return {!promise.Promise} A promise that will be resolved with
        1517 * the result of the action.
        1518 */
        1519promise.ControlFlow.prototype.timeout = function(ms, opt_description) {
        1520 return this.execute(function() {
        1521 return promise.delayed(ms);
        1522 }, opt_description);
        1523};
        1524
        1525
        1526/**
        1527 * Schedules a task that shall wait for a condition to hold. Each condition
        1528 * function may return any value, but it will always be evaluated as a boolean.
        1529 *
        1530 * Condition functions may schedule sub-tasks with this instance, however,
        1531 * their execution time will be factored into whether a wait has timed out.
        1532 *
        1533 * In the event a condition returns a Promise, the polling loop will wait for
        1534 * it to be resolved before evaluating whether the condition has been satisfied.
        1535 * The resolution time for a promise is factored into whether a wait has timed
        1536 * out.
        1537 *
        1538 * If the condition function throws, or returns a rejected promise, the
        1539 * wait task will fail.
        1540 *
        1541 * If the condition is defined as a promise, the flow will wait for it to
        1542 * settle. If the timeout expires before the promise settles, the promise
        1543 * returned by this function will be rejected.
        1544 *
        1545 * If this function is invoked with `timeout === 0`, or the timeout is omitted,
        1546 * the flow will wait indefinitely for the condition to be satisfied.
        1547 *
        1548 * @param {(!promise.Promise<T>|function())} condition The condition to poll,
        1549 * or a promise to wait on.
        1550 * @param {number=} opt_timeout How long to wait, in milliseconds, for the
        1551 * condition to hold before timing out. If omitted, the flow will wait
        1552 * indefinitely.
        1553 * @param {string=} opt_message An optional error message to include if the
        1554 * wait times out; defaults to the empty string.
        1555 * @return {!promise.Promise<T>} A promise that will be fulfilled
        1556 * when the condition has been satisified. The promise shall be rejected if
        1557 * the wait times out waiting for the condition.
        1558 * @throws {TypeError} If condition is not a function or promise or if timeout
        1559 * is not a number >= 0.
        1560 * @template T
        1561 */
        1562promise.ControlFlow.prototype.wait = function(
        1563 condition, opt_timeout, opt_message) {
        1564 var timeout = opt_timeout || 0;
        1565 if (!goog.isNumber(timeout) || timeout < 0) {
        1566 throw TypeError('timeout must be a number >= 0: ' + timeout);
        1567 }
        1568
        1569 if (promise.isPromise(condition)) {
        1570 return this.execute(function() {
        1571 if (!timeout) {
        1572 return condition;
        1573 }
        1574 return new promise.Promise(function(fulfill, reject) {
        1575 var start = goog.now();
        1576 var timer = setTimeout(function() {
        1577 timer = null;
        1578 reject(Error((opt_message ? opt_message + '\n' : '') +
        1579 'Timed out waiting for promise to resolve after ' +
        1580 (goog.now() - start) + 'ms'));
        1581 }, timeout);
        1582
        1583 /** @type {Thenable} */(condition).then(
        1584 function(value) {
        1585 timer && clearTimeout(timer);
        1586 fulfill(value);
        1587 },
        1588 function(error) {
        1589 timer && clearTimeout(timer);
        1590 reject(error);
        1591 });
        1592 });
        1593 }, opt_message || '<anonymous wait: promise resolution>');
        1594 }
        1595
        1596 if (!goog.isFunction(condition)) {
        1597 throw TypeError('Invalid condition; must be a function or promise: ' +
        1598 goog.typeOf(condition));
        1599 }
        1600
        1601 if (promise.isGenerator(condition)) {
        1602 condition = goog.partial(promise.consume, condition);
        1603 }
        1604
        1605 var self = this;
        1606 return this.execute(function() {
        1607 var startTime = goog.now();
        1608 return new promise.Promise(function(fulfill, reject) {
        1609 self.suspend_();
        1610 pollCondition();
        1611
        1612 function pollCondition() {
        1613 self.resume_();
        1614 self.execute(/**@type {function()}*/(condition)).then(function(value) {
        1615 var elapsed = goog.now() - startTime;
        1616 if (!!value) {
        1617 fulfill(value);
        1618 } else if (timeout && elapsed >= timeout) {
        1619 reject(new Error((opt_message ? opt_message + '\n' : '') +
        1620 'Wait timed out after ' + elapsed + 'ms'));
        1621 } else {
        1622 self.suspend_();
        1623 // Do not use asyncRun here because we need a non-micro yield
        1624 // here so the UI thread is given a chance when running in a
        1625 // browser.
        1626 setTimeout(pollCondition, 0);
        1627 }
        1628 }, reject);
        1629 }
        1630 });
        1631 }, opt_message || '<anonymous wait>');
        1632};
        1633
        1634
        1635/**
        1636 * Schedules the interval for this instance's event loop, if necessary.
        1637 * @private
        1638 */
        1639promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() {
        1640 if (!this.eventLoopTask_ && !this.yieldCount_ && this.activeFrame_ &&
        1641 !this.activeFrame_.getPendingTask()) {
        1642 this.eventLoopTask_ = new MicroTask(this.runEventLoop_, this);
        1643 }
        1644};
        1645
        1646
        1647/**
        1648 * Cancels the event loop, if necessary.
        1649 * @private
        1650 */
        1651promise.ControlFlow.prototype.cancelEventLoop_ = function() {
        1652 if (this.eventLoopTask_) {
        1653 this.eventLoopTask_.cancel();
        1654 this.eventLoopTask_ = null;
        1655 }
        1656};
        1657
        1658
        1659/**
        1660 * Suspends this control flow, preventing it from executing any more tasks.
        1661 * @private
        1662 */
        1663promise.ControlFlow.prototype.suspend_ = function() {
        1664 this.yieldCount_ += 1;
        1665 this.cancelEventLoop_();
        1666};
        1667
        1668
        1669/**
        1670 * Resumes execution of tasks scheduled within this control flow.
        1671 * @private
        1672 */
        1673promise.ControlFlow.prototype.resume_ = function() {
        1674 this.yieldCount_ -= 1;
        1675 if (!this.yieldCount_ && this.activeFrame_) {
        1676 this.scheduleEventLoopStart_();
        1677 }
        1678};
        1679
        1680
        1681/**
        1682 * Executes the next task for the current frame. If the current frame has no
        1683 * more tasks, the frame's result will be resolved, returning control to the
        1684 * frame's creator. This will terminate the flow if the completed frame was at
        1685 * the top of the stack.
        1686 * @private
        1687 */
        1688promise.ControlFlow.prototype.runEventLoop_ = function() {
        1689 this.eventLoopTask_ = null;
        1690
        1691 if (this.yieldCount_) {
        1692 return;
        1693 }
        1694
        1695 if (!this.activeFrame_) {
        1696 this.commenceShutdown_();
        1697 return;
        1698 }
        1699
        1700 if (this.activeFrame_.getPendingTask()) {
        1701 return;
        1702 }
        1703
        1704 var task = this.getNextTask_();
        1705 if (!task) {
        1706 return;
        1707 }
        1708
        1709 var activeFrame = this.activeFrame_;
        1710 var scheduleEventLoop = goog.bind(this.scheduleEventLoopStart_, this);
        1711
        1712 var onSuccess = function(value) {
        1713 activeFrame.setPendingTask(null);
        1714 task.setFrame(null);
        1715 task.fulfill(value);
        1716 scheduleEventLoop();
        1717 };
        1718
        1719 var onFailure = function(reason) {
        1720 activeFrame.setPendingTask(null);
        1721 task.setFrame(null);
        1722 task.reject(reason);
        1723 scheduleEventLoop();
        1724 };
        1725
        1726 activeFrame.setPendingTask(task);
        1727 var frame = new Frame(this);
        1728 task.setFrame(frame);
        1729 this.runInFrame_(frame, task.execute, function(result) {
        1730 promise.asap(result, onSuccess, onFailure);
        1731 }, onFailure, true);
        1732};
        1733
        1734
        1735/**
        1736 * @return {Task} The next task to execute, or
        1737 * {@code null} if a frame was resolved.
        1738 * @private
        1739 */
        1740promise.ControlFlow.prototype.getNextTask_ = function() {
        1741 var frame = this.activeFrame_;
        1742 var firstChild = frame.getFirstChild();
        1743 if (!firstChild) {
        1744 if (!frame.pendingCallback && !frame.isBlocked_) {
        1745 this.resolveFrame_(frame);
        1746 }
        1747 return null;
        1748 }
        1749
        1750 if (firstChild instanceof Frame) {
        1751 this.activeFrame_ = firstChild;
        1752 return this.getNextTask_();
        1753 }
        1754
        1755 frame.removeChild(firstChild);
        1756 if (!firstChild.isPending()) {
        1757 return this.getNextTask_();
        1758 }
        1759 return firstChild;
        1760};
        1761
        1762
        1763/**
        1764 * @param {!Frame} frame The frame to resolve.
        1765 * @private
        1766 */
        1767promise.ControlFlow.prototype.resolveFrame_ = function(frame) {
        1768 if (this.activeFrame_ === frame) {
        1769 this.activeFrame_ = frame.getParent();
        1770 }
        1771
        1772 if (frame.getParent()) {
        1773 frame.getParent().removeChild(frame);
        1774 }
        1775 frame.emit(Frame.CLOSE_EVENT);
        1776
        1777 if (!this.activeFrame_) {
        1778 this.commenceShutdown_();
        1779 } else {
        1780 this.scheduleEventLoopStart_();
        1781 }
        1782};
        1783
        1784
        1785/**
        1786 * Aborts the current frame. The frame, and all of the tasks scheduled within it
        1787 * will be discarded. If this instance does not have an active frame, it will
        1788 * immediately terminate all execution.
        1789 * @param {*} error The reason the frame is being aborted; typically either
        1790 * an Error or string.
        1791 * @param {Frame=} opt_frame The frame to abort; will use the
        1792 * currently active frame if not specified.
        1793 * @private
        1794 */
        1795promise.ControlFlow.prototype.abortFrame_ = function(error, opt_frame) {
        1796 if (!this.activeFrame_) {
        1797 this.abortNow_(error);
        1798 return;
        1799 }
        1800
        1801 // Frame parent is always another frame, but the compiler is not smart
        1802 // enough to recognize this.
        1803 var parent = /** @type {Frame} */ (
        1804 this.activeFrame_.getParent());
        1805 if (parent) {
        1806 parent.removeChild(this.activeFrame_);
        1807 }
        1808
        1809 var frame = this.activeFrame_;
        1810 this.activeFrame_ = parent;
        1811 frame.abort(error);
        1812};
        1813
        1814
        1815/**
        1816 * Executes a function within a specific frame. If the function does not
        1817 * schedule any new tasks, the frame will be discarded and the function's result
        1818 * returned passed to the given callback immediately. Otherwise, the callback
        1819 * will be invoked once all of the tasks scheduled within the function have been
        1820 * completed. If the frame is aborted, the `errback` will be invoked with the
        1821 * offending error.
        1822 *
        1823 * @param {!Frame} newFrame The frame to use.
        1824 * @param {!Function} fn The function to execute.
        1825 * @param {function(T)} callback The function to call with a successful result.
        1826 * @param {function(*)} errback The function to call if there is an error.
        1827 * @param {boolean=} opt_isTask Whether the function is a task and the frame
        1828 * should be immediately activated to capture subtasks and errors.
        1829 * @throws {Error} If this function is invoked while another call to this
        1830 * function is present on the stack.
        1831 * @template T
        1832 * @private
        1833 */
        1834promise.ControlFlow.prototype.runInFrame_ = function(
        1835 newFrame, fn, callback, errback, opt_isTask) {
        1836 asserts.assert(
        1837 !this.runningFrame_, 'unexpected recursive call to runInFrame');
        1838
        1839 var self = this,
        1840 oldFrame = this.activeFrame_;
        1841
        1842 try {
        1843 if (this.activeFrame_ !== newFrame && !newFrame.getParent()) {
        1844 this.activeFrame_.addChild(newFrame);
        1845 }
        1846
        1847 // Activate the new frame to force tasks to be treated as sub-tasks of
        1848 // the parent frame.
        1849 if (opt_isTask) {
        1850 this.activeFrame_ = newFrame;
        1851 }
        1852
        1853 try {
        1854 this.runningFrame_ = newFrame;
        1855 this.schedulingFrame_ = newFrame;
        1856 activeFlows.push(this);
        1857 var result = fn();
        1858 } finally {
        1859 activeFlows.pop();
        1860 this.schedulingFrame_ = null;
        1861
        1862 // `newFrame` should only be considered the running frame when it is
        1863 // actually executing. After it finishes, set top of stack to its parent
        1864 // so any failures/interrupts that occur while processing newFrame's
        1865 // result are handled there.
        1866 this.runningFrame_ = newFrame.parent_;
        1867 }
        1868 newFrame.isLocked_ = true;
        1869
        1870 // If there was nothing scheduled in the new frame we can discard the
        1871 // frame and return immediately.
        1872 if (isCloseable(newFrame) && (!opt_isTask || !promise.isPromise(result))) {
        1873 removeNewFrame();
        1874 callback(result);
        1875 return;
        1876 }
        1877
        1878 // If the executed function returned a promise, wait for it to resolve. If
        1879 // there is nothing scheduled in the frame, go ahead and discard it.
        1880 // Otherwise, we wait for the frame to be closed out by the event loop.
        1881 var shortCircuitTask;
        1882 if (promise.isPromise(result)) {
        1883 newFrame.isBlocked_ = true;
        1884 var onResolve = function() {
        1885 newFrame.isBlocked_ = false;
        1886 shortCircuitTask = new MicroTask(function() {
        1887 if (isCloseable(newFrame)) {
        1888 removeNewFrame();
        1889 callback(result);
        1890 }
        1891 });
        1892 };
        1893 /** @type {Thenable} */(result).then(onResolve, onResolve);
        1894
        1895 // If the result is a thenable, attach a listener to silence any unhandled
        1896 // rejection warnings. This is safe because we *will* handle it once the
        1897 // frame has completed.
        1898 } else if (promise.Thenable.isImplementation(result)) {
        1899 /** @type {!promise.Thenable} */(result).thenCatch(goog.nullFunction);
        1900 }
        1901
        1902 newFrame.once(Frame.CLOSE_EVENT, function() {
        1903 shortCircuitTask && shortCircuitTask.cancel();
        1904 if (isCloseable(newFrame)) {
        1905 removeNewFrame();
        1906 }
        1907 callback(result);
        1908 }).once(Frame.ERROR_EVENT, function(reason) {
        1909 shortCircuitTask && shortCircuitTask.cancel();
        1910 if (promise.Thenable.isImplementation(result) && result.isPending()) {
        1911 result.cancel(reason);
        1912 }
        1913 errback(reason);
        1914 });
        1915 } catch (ex) {
        1916 removeNewFrame(ex);
        1917 errback(ex);
        1918 } finally {
        1919 // No longer running anything, clear the reference.
        1920 this.runningFrame_ = null;
        1921 }
        1922
        1923 function isCloseable(frame) {
        1924 return (!frame.children_ || !frame.children_.length)
        1925 && !frame.pendingRejection;
        1926 }
        1927
        1928 /**
        1929 * @param {*=} opt_err If provided, the reason that the frame was removed.
        1930 */
        1931 function removeNewFrame(opt_err) {
        1932 var parent = newFrame.getParent();
        1933 if (parent) {
        1934 parent.removeChild(newFrame);
        1935 asyncRun(function() {
        1936 if (isCloseable(parent) && parent !== self.activeFrame_) {
        1937 parent.emit(Frame.CLOSE_EVENT);
        1938 }
        1939 });
        1940 self.scheduleEventLoopStart_();
        1941 }
        1942
        1943 if (opt_err) {
        1944 newFrame.cancelRemainingTasks(promise.CancellationError.wrap(
        1945 opt_err, 'Tasks cancelled due to uncaught error'));
        1946 }
        1947 self.activeFrame_ = oldFrame;
        1948 }
        1949};
        1950
        1951
        1952/**
        1953 * Commences the shutdown sequence for this instance. After one turn of the
        1954 * event loop, this object will emit the
        1955 * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event to signal
        1956 * listeners that it has completed. During this wait, if another task is
        1957 * scheduled, the shutdown will be aborted.
        1958 * @private
        1959 */
        1960promise.ControlFlow.prototype.commenceShutdown_ = function() {
        1961 if (!this.shutdownTask_) {
        1962 // Go ahead and stop the event loop now. If we're in here, then there are
        1963 // no more frames with tasks to execute. If we waited to cancel the event
        1964 // loop in our timeout below, the event loop could trigger *before* the
        1965 // timeout, generating an error from there being no frames.
        1966 // If #execute is called before the timeout below fires, it will cancel
        1967 // the timeout and restart the event loop.
        1968 this.cancelEventLoop_();
        1969 this.shutdownTask_ = new MicroTask(this.shutdown_, this);
        1970 }
        1971};
        1972
        1973
        1974/** @private */
        1975promise.ControlFlow.prototype.cancelHold_ = function() {
        1976 if (this.hold_) {
        1977 clearInterval(this.hold_);
        1978 this.hold_ = null;
        1979 }
        1980};
        1981
        1982
        1983/** @private */
        1984promise.ControlFlow.prototype.shutdown_ = function() {
        1985 this.cancelHold_();
        1986 this.shutdownTask_ = null;
        1987 this.emit(promise.ControlFlow.EventType.IDLE);
        1988};
        1989
        1990
        1991/**
        1992 * Cancels the shutdown sequence if it is currently scheduled.
        1993 * @private
        1994 */
        1995promise.ControlFlow.prototype.cancelShutdown_ = function() {
        1996 if (this.shutdownTask_) {
        1997 this.shutdownTask_.cancel();
        1998 this.shutdownTask_ = null;
        1999 }
        2000};
        2001
        2002
        2003/**
        2004 * Aborts this flow, abandoning all remaining tasks. If there are
        2005 * listeners registered, an {@code UNCAUGHT_EXCEPTION} will be emitted with the
        2006 * offending {@code error}, otherwise, the {@code error} will be rethrown to the
        2007 * global error handler.
        2008 * @param {*} error Object describing the error that caused the flow to
        2009 * abort; usually either an Error or string value.
        2010 * @private
        2011 */
        2012promise.ControlFlow.prototype.abortNow_ = function(error) {
        2013 this.activeFrame_ = null;
        2014 this.cancelShutdown_();
        2015 this.cancelEventLoop_();
        2016 this.cancelHold_();
        2017
        2018 var listeners = this.listeners(
        2019 promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION);
        2020 if (!listeners.length) {
        2021 throwException(error);
        2022 } else {
        2023 this.emit(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, error);
        2024 }
        2025};
        2026
        2027
        2028/**
        2029 * Wraps a function to execute as a cancellable micro task.
        2030 * @final
        2031 */
        2032var MicroTask = goog.defineClass(null, {
        2033 /**
        2034 * @param {function(this: THIS)} fn The function to run as a micro task.
        2035 * @param {THIS=} opt_scope The scope to run the function in.
        2036 * @template THIS
        2037 */
        2038 constructor: function(fn, opt_scope) {
        2039 /** @private {boolean} */
        2040 this.cancelled_ = false;
        2041 asyncRun(function() {
        2042 if (!this.cancelled_) {
        2043 fn.call(opt_scope);
        2044 }
        2045 }, this);
        2046 },
        2047
        2048 /**
        2049 * Cancels the execution of this task. Note: this will not prevent the task
        2050 * timer from firing, just the invocation of the wrapped function.
        2051 */
        2052 cancel: function() {
        2053 this.cancelled_ = true;
        2054 }
        2055});
        2056
        2057
        2058/**
        2059 * An execution frame within a {@link webdriver.promise.ControlFlow}. Each
        2060 * frame represents the execution context for either a
        2061 * {@link webdriver.Task} or a callback on a
        2062 * {@link webdriver.promise.Promise}.
        2063 *
        2064 * Each frame may contain sub-frames. If child N is a sub-frame, then the
        2065 * items queued within it are given priority over child N+1.
        2066 *
        2067 * @unrestricted
        2068 * @final
        2069 * @private
        2070 */
        2071var Frame = goog.defineClass(EventEmitter, {
        2072 /**
        2073 * @param {!promise.ControlFlow} flow The flow this instance belongs to.
        2074 */
        2075 constructor: function(flow) {
        2076 EventEmitter.call(this);
        2077 goog.getUid(this);
        2078
        2079 /** @private {!promise.ControlFlow} */
        2080 this.flow_ = flow;
        2081
        2082 /** @private {Frame} */
        2083 this.parent_ = null;
        2084
        2085 /** @private {Array<!(Frame|Task)>} */
        2086 this.children_ = null;
        2087
        2088 /** @private {(Frame|Task)} */
        2089 this.lastInsertedChild_ = null;
        2090
        2091 /**
        2092 * The task currently being executed within this frame.
        2093 * @private {Task}
        2094 */
        2095 this.pendingTask_ = null;
        2096
        2097 /**
        2098 * Whether this frame is currently locked. A locked frame represents an
        2099 * executed function that has scheduled all of its tasks.
        2100 *
        2101 * Once a frame becomes locked, any new frames which are added as children
        2102 * represent interrupts (such as a {@link webdriver.promise.Promise}
        2103 * callback) whose tasks must be given priority over those already scheduled
        2104 * within this frame. For example:
        2105 *
        2106 * var flow = promise.controlFlow();
        2107 * flow.execute('start here', goog.nullFunction).then(function() {
        2108 * flow.execute('this should execute 2nd', goog.nullFunction);
        2109 * });
        2110 * flow.execute('this should execute last', goog.nullFunction);
        2111 *
        2112 * @private {boolean}
        2113 */
        2114 this.isLocked_ = false;
        2115
        2116 /**
        2117 * Whether this frame's completion is blocked on the resolution of a promise
        2118 * returned by its main function.
        2119 * @private
        2120 */
        2121 this.isBlocked_ = false;
        2122
        2123 /**
        2124 * Whether this frame represents a pending callback attached to a
        2125 * {@link webdriver.promise.Promise}.
        2126 * @private {boolean}
        2127 */
        2128 this.pendingCallback = false;
        2129
        2130 /**
        2131 * Whether there are pending unhandled rejections detected within this frame.
        2132 * @private {boolean}
        2133 */
        2134 this.pendingRejection = false;
        2135
        2136 /** @private {promise.CancellationError} */
        2137 this.cancellationError_ = null;
        2138 },
        2139
        2140 statics: {
        2141 /** @const */
        2142 CLOSE_EVENT: 'close',
        2143
        2144 /** @const */
        2145 ERROR_EVENT: 'error',
        2146
        2147 /**
        2148 * @param {!promise.CancellationError} error The cancellation error.
        2149 * @param {!(Frame|Task)} child The child to cancel.
        2150 * @private
        2151 */
        2152 cancelChild_: function(error, child) {
        2153 if (child instanceof Frame) {
        2154 child.cancelRemainingTasks(error);
        2155 } else {
        2156 child.promise.callbacks_ = null;
        2157 child.cancel(error);
        2158 }
        2159 }
        2160 },
        2161
        2162 /** @return {Frame} This frame's parent, if any. */
        2163 getParent: function() {
        2164 return this.parent_;
        2165 },
        2166
        2167 /** @param {Frame} parent This frame's new parent. */
        2168 setParent: function(parent) {
        2169 this.parent_ = parent;
        2170 },
        2171
        2172 /** @return {!Frame} The root of this frame's tree. */
        2173 getRoot: function() {
        2174 var root = this;
        2175 while (root.parent_) {
        2176 root = root.parent_;
        2177 }
        2178 return root;
        2179 },
        2180
        2181 /**
        2182 * Aborts the execution of this frame, cancelling all outstanding tasks
        2183 * scheduled within this frame.
        2184 *
        2185 * @param {*} error The error that triggered this abortion.
        2186 */
        2187 abort: function(error) {
        2188 this.cancellationError_ = promise.CancellationError.wrap(
        2189 error, 'Task discarded due to a previous task failure');
        2190 this.cancelRemainingTasks(this.cancellationError_);
        2191 if (!this.pendingCallback) {
        2192 this.emit(Frame.ERROR_EVENT, error);
        2193 }
        2194 },
        2195
        2196 /**
        2197 * Marks all of the tasks that are descendants of this frame in the execution
        2198 * tree as cancelled. This is necessary for callbacks scheduled asynchronous.
        2199 * For example:
        2200 *
        2201 * var someResult;
        2202 * promise.createFlow(function(flow) {
        2203 * someResult = flow.execute(function() {});
        2204 * throw Error();
        2205 * }).thenCatch(function(err) {
        2206 * console.log('flow failed: ' + err);
        2207 * someResult.then(function() {
        2208 * console.log('task succeeded!');
        2209 * }, function(err) {
        2210 * console.log('task failed! ' + err);
        2211 * });
        2212 * });
        2213 * // flow failed: Error: boom
        2214 * // task failed! CancelledTaskError: Task discarded due to a previous
        2215 * // task failure: Error: boom
        2216 *
        2217 * @param {!promise.CancellationError} reason The cancellation reason.
        2218 */
        2219 cancelRemainingTasks: function(reason) {
        2220 if (this.children_) {
        2221 this.children_.forEach(function(child) {
        2222 Frame.cancelChild_(reason, child);
        2223 });
        2224 }
        2225 },
        2226
        2227 /**
        2228 * @return {Task} The task currently executing
        2229 * within this frame, if any.
        2230 */
        2231 getPendingTask: function() {
        2232 return this.pendingTask_;
        2233 },
        2234
        2235 /**
        2236 * @param {Task} task The task currently
        2237 * executing within this frame, if any.
        2238 */
        2239 setPendingTask: function(task) {
        2240 this.pendingTask_ = task;
        2241 },
        2242
        2243 /**
        2244 * @return {boolean} Whether this frame is empty (has no scheduled tasks or
        2245 * pending callback frames).
        2246 */
        2247 isEmpty: function() {
        2248 return !this.children_ || !this.children_.length;
        2249 },
        2250
        2251 /**
        2252 * Adds a new node to this frame.
        2253 * @param {!(Frame|Task)} node The node to insert.
        2254 */
        2255 addChild: function(node) {
        2256 if (this.cancellationError_) {
        2257 Frame.cancelChild_(this.cancellationError_, node);
        2258 return; // Child will never run, no point keeping a reference.
        2259 }
        2260
        2261 if (!this.children_) {
        2262 this.children_ = [];
        2263 }
        2264
        2265 node.setParent(this);
        2266 if (this.isLocked_ && node instanceof Frame) {
        2267 var index = 0;
        2268 if (this.lastInsertedChild_ instanceof Frame) {
        2269 index = this.children_.indexOf(this.lastInsertedChild_);
        2270 // If the last inserted child into a locked frame is a pending callback,
        2271 // it is an interrupt and the new interrupt must come after it. Otherwise,
        2272 // we have our first interrupt for this frame and it shoudl go before the
        2273 // last inserted child.
        2274 index += (this.lastInsertedChild_.pendingCallback) ? 1 : -1;
        2275 }
        2276 this.children_.splice(Math.max(index, 0), 0, node);
        2277 this.lastInsertedChild_ = node;
        2278 return;
        2279 }
        2280
        2281 this.lastInsertedChild_ = node;
        2282 this.children_.push(node);
        2283 },
        2284
        2285 /**
        2286 * @return {(Frame|Task)} This frame's fist child.
        2287 */
        2288 getFirstChild: function() {
        2289 this.isLocked_ = true;
        2290 return this.children_ && this.children_[0];
        2291 },
        2292
        2293 /**
        2294 * Removes a child from this frame.
        2295 * @param {!(Frame|Task)} child The child to remove.
        2296 */
        2297 removeChild: function(child) {
        2298 goog.asserts.assert(child.parent_ === this, 'not a child of this frame');
        2299 goog.asserts.assert(this.children_ !== null, 'frame has no children!');
        2300 var index = this.children_.indexOf(child);
        2301 child.setParent(null);
        2302 this.children_.splice(index, 1);
        2303 if (this.lastInsertedChild_ === child) {
        2304 this.lastInsertedChild_ = this.children_[index - 1] || null;
        2305 }
        2306 if (!this.children_.length) {
        2307 this.children_ = null;
        2308 }
        2309 },
        2310
        2311 /** @override */
        2312 toString: function() {
        2313 return 'Frame::' + goog.getUid(this);
        2314 }
        2315});
        2316
        2317
        2318/**
        2319 * A task to be executed by a {@link webdriver.promise.ControlFlow}.
        2320 *
        2321 * @unrestricted
        2322 * @final
        2323 */
        2324var Task = goog.defineClass(promise.Deferred, {
        2325 /**
        2326 * @param {!promise.ControlFlow} flow The flow this instances belongs
        2327 * to.
        2328 * @param {function(): (T|!promise.Promise<T>)} fn The function to
        2329 * call when the task executes. If it returns a
        2330 * {@link webdriver.promise.Promise}, the flow will wait for it to be
        2331 * resolved before starting the next task.
        2332 * @param {string} description A description of the task for debugging.
        2333 * @constructor
        2334 * @extends {promise.Deferred<T>}
        2335 * @template T
        2336 */
        2337 constructor: function(flow, fn, description) {
        2338 Task.base(this, 'constructor', flow);
        2339 goog.getUid(this);
        2340
        2341 /**
        2342 * @type {function(): (T|!promise.Promise<T>)}
        2343 */
        2344 this.execute = fn;
        2345
        2346 /** @private {string} */
        2347 this.description_ = description;
        2348
        2349 /** @private {Frame} */
        2350 this.parent_ = null;
        2351
        2352 /** @private {Frame} */
        2353 this.frame_ = null;
        2354 },
        2355
        2356 /**
        2357 * @return {Frame} frame The frame used to run this task's
        2358 * {@link #execute} method.
        2359 */
        2360 getFrame: function() {
        2361 return this.frame_;
        2362 },
        2363
        2364 /**
        2365 * @param {Frame} frame The frame used to run this task's
        2366 * {@link #execute} method.
        2367 */
        2368 setFrame: function(frame) {
        2369 this.frame_ = frame;
        2370 },
        2371
        2372 /**
        2373 * @param {Frame} frame The frame this task is scheduled in.
        2374 */
        2375 setParent: function(frame) {
        2376 goog.asserts.assert(goog.isNull(this.parent_) || goog.isNull(frame),
        2377 'parent already set');
        2378 this.parent_ = frame;
        2379 },
        2380
        2381 /** @return {string} This task's description. */
        2382 getDescription: function() {
        2383 return this.description_;
        2384 },
        2385
        2386 /** @override */
        2387 toString: function() {
        2388 return 'Task::' + goog.getUid(this) + '<' + this.description_ + '>';
        2389 }
        2390});
        2391
        2392
        2393/**
        2394 * Manages a callback attached to a {@link webdriver.promise.Promise}. When the
        2395 * promise is resolved, this callback will invoke the appropriate callback
        2396 * function based on the promise's resolved value.
        2397 *
        2398 * @unrestricted
        2399 * @final
        2400 */
        2401var Callback = goog.defineClass(promise.Deferred, {
        2402 /**
        2403 * @param {!promise.Promise} parent The promise this callback is attached to.
        2404 * @param {(function(T): (IThenable<R>|R)|null|undefined)} callback
        2405 * The fulfillment callback.
        2406 * @param {(function(*): (IThenable<R>|R)|null|undefined)} errback
        2407 * The rejection callback.
        2408 * @param {string} name The callback name.
        2409 * @param {!Function} fn The function to use as the top of the stack when
        2410 * recording the callback's creation point.
        2411 * @extends {promise.Deferred<R>}
        2412 * @template T, R
        2413 */
        2414 constructor: function(parent, callback, errback, name, fn) {
        2415 Callback.base(this, 'constructor', parent.flow_);
        2416
        2417 /** @private {(function(T): (IThenable<R>|R)|null|undefined)} */
        2418 this.callback_ = callback;
        2419
        2420 /** @private {(function(*): (IThenable<R>|R)|null|undefined)} */
        2421 this.errback_ = errback;
        2422
        2423 /** @private {!Frame} */
        2424 this.frame_ = new Frame(parent.flow_);
        2425 this.frame_.pendingCallback = true;
        2426
        2427 this.promise.parent_ = parent;
        2428 if (promise.LONG_STACK_TRACES) {
        2429 this.promise.stack_ = promise.captureStackTrace('Promise', name, fn);
        2430 }
        2431 },
        2432
        2433 /**
        2434 * Called by the parent promise when it has been resolved.
        2435 * @param {!PromiseState} state The parent's new state.
        2436 * @param {*} value The parent's new value.
        2437 */
        2438 notify: function(state, value) {
        2439 var callback = this.callback_;
        2440 var fallback = this.fulfill;
        2441 if (state === PromiseState.REJECTED) {
        2442 callback = this.errback_;
        2443 fallback = this.reject;
        2444 }
        2445
        2446 this.frame_.pendingCallback = false;
        2447 if (goog.isFunction(callback)) {
        2448 this.frame_.flow_.runInFrame_(
        2449 this.frame_,
        2450 goog.bind(callback, undefined, value),
        2451 this.fulfill, this.reject);
        2452 } else {
        2453 if (this.frame_.getParent()) {
        2454 this.frame_.getParent().removeChild(this.frame_);
        2455 }
        2456 fallback(value);
        2457 }
        2458 }
        2459});
        2460
        2461
        2462
        2463/**
        2464 * The default flow to use if no others are active.
        2465 * @type {!promise.ControlFlow}
        2466 */
        2467var defaultFlow = new promise.ControlFlow();
        2468
        2469
        2470/**
        2471 * A stack of active control flows, with the top of the stack used to schedule
        2472 * commands. When there are multiple flows on the stack, the flow at index N
        2473 * represents a callback triggered within a task owned by the flow at index
        2474 * N-1.
        2475 * @type {!Array<!promise.ControlFlow>}
        2476 */
        2477var activeFlows = [];
        2478
        2479
        2480/**
        2481 * Changes the default flow to use when no others are active.
        2482 * @param {!promise.ControlFlow} flow The new default flow.
        2483 * @throws {Error} If the default flow is not currently active.
        2484 */
        2485promise.setDefaultFlow = function(flow) {
        2486 if (activeFlows.length) {
        2487 throw Error('You may only change the default flow while it is active');
        2488 }
        2489 defaultFlow = flow;
        2490};
        2491
        2492
        2493/**
        2494 * @return {!promise.ControlFlow} The currently active control flow.
        2495 */
        2496promise.controlFlow = function() {
        2497 return /** @type {!promise.ControlFlow} */ (
        2498 Arrays.peek(activeFlows) || defaultFlow);
        2499};
        2500
        2501
        2502/**
        2503 * Creates a new control flow. The provided callback will be invoked as the
        2504 * first task within the new flow, with the flow as its sole argument. Returns
        2505 * a promise that resolves to the callback result.
        2506 * @param {function(!promise.ControlFlow)} callback The entry point
        2507 * to the newly created flow.
        2508 * @return {!promise.Promise} A promise that resolves to the callback
        2509 * result.
        2510 */
        2511promise.createFlow = function(callback) {
        2512 var flow = new promise.ControlFlow;
        2513 return flow.execute(function() {
        2514 return callback(flow);
        2515 });
        2516};
        2517
        2518
        2519/**
        2520 * Tests is a function is a generator.
        2521 * @param {!Function} fn The function to test.
        2522 * @return {boolean} Whether the function is a generator.
        2523 */
        2524promise.isGenerator = function(fn) {
        2525 return fn.constructor.name === 'GeneratorFunction';
        2526};
        2527
        2528
        2529/**
        2530 * Consumes a {@code GeneratorFunction}. Each time the generator yields a
        2531 * promise, this function will wait for it to be fulfilled before feeding the
        2532 * fulfilled value back into {@code next}. Likewise, if a yielded promise is
        2533 * rejected, the rejection error will be passed to {@code throw}.
        2534 *
        2535 * __Example 1:__ the Fibonacci Sequence.
        2536 *
        2537 * promise.consume(function* fibonacci() {
        2538 * var n1 = 1, n2 = 1;
        2539 * for (var i = 0; i < 4; ++i) {
        2540 * var tmp = yield n1 + n2;
        2541 * n1 = n2;
        2542 * n2 = tmp;
        2543 * }
        2544 * return n1 + n2;
        2545 * }).then(function(result) {
        2546 * console.log(result); // 13
        2547 * });
        2548 *
        2549 * __Example 2:__ a generator that throws.
        2550 *
        2551 * promise.consume(function* () {
        2552 * yield promise.delayed(250).then(function() {
        2553 * throw Error('boom');
        2554 * });
        2555 * }).thenCatch(function(e) {
        2556 * console.log(e.toString()); // Error: boom
        2557 * });
        2558 *
        2559 * @param {!Function} generatorFn The generator function to execute.
        2560 * @param {Object=} opt_self The object to use as "this" when invoking the
        2561 * initial generator.
        2562 * @param {...*} var_args Any arguments to pass to the initial generator.
        2563 * @return {!promise.Promise<?>} A promise that will resolve to the
        2564 * generator's final result.
        2565 * @throws {TypeError} If the given function is not a generator.
        2566 */
        2567promise.consume = function(generatorFn, opt_self, var_args) {
        2568 if (!promise.isGenerator(generatorFn)) {
        2569 throw new TypeError('Input is not a GeneratorFunction: ' +
        2570 generatorFn.constructor.name);
        2571 }
        2572
        2573 var deferred = promise.defer();
        2574 var generator = generatorFn.apply(opt_self, Arrays.slice(arguments, 2));
        2575 callNext();
        2576 return deferred.promise;
        2577
        2578 /** @param {*=} opt_value . */
        2579 function callNext(opt_value) {
        2580 pump(generator.next, opt_value);
        2581 }
        2582
        2583 /** @param {*=} opt_error . */
        2584 function callThrow(opt_error) {
        2585 // Dictionary lookup required because Closure compiler's built-in
        2586 // externs does not include GeneratorFunction.prototype.throw.
        2587 pump(generator['throw'], opt_error);
        2588 }
        2589
        2590 function pump(fn, opt_arg) {
        2591 if (!deferred.isPending()) {
        2592 return; // Defererd was cancelled; silently abort.
        2593 }
        2594
        2595 try {
        2596 var result = fn.call(generator, opt_arg);
        2597 } catch (ex) {
        2598 deferred.reject(ex);
        2599 return;
        2600 }
        2601
        2602 if (result.done) {
        2603 deferred.fulfill(result.value);
        2604 return;
        2605 }
        2606
        2607 promise.asap(result.value, callNext, callThrow);
        2608 }
        2609};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/serializable.js.src.html b/docs/source/lib/webdriver/serializable.js.src.html new file mode 100644 index 0000000..e2a092f --- /dev/null +++ b/docs/source/lib/webdriver/serializable.js.src.html @@ -0,0 +1 @@ +serializable.js

        lib/webdriver/serializable.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.Serializable');
        19
        20
        21
        22/**
        23 * Defines an object that can be asynchronously serialized to its WebDriver
        24 * wire representation.
        25 *
        26 * @constructor
        27 * @template T
        28 */
        29webdriver.Serializable = function() {};
        30
        31
        32/**
        33 * Returns either this instance's serialized represention, if immediately
        34 * available, or a promise for its serialized representation. This function is
        35 * conceptually equivalent to objects that have a {@code toJSON()} property,
        36 * except the serialize() result may be a promise or an object containing a
        37 * promise (which are not directly JSON friendly).
        38 *
        39 * @return {!(T|IThenable.<!T>)} This instance's serialized wire format.
        40 */
        41webdriver.Serializable.prototype.serialize = goog.abstractMethod;
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/session.js.src.html b/docs/source/lib/webdriver/session.js.src.html index bc91970..488aeef 100644 --- a/docs/source/lib/webdriver/session.js.src.html +++ b/docs/source/lib/webdriver/session.js.src.html @@ -1 +1 @@ -session.js

        lib/webdriver/session.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15goog.provide('webdriver.Session');
        16
        17goog.require('webdriver.Capabilities');
        18
        19
        20
        21/**
        22 * Contains information about a WebDriver session.
        23 * @param {string} id The session ID.
        24 * @param {!(Object|webdriver.Capabilities)} capabilities The session
        25 * capabilities.
        26 * @constructor
        27 */
        28webdriver.Session = function(id, capabilities) {
        29
        30 /** @private {string} */
        31 this.id_ = id;
        32
        33 /** @private {!webdriver.Capabilities} */
        34 this.caps_ = new webdriver.Capabilities().merge(capabilities);
        35};
        36
        37
        38/**
        39 * @return {string} This session's ID.
        40 */
        41webdriver.Session.prototype.getId = function() {
        42 return this.id_;
        43};
        44
        45
        46/**
        47 * @return {!webdriver.Capabilities} This session's capabilities.
        48 */
        49webdriver.Session.prototype.getCapabilities = function() {
        50 return this.caps_;
        51};
        52
        53
        54/**
        55 * Retrieves the value of a specific capability.
        56 * @param {string} key The capability to retrieve.
        57 * @return {*} The capability value.
        58 */
        59webdriver.Session.prototype.getCapability = function(key) {
        60 return this.caps_.get(key);
        61};
        62
        63
        64/**
        65 * Returns the JSON representation of this object, which is just the string
        66 * session ID.
        67 * @return {string} The JSON representation of this Session.
        68 */
        69webdriver.Session.prototype.toJSON = function() {
        70 return this.getId();
        71};
        \ No newline at end of file +session.js

        lib/webdriver/session.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.Session');
        19
        20goog.require('webdriver.Capabilities');
        21
        22
        23
        24/**
        25 * Contains information about a WebDriver session.
        26 * @param {string} id The session ID.
        27 * @param {!(Object|webdriver.Capabilities)} capabilities The session
        28 * capabilities.
        29 * @constructor
        30 */
        31webdriver.Session = function(id, capabilities) {
        32
        33 /** @private {string} */
        34 this.id_ = id;
        35
        36 /** @private {!webdriver.Capabilities} */
        37 this.caps_ = new webdriver.Capabilities().merge(capabilities);
        38};
        39
        40
        41/**
        42 * @return {string} This session's ID.
        43 */
        44webdriver.Session.prototype.getId = function() {
        45 return this.id_;
        46};
        47
        48
        49/**
        50 * @return {!webdriver.Capabilities} This session's capabilities.
        51 */
        52webdriver.Session.prototype.getCapabilities = function() {
        53 return this.caps_;
        54};
        55
        56
        57/**
        58 * Retrieves the value of a specific capability.
        59 * @param {string} key The capability to retrieve.
        60 * @return {*} The capability value.
        61 */
        62webdriver.Session.prototype.getCapability = function(key) {
        63 return this.caps_.get(key);
        64};
        65
        66
        67/**
        68 * Returns the JSON representation of this object, which is just the string
        69 * session ID.
        70 * @return {string} The JSON representation of this Session.
        71 */
        72webdriver.Session.prototype.toJSON = function() {
        73 return this.getId();
        74};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/stacktrace.js.src.html b/docs/source/lib/webdriver/stacktrace.js.src.html index 332f1ce..cf9311b 100644 --- a/docs/source/lib/webdriver/stacktrace.js.src.html +++ b/docs/source/lib/webdriver/stacktrace.js.src.html @@ -1 +1 @@ -stacktrace.js

        lib/webdriver/stacktrace.js

        1// Copyright 2009 The Closure Library Authors. All Rights Reserved.
        2// Copyright 2012 Selenium comitters
        3// Copyright 2012 Software Freedom Conservancy
        4//
        5// Licensed under the Apache License, Version 2.0 (the "License");
        6// you may not use this file except in compliance with the License.
        7// You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing, software
        12// distributed under the License is distributed on an "AS-IS" BASIS,
        13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        14// See the License for the specific language governing permissions and
        15// limitations under the License.
        16
        17/**
        18 * @fileoverview Tools for parsing and pretty printing error stack traces. This
        19 * file is based on goog.testing.stacktrace.
        20 */
        21
        22goog.provide('webdriver.stacktrace');
        23goog.provide('webdriver.stacktrace.Snapshot');
        24
        25goog.require('goog.array');
        26goog.require('goog.string');
        27goog.require('goog.userAgent');
        28
        29
        30
        31/**
        32 * Stores a snapshot of the stack trace at the time this instance was created.
        33 * The stack trace will always be adjusted to exclude this function call.
        34 * @param {number=} opt_slice The number of frames to remove from the top of
        35 * the generated stack trace.
        36 * @constructor
        37 */
        38webdriver.stacktrace.Snapshot = function(opt_slice) {
        39
        40 /** @private {number} */
        41 this.slice_ = opt_slice || 0;
        42
        43 var error;
        44 if (webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_) {
        45 error = Error();
        46 Error.captureStackTrace(error, webdriver.stacktrace.Snapshot);
        47 } else {
        48 // Remove 1 extra frame for the call to this constructor.
        49 this.slice_ += 1;
        50 // IE will only create a stack trace when the Error is thrown.
        51 // We use null.x() to throw an exception instead of throw this.error_
        52 // because the closure compiler may optimize throws to a function call
        53 // in an attempt to minimize the binary size which in turn has the side
        54 // effect of adding an unwanted stack frame.
        55 try {
        56 null.x();
        57 } catch (e) {
        58 error = e;
        59 }
        60 }
        61
        62 /**
        63 * The error's stacktrace. This must be accessed immediately to ensure Opera
        64 * computes the context correctly.
        65 * @private {string}
        66 */
        67 this.stack_ = webdriver.stacktrace.getStack_(error);
        68};
        69
        70
        71/**
        72 * Whether the current environment supports the Error.captureStackTrace
        73 * function (as of 10/17/2012, only V8).
        74 * @private {boolean}
        75 * @const
        76 */
        77webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ =
        78 goog.isFunction(Error.captureStackTrace);
        79
        80
        81/**
        82 * Whether the current browser supports stack traces.
        83 *
        84 * @type {boolean}
        85 * @const
        86 */
        87webdriver.stacktrace.BROWSER_SUPPORTED =
        88 webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ || (function() {
        89 try {
        90 throw Error();
        91 } catch (e) {
        92 return !!e.stack;
        93 }
        94 })();
        95
        96
        97/**
        98 * The parsed stack trace. This list is lazily generated the first time it is
        99 * accessed.
        100 * @private {Array.<!webdriver.stacktrace.Frame>}
        101 */
        102webdriver.stacktrace.Snapshot.prototype.parsedStack_ = null;
        103
        104
        105/**
        106 * @return {!Array.<!webdriver.stacktrace.Frame>} The parsed stack trace.
        107 */
        108webdriver.stacktrace.Snapshot.prototype.getStacktrace = function() {
        109 if (goog.isNull(this.parsedStack_)) {
        110 this.parsedStack_ = webdriver.stacktrace.parse_(this.stack_);
        111 if (this.slice_) {
        112 this.parsedStack_ = goog.array.slice(this.parsedStack_, this.slice_);
        113 }
        114 delete this.slice_;
        115 delete this.stack_;
        116 }
        117 return this.parsedStack_;
        118};
        119
        120
        121
        122/**
        123 * Class representing one stack frame.
        124 * @param {(string|undefined)} context Context object, empty in case of global
        125 * functions or if the browser doesn't provide this information.
        126 * @param {(string|undefined)} name Function name, empty in case of anonymous
        127 * functions.
        128 * @param {(string|undefined)} alias Alias of the function if available. For
        129 * example the function name will be 'c' and the alias will be 'b' if the
        130 * function is defined as <code>a.b = function c() {};</code>.
        131 * @param {(string|undefined)} path File path or URL including line number and
        132 * optionally column number separated by colons.
        133 * @constructor
        134 */
        135webdriver.stacktrace.Frame = function(context, name, alias, path) {
        136
        137 /** @private {string} */
        138 this.context_ = context || '';
        139
        140 /** @private {string} */
        141 this.name_ = name || '';
        142
        143 /** @private {string} */
        144 this.alias_ = alias || '';
        145
        146 /** @private {string} */
        147 this.path_ = path || '';
        148
        149 /** @private {string} */
        150 this.url_ = this.path_;
        151
        152 /** @private {number} */
        153 this.line_ = -1;
        154
        155 /** @private {number} */
        156 this.column_ = -1;
        157
        158 if (path) {
        159 var match = /:(\d+)(?::(\d+))?$/.exec(path);
        160 if (match) {
        161 this.line_ = Number(match[1]);
        162 this.column = Number(match[2] || -1);
        163 this.url_ = path.substr(0, match.index);
        164 }
        165 }
        166};
        167
        168
        169/**
        170 * Constant for an anonymous frame.
        171 * @private {!webdriver.stacktrace.Frame}
        172 * @const
        173 */
        174webdriver.stacktrace.ANONYMOUS_FRAME_ =
        175 new webdriver.stacktrace.Frame('', '', '', '');
        176
        177
        178/**
        179 * @return {string} The function name or empty string if the function is
        180 * anonymous and the object field which it's assigned to is unknown.
        181 */
        182webdriver.stacktrace.Frame.prototype.getName = function() {
        183 return this.name_;
        184};
        185
        186
        187/**
        188 * @return {string} The url or empty string if it is unknown.
        189 */
        190webdriver.stacktrace.Frame.prototype.getUrl = function() {
        191 return this.url_;
        192};
        193
        194
        195/**
        196 * @return {number} The line number if known or -1 if it is unknown.
        197 */
        198webdriver.stacktrace.Frame.prototype.getLine = function() {
        199 return this.line_;
        200};
        201
        202
        203/**
        204 * @return {number} The column number if known and -1 if it is unknown.
        205 */
        206webdriver.stacktrace.Frame.prototype.getColumn = function() {
        207 return this.column_;
        208};
        209
        210
        211/**
        212 * @return {boolean} Whether the stack frame contains an anonymous function.
        213 */
        214webdriver.stacktrace.Frame.prototype.isAnonymous = function() {
        215 return !this.name_ || this.context_ == '[object Object]';
        216};
        217
        218
        219/**
        220 * Converts this frame to its string representation using V8's stack trace
        221 * format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
        222 * @return {string} The string representation of this frame.
        223 * @override
        224 */
        225webdriver.stacktrace.Frame.prototype.toString = function() {
        226 var context = this.context_;
        227 if (context && context !== 'new ') {
        228 context += '.';
        229 }
        230 context += this.name_;
        231 context += this.alias_ ? ' [as ' + this.alias_ + ']' : '';
        232
        233 var path = this.path_ || '<anonymous>';
        234 return ' at ' + (context ? context + ' (' + path + ')' : path);
        235};
        236
        237
        238/**
        239 * Maximum length of a string that can be matched with a RegExp on
        240 * Firefox 3x. Exceeding this approximate length will cause string.match
        241 * to exceed Firefox's stack quota. This situation can be encountered
        242 * when goog.globalEval is invoked with a long argument; such as
        243 * when loading a module.
        244 * @private {number}
        245 * @const
        246 */
        247webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;
        248
        249
        250/**
        251 * RegExp pattern for JavaScript identifiers. We don't support Unicode
        252 * identifiers defined in ECMAScript v3.
        253 * @private {string}
        254 * @const
        255 */
        256webdriver.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';
        257
        258
        259/**
        260 * Pattern for a matching the type on a fully-qualified name. Forms an
        261 * optional sub-match on the type. For example, in "foo.bar.baz", will match on
        262 * "foo.bar".
        263 * @private {string}
        264 * @const
        265 */
        266webdriver.stacktrace.CONTEXT_PATTERN_ =
        267 '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +
        268 '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.';
        269
        270
        271/**
        272 * Pattern for matching a fully qualified name. Will create two sub-matches:
        273 * the type (optional), and the name. For example, in "foo.bar.baz", will
        274 * match on ["foo.bar", "baz"].
        275 * @private {string}
        276 * @const
        277 */
        278webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ =
        279 '(?:' + webdriver.stacktrace.CONTEXT_PATTERN_ + ')?' +
        280 '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')';
        281
        282
        283/**
        284 * RegExp pattern for function name alias in the V8 stack trace.
        285 * @private {string}
        286 * @const
        287 */
        288webdriver.stacktrace.V8_ALIAS_PATTERN_ =
        289 '(?: \\[as (' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';
        290
        291
        292/**
        293 * RegExp pattern for function names and constructor calls in the V8 stack
        294 * trace.
        295 * @private {string}
        296 * @const
        297 */
        298webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ =
        299 '(?:' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '|<anonymous>)';
        300
        301
        302/**
        303 * RegExp pattern for the context of a function call in V8. Creates two
        304 * submatches, only one of which will ever match: either the namespace
        305 * identifier (with optional "new" keyword in the case of a constructor call),
        306 * or just the "new " phrase for a top level constructor call.
        307 * @private {string}
        308 * @const
        309 */
        310webdriver.stacktrace.V8_CONTEXT_PATTERN_ =
        311 '(?:((?:new )?(?:\\[object Object\\]|' +
        312 webdriver.stacktrace.IDENTIFIER_PATTERN_ +
        313 '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)' +
        314 ')\\.|(new ))';
        315
        316
        317/**
        318 * RegExp pattern for function call in the V8 stack trace.
        319 * Creates 3 submatches with context object (optional), function name and
        320 * function alias (optional).
        321 * @private {string}
        322 * @const
        323 */
        324webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ =
        325 ' (?:' + webdriver.stacktrace.V8_CONTEXT_PATTERN_ + ')?' +
        326 '(' + webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +
        327 webdriver.stacktrace.V8_ALIAS_PATTERN_;
        328
        329
        330/**
        331 * RegExp pattern for an URL + position inside the file.
        332 * @private {string}
        333 * @const
        334 */
        335webdriver.stacktrace.URL_PATTERN_ =
        336 '((?:http|https|file)://[^\\s]+|javascript:.*)';
        337
        338
        339/**
        340 * RegExp pattern for a location string in a V8 stack frame. Creates two
        341 * submatches for the location, one for enclosed in parentheticals and on
        342 * where the location appears alone (which will only occur if the location is
        343 * the only information in the frame).
        344 * @private {string}
        345 * @const
        346 * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
        347 */
        348webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))';
        349
        350
        351/**
        352 * Regular expression for parsing one stack frame in V8.
        353 * @private {!RegExp}
        354 * @const
        355 */
        356webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^ at' +
        357 '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +
        358 webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$');
        359
        360
        361/**
        362 * RegExp pattern for function names in the Firefox stack trace.
        363 * Firefox has extended identifiers to deal with inner functions and anonymous
        364 * functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9
        365 * @private {string}
        366 * @const
        367 */
        368webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ =
        369 webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*';
        370
        371
        372/**
        373 * RegExp pattern for function call in the Firefox stack trace.
        374 * Creates a submatch for the function name.
        375 * @private {string}
        376 * @const
        377 */
        378webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =
        379 '(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' +
        380 '(?:\\(.*\\))?@';
        381
        382
        383/**
        384 * Regular expression for parsing one stack frame in Firefox.
        385 * @private {!RegExp}
        386 * @const
        387 */
        388webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +
        389 webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +
        390 '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$');
        391
        392
        393/**
        394 * RegExp pattern for an anonymous function call in an Opera stack frame.
        395 * Creates 2 (optional) submatches: the context object and function name.
        396 * @private {string}
        397 * @const
        398 */
        399webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =
        400 '<anonymous function(?:\\: ' +
        401 webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ + ')?>';
        402
        403
        404/**
        405 * RegExp pattern for a function call in an Opera stack frame.
        406 * Creates 3 (optional) submatches: the function name (if not anonymous),
        407 * the aliased context object and the function name (if anonymous).
        408 * @private {string}
        409 * @const
        410 */
        411webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ =
        412 '(?:(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')|' +
        413 webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +
        414 ')(?:\\(.*\\)))?@';
        415
        416
        417/**
        418 * Regular expression for parsing on stack frame in Opera 11.68+
        419 * @private {!RegExp}
        420 * @const
        421 */
        422webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp('^' +
        423 webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +
        424 webdriver.stacktrace.URL_PATTERN_ + '?$');
        425
        426
        427/**
        428 * RegExp pattern for function call in a Chakra (IE) stack trace. This
        429 * expression allows for identifiers like 'Anonymous function', 'eval code',
        430 * and 'Global code'.
        431 * @private {string}
        432 * @const
        433 */
        434webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ = '(' +
        435 webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';
        436
        437
        438/**
        439 * Regular expression for parsing on stack frame in Chakra (IE).
        440 * @private {!RegExp}
        441 * @const
        442 */
        443webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^ at ' +
        444 webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ +
        445 '\\s*(?:\\((.*)\\))$');
        446
        447
        448/**
        449 * Placeholder for an unparsable frame in a stack trace generated by
        450 * {@link goog.testing.stacktrace}.
        451 * @private {string}
        452 * @const
        453 */
        454webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)';
        455
        456
        457/**
        458 * Representation of an anonymous frame in a stack trace generated by
        459 * {@link goog.testing.stacktrace}.
        460 * @private {string}
        461 * @const
        462 */
        463webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous';
        464
        465
        466/**
        467 * Pattern for a function call in a Closure stack trace. Creates three optional
        468 * submatches: the context, function name, and alias.
        469 * @private {string}
        470 * @const
        471 */
        472webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ =
        473 webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ +
        474 '(?:\\(.*\\))?' + // Ignore arguments if present.
        475 webdriver.stacktrace.V8_ALIAS_PATTERN_;
        476
        477
        478/**
        479 * Regular expression for parsing a stack frame generated by Closure's
        480 * {@link goog.testing.stacktrace}.
        481 * @private {!RegExp}
        482 * @const
        483 */
        484webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' +
        485 '(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ +
        486 '(?: at )?)?' +
        487 '(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$');
        488
        489
        490/**
        491 * Parses one stack frame.
        492 * @param {string} frameStr The stack frame as string.
        493 * @return {webdriver.stacktrace.Frame} Stack frame object or null if the
        494 * parsing failed.
        495 * @private
        496 */
        497webdriver.stacktrace.parseStackFrame_ = function(frameStr) {
        498 var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_);
        499 if (m) {
        500 return new webdriver.stacktrace.Frame(
        501 m[1] || m[2], m[3], m[4], m[5] || m[6]);
        502 }
        503
        504 if (frameStr.length >
        505 webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {
        506 return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr);
        507 }
        508
        509 m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);
        510 if (m) {
        511 return new webdriver.stacktrace.Frame('', m[1], '', m[2]);
        512 }
        513
        514 m = frameStr.match(webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_);
        515 if (m) {
        516 return new webdriver.stacktrace.Frame(m[2], m[1] || m[3], '', m[4]);
        517 }
        518
        519 m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_);
        520 if (m) {
        521 return new webdriver.stacktrace.Frame('', m[1], '', m[2]);
        522 }
        523
        524 if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ ||
        525 frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) {
        526 return webdriver.stacktrace.ANONYMOUS_FRAME_;
        527 }
        528
        529 m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_);
        530 if (m) {
        531 return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]);
        532 }
        533
        534 return null;
        535};
        536
        537
        538/**
        539 * Parses a long firefox stack frame.
        540 * @param {string} frameStr The stack frame as string.
        541 * @return {!webdriver.stacktrace.Frame} Stack frame object.
        542 * @private
        543 */
        544webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {
        545 var firstParen = frameStr.indexOf('(');
        546 var lastAmpersand = frameStr.lastIndexOf('@');
        547 var lastColon = frameStr.lastIndexOf(':');
        548 var functionName = '';
        549 if ((firstParen >= 0) && (firstParen < lastAmpersand)) {
        550 functionName = frameStr.substring(0, firstParen);
        551 }
        552 var loc = '';
        553 if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {
        554 loc = frameStr.substring(lastAmpersand + 1);
        555 }
        556 return new webdriver.stacktrace.Frame('', functionName, '', loc);
        557};
        558
        559
        560/**
        561 * Get an error's stack trace with the error string trimmed.
        562 * V8 prepends the string representation of an error to its stack trace.
        563 * This function trims the string so that the stack trace can be parsed
        564 * consistently with the other JS engines.
        565 * @param {!(Error|goog.testing.JsUnitException)} error The error.
        566 * @return {string} The stack trace string.
        567 * @private
        568 */
        569webdriver.stacktrace.getStack_ = function(error) {
        570 var stack = error.stack || error.stackTrace || '';
        571 var errorStr = error + '\n';
        572 if (goog.string.startsWith(stack, errorStr)) {
        573 stack = stack.substring(errorStr.length);
        574 }
        575 return stack;
        576};
        577
        578
        579/**
        580 * Formats an error's stack trace.
        581 * @param {!(Error|goog.testing.JsUnitException)} error The error to format.
        582 * @return {!(Error|goog.testing.JsUnitException)} The formatted error.
        583 */
        584webdriver.stacktrace.format = function(error) {
        585 var stack = webdriver.stacktrace.getStack_(error);
        586 var frames = webdriver.stacktrace.parse_(stack);
        587
        588 // Older versions of IE simply return [object Error] for toString(), so
        589 // only use that as a last resort.
        590 var errorStr = '';
        591 if (error.message) {
        592 errorStr = (error.name ? error.name + ': ' : '') + error.message;
        593 } else {
        594 errorStr = error.toString();
        595 }
        596
        597 // Ensure the error is in the V8 style with the error's string representation
        598 // prepended to the stack.
        599 error.stack = errorStr + '\n' + frames.join('\n');
        600 return error;
        601};
        602
        603
        604/**
        605 * Parses an Error object's stack trace.
        606 * @param {string} stack The stack trace.
        607 * @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The
        608 * unrecognized frames will be nulled out.
        609 * @private
        610 */
        611webdriver.stacktrace.parse_ = function(stack) {
        612 if (!stack) {
        613 return [];
        614 }
        615
        616 var lines = stack.
        617 replace(/\s*$/, '').
        618 split('\n');
        619 var frames = [];
        620 for (var i = 0; i < lines.length; i++) {
        621 var frame = webdriver.stacktrace.parseStackFrame_(lines[i]);
        622 // The first two frames will be:
        623 // webdriver.stacktrace.Snapshot()
        624 // webdriver.stacktrace.get()
        625 // In the case of Opera, sometimes an extra frame is injected in the next
        626 // frame with a reported line number of zero. The next line detects that
        627 // case and skips that frame.
        628 if (!(goog.userAgent.OPERA && i == 2 && frame.getLine() == 0)) {
        629 frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_);
        630 }
        631 }
        632 return frames;
        633};
        634
        635
        636/**
        637 * Gets the native stack trace if available otherwise follows the call chain.
        638 * The generated trace will exclude all frames up to and including the call to
        639 * this function.
        640 * @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace.
        641 */
        642webdriver.stacktrace.get = function() {
        643 return new webdriver.stacktrace.Snapshot(1).getStacktrace();
        644};
        \ No newline at end of file +stacktrace.js

        lib/webdriver/stacktrace.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Tools for parsing and pretty printing error stack traces. This
        20 * file is based on goog.testing.stacktrace.
        21 */
        22
        23goog.provide('webdriver.stacktrace');
        24goog.provide('webdriver.stacktrace.Snapshot');
        25
        26goog.require('goog.array');
        27goog.require('goog.string');
        28goog.require('goog.userAgent');
        29
        30
        31
        32/**
        33 * Stores a snapshot of the stack trace at the time this instance was created.
        34 * The stack trace will always be adjusted to exclude this function call.
        35 * @param {number=} opt_slice The number of frames to remove from the top of
        36 * the generated stack trace.
        37 * @constructor
        38 */
        39webdriver.stacktrace.Snapshot = function(opt_slice) {
        40
        41 /** @private {number} */
        42 this.slice_ = opt_slice || 0;
        43
        44 var error;
        45 if (webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_) {
        46 error = Error();
        47 Error.captureStackTrace(error, webdriver.stacktrace.Snapshot);
        48 } else {
        49 // Remove 1 extra frame for the call to this constructor.
        50 this.slice_ += 1;
        51 // IE will only create a stack trace when the Error is thrown.
        52 // We use null.x() to throw an exception instead of throw this.error_
        53 // because the closure compiler may optimize throws to a function call
        54 // in an attempt to minimize the binary size which in turn has the side
        55 // effect of adding an unwanted stack frame.
        56 try {
        57 null.x();
        58 } catch (e) {
        59 error = e;
        60 }
        61 }
        62
        63 /**
        64 * The error's stacktrace.
        65 * @private {string}
        66 */
        67 this.stack_ = webdriver.stacktrace.getStack(error);
        68};
        69
        70
        71/**
        72 * Whether the current environment supports the Error.captureStackTrace
        73 * function (as of 10/17/2012, only V8).
        74 * @private {boolean}
        75 * @const
        76 */
        77webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ =
        78 goog.isFunction(Error.captureStackTrace);
        79
        80
        81/**
        82 * Whether the current browser supports stack traces.
        83 *
        84 * @type {boolean}
        85 * @const
        86 */
        87webdriver.stacktrace.BROWSER_SUPPORTED =
        88 webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ || (function() {
        89 try {
        90 throw Error();
        91 } catch (e) {
        92 return !!e.stack;
        93 }
        94 })();
        95
        96
        97/**
        98 * The parsed stack trace. This list is lazily generated the first time it is
        99 * accessed.
        100 * @private {Array.<!webdriver.stacktrace.Frame>}
        101 */
        102webdriver.stacktrace.Snapshot.prototype.parsedStack_ = null;
        103
        104
        105/**
        106 * @return {!Array.<!webdriver.stacktrace.Frame>} The parsed stack trace.
        107 */
        108webdriver.stacktrace.Snapshot.prototype.getStacktrace = function() {
        109 if (goog.isNull(this.parsedStack_)) {
        110 this.parsedStack_ = webdriver.stacktrace.parse_(this.stack_);
        111 if (this.slice_) {
        112 this.parsedStack_ = goog.array.slice(this.parsedStack_, this.slice_);
        113 }
        114 delete this.slice_;
        115 delete this.stack_;
        116 }
        117 return this.parsedStack_;
        118};
        119
        120
        121
        122/**
        123 * Class representing one stack frame.
        124 * @param {(string|undefined)} context Context object, empty in case of global
        125 * functions or if the browser doesn't provide this information.
        126 * @param {(string|undefined)} name Function name, empty in case of anonymous
        127 * functions.
        128 * @param {(string|undefined)} alias Alias of the function if available. For
        129 * example the function name will be 'c' and the alias will be 'b' if the
        130 * function is defined as <code>a.b = function c() {};</code>.
        131 * @param {(string|undefined)} path File path or URL including line number and
        132 * optionally column number separated by colons.
        133 * @constructor
        134 */
        135webdriver.stacktrace.Frame = function(context, name, alias, path) {
        136
        137 /** @private {string} */
        138 this.context_ = context || '';
        139
        140 /** @private {string} */
        141 this.name_ = name || '';
        142
        143 /** @private {string} */
        144 this.alias_ = alias || '';
        145
        146 /** @private {string} */
        147 this.path_ = path || '';
        148
        149 /** @private {string} */
        150 this.url_ = this.path_;
        151
        152 /** @private {number} */
        153 this.line_ = -1;
        154
        155 /** @private {number} */
        156 this.column_ = -1;
        157
        158 if (path) {
        159 var match = /:(\d+)(?::(\d+))?$/.exec(path);
        160 if (match) {
        161 this.line_ = Number(match[1]);
        162 this.column = Number(match[2] || -1);
        163 this.url_ = path.substr(0, match.index);
        164 }
        165 }
        166};
        167
        168
        169/**
        170 * Constant for an anonymous frame.
        171 * @private {!webdriver.stacktrace.Frame}
        172 * @const
        173 */
        174webdriver.stacktrace.ANONYMOUS_FRAME_ =
        175 new webdriver.stacktrace.Frame('', '', '', '');
        176
        177
        178/**
        179 * @return {string} The function name or empty string if the function is
        180 * anonymous and the object field which it's assigned to is unknown.
        181 */
        182webdriver.stacktrace.Frame.prototype.getName = function() {
        183 return this.name_;
        184};
        185
        186
        187/**
        188 * @return {string} The url or empty string if it is unknown.
        189 */
        190webdriver.stacktrace.Frame.prototype.getUrl = function() {
        191 return this.url_;
        192};
        193
        194
        195/**
        196 * @return {number} The line number if known or -1 if it is unknown.
        197 */
        198webdriver.stacktrace.Frame.prototype.getLine = function() {
        199 return this.line_;
        200};
        201
        202
        203/**
        204 * @return {number} The column number if known and -1 if it is unknown.
        205 */
        206webdriver.stacktrace.Frame.prototype.getColumn = function() {
        207 return this.column_;
        208};
        209
        210
        211/**
        212 * @return {boolean} Whether the stack frame contains an anonymous function.
        213 */
        214webdriver.stacktrace.Frame.prototype.isAnonymous = function() {
        215 return !this.name_ || this.context_ == '[object Object]';
        216};
        217
        218
        219/**
        220 * Converts this frame to its string representation using V8's stack trace
        221 * format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
        222 * @return {string} The string representation of this frame.
        223 * @override
        224 */
        225webdriver.stacktrace.Frame.prototype.toString = function() {
        226 var context = this.context_;
        227 if (context && context !== 'new ') {
        228 context += '.';
        229 }
        230 context += this.name_;
        231 context += this.alias_ ? ' [as ' + this.alias_ + ']' : '';
        232
        233 var path = this.path_ || '<anonymous>';
        234 return ' at ' + (context ? context + ' (' + path + ')' : path);
        235};
        236
        237
        238/**
        239 * Maximum length of a string that can be matched with a RegExp on
        240 * Firefox 3x. Exceeding this approximate length will cause string.match
        241 * to exceed Firefox's stack quota. This situation can be encountered
        242 * when goog.globalEval is invoked with a long argument; such as
        243 * when loading a module.
        244 * @private {number}
        245 * @const
        246 */
        247webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;
        248
        249
        250/**
        251 * RegExp pattern for JavaScript identifiers. We don't support Unicode
        252 * identifiers defined in ECMAScript v3.
        253 * @private {string}
        254 * @const
        255 */
        256webdriver.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';
        257
        258
        259/**
        260 * Pattern for a matching the type on a fully-qualified name. Forms an
        261 * optional sub-match on the type. For example, in "foo.bar.baz", will match on
        262 * "foo.bar".
        263 * @private {string}
        264 * @const
        265 */
        266webdriver.stacktrace.CONTEXT_PATTERN_ =
        267 '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +
        268 '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.';
        269
        270
        271/**
        272 * Pattern for matching a fully qualified name. Will create two sub-matches:
        273 * the type (optional), and the name. For example, in "foo.bar.baz", will
        274 * match on ["foo.bar", "baz"].
        275 * @private {string}
        276 * @const
        277 */
        278webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ =
        279 '(?:' + webdriver.stacktrace.CONTEXT_PATTERN_ + ')?' +
        280 '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')';
        281
        282
        283/**
        284 * RegExp pattern for function name alias in the V8 stack trace.
        285 * @private {string}
        286 * @const
        287 */
        288webdriver.stacktrace.V8_ALIAS_PATTERN_ =
        289 '(?: \\[as (' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';
        290
        291
        292/**
        293 * RegExp pattern for function names and constructor calls in the V8 stack
        294 * trace.
        295 * @private {string}
        296 * @const
        297 */
        298webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ =
        299 '(?:' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '|<anonymous>)';
        300
        301
        302/**
        303 * RegExp pattern for the context of a function call in V8. Creates two
        304 * submatches, only one of which will ever match: either the namespace
        305 * identifier (with optional "new" keyword in the case of a constructor call),
        306 * or just the "new " phrase for a top level constructor call.
        307 * @private {string}
        308 * @const
        309 */
        310webdriver.stacktrace.V8_CONTEXT_PATTERN_ =
        311 '(?:((?:new )?(?:\\[object Object\\]|' +
        312 webdriver.stacktrace.IDENTIFIER_PATTERN_ +
        313 '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)' +
        314 ')\\.|(new ))';
        315
        316
        317/**
        318 * RegExp pattern for function call in the V8 stack trace.
        319 * Creates 3 submatches with context object (optional), function name and
        320 * function alias (optional).
        321 * @private {string}
        322 * @const
        323 */
        324webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ =
        325 ' (?:' + webdriver.stacktrace.V8_CONTEXT_PATTERN_ + ')?' +
        326 '(' + webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +
        327 webdriver.stacktrace.V8_ALIAS_PATTERN_;
        328
        329
        330/**
        331 * RegExp pattern for an URL + position inside the file.
        332 * @private {string}
        333 * @const
        334 */
        335webdriver.stacktrace.URL_PATTERN_ =
        336 '((?:http|https|file)://[^\\s]+|javascript:.*)';
        337
        338
        339/**
        340 * RegExp pattern for a location string in a V8 stack frame. Creates two
        341 * submatches for the location, one for enclosed in parentheticals and on
        342 * where the location appears alone (which will only occur if the location is
        343 * the only information in the frame).
        344 * @private {string}
        345 * @const
        346 * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
        347 */
        348webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))';
        349
        350
        351/**
        352 * Regular expression for parsing one stack frame in V8.
        353 * @private {!RegExp}
        354 * @const
        355 */
        356webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^\\s+at' +
        357 // Prevent intersections with IE10 stack frame regex.
        358 '(?! (?:Anonymous function|Global code|eval code) )' +
        359 '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +
        360 webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$');
        361
        362
        363/**
        364 * RegExp pattern for function names in the Firefox stack trace.
        365 * Firefox has extended identifiers to deal with inner functions and anonymous
        366 * functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9
        367 * @private {string}
        368 * @const
        369 */
        370webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ =
        371 webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*';
        372
        373
        374/**
        375 * RegExp pattern for function call in the Firefox stack trace.
        376 * Creates a submatch for the function name.
        377 * @private {string}
        378 * @const
        379 */
        380webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =
        381 '(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' +
        382 '(?:\\(.*\\))?@';
        383
        384
        385/**
        386 * Regular expression for parsing one stack frame in Firefox.
        387 * @private {!RegExp}
        388 * @const
        389 */
        390webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +
        391 webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +
        392 '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$');
        393
        394
        395/**
        396 * RegExp pattern for function call in a Chakra (IE) stack trace. This
        397 * expression creates 2 submatches on the (optional) context and function name,
        398 * matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function',
        399 * 'eval code', and 'Global code'.
        400 * @private {string}
        401 * @const
        402 */
        403webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ =
        404 '(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +
        405 '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +
        406 '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';
        407
        408
        409/**
        410 * Regular expression for parsing on stack frame in Chakra (IE).
        411 * @private {!RegExp}
        412 * @const
        413 */
        414webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^ at ' +
        415 webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ +
        416 '\\s*(?:\\((.*)\\))$');
        417
        418
        419/**
        420 * Placeholder for an unparsable frame in a stack trace generated by
        421 * {@link goog.testing.stacktrace}.
        422 * @private {string}
        423 * @const
        424 */
        425webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)';
        426
        427
        428/**
        429 * Representation of an anonymous frame in a stack trace generated by
        430 * {@link goog.testing.stacktrace}.
        431 * @private {string}
        432 * @const
        433 */
        434webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous';
        435
        436
        437/**
        438 * Pattern for a function call in a Closure stack trace. Creates three optional
        439 * submatches: the context, function name, and alias.
        440 * @private {string}
        441 * @const
        442 */
        443webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ =
        444 webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ +
        445 '(?:\\(.*\\))?' + // Ignore arguments if present.
        446 webdriver.stacktrace.V8_ALIAS_PATTERN_;
        447
        448
        449/**
        450 * Regular expression for parsing a stack frame generated by Closure's
        451 * {@link goog.testing.stacktrace}.
        452 * @private {!RegExp}
        453 * @const
        454 */
        455webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' +
        456 '(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ +
        457 '(?: at )?)?' +
        458 '(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$');
        459
        460
        461/**
        462 * Parses one stack frame.
        463 * @param {string} frameStr The stack frame as string.
        464 * @return {webdriver.stacktrace.Frame} Stack frame object or null if the
        465 * parsing failed.
        466 * @private
        467 */
        468webdriver.stacktrace.parseStackFrame_ = function(frameStr) {
        469 var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_);
        470 if (m) {
        471 return new webdriver.stacktrace.Frame(
        472 m[1] || m[2], m[3], m[4], m[5] || m[6]);
        473 }
        474
        475 if (frameStr.length >
        476 webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {
        477 return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr);
        478 }
        479
        480 m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);
        481 if (m) {
        482 return new webdriver.stacktrace.Frame('', m[1], '', m[2]);
        483 }
        484
        485 m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_);
        486 if (m) {
        487 return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]);
        488 }
        489
        490 if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ ||
        491 frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) {
        492 return webdriver.stacktrace.ANONYMOUS_FRAME_;
        493 }
        494
        495 m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_);
        496 if (m) {
        497 return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]);
        498 }
        499
        500 return null;
        501};
        502
        503
        504/**
        505 * Parses a long firefox stack frame.
        506 * @param {string} frameStr The stack frame as string.
        507 * @return {!webdriver.stacktrace.Frame} Stack frame object.
        508 * @private
        509 */
        510webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {
        511 var firstParen = frameStr.indexOf('(');
        512 var lastAmpersand = frameStr.lastIndexOf('@');
        513 var lastColon = frameStr.lastIndexOf(':');
        514 var functionName = '';
        515 if ((firstParen >= 0) && (firstParen < lastAmpersand)) {
        516 functionName = frameStr.substring(0, firstParen);
        517 }
        518 var loc = '';
        519 if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {
        520 loc = frameStr.substring(lastAmpersand + 1);
        521 }
        522 return new webdriver.stacktrace.Frame('', functionName, '', loc);
        523};
        524
        525
        526/**
        527 * Get an error's stack trace with the error string trimmed.
        528 * V8 prepends the string representation of an error to its stack trace.
        529 * This function trims the string so that the stack trace can be parsed
        530 * consistently with the other JS engines.
        531 * @param {(Error|goog.testing.JsUnitException)} error The error.
        532 * @return {string} The stack trace string.
        533 */
        534webdriver.stacktrace.getStack = function(error) {
        535 if (!error) {
        536 return '';
        537 }
        538 var stack = error.stack || error.stackTrace || '';
        539 var errorStr = error + '\n';
        540 if (goog.string.startsWith(stack, errorStr)) {
        541 stack = stack.substring(errorStr.length);
        542 }
        543 return stack;
        544};
        545
        546
        547/**
        548 * Formats an error's stack trace.
        549 * @param {!(Error|goog.testing.JsUnitException)} error The error to format.
        550 * @return {!(Error|goog.testing.JsUnitException)} The formatted error.
        551 */
        552webdriver.stacktrace.format = function(error) {
        553 var stack = webdriver.stacktrace.getStack(error);
        554 var frames = webdriver.stacktrace.parse_(stack);
        555
        556 // If the original stack is in an unexpected format, our formatted stack
        557 // trace will be a bunch of " at <anonymous>" lines. If this is the case,
        558 // just return the error unmodified to avoid losing information. This is
        559 // necessary since the user may have defined a custom stack formatter in
        560 // V8 via Error.prepareStackTrace. See issue 7994.
        561 var isAnonymousFrame = function(frame) {
        562 return frame.toString() === ' at <anonymous>';
        563 };
        564 if (frames.length && goog.array.every(frames, isAnonymousFrame)) {
        565 return error;
        566 }
        567
        568 // Older versions of IE simply return [object Error] for toString(), so
        569 // only use that as a last resort.
        570 var errorStr = '';
        571 if (error.message) {
        572 errorStr = (error.name ? error.name + ': ' : '') + error.message;
        573 } else {
        574 errorStr = error.toString();
        575 }
        576
        577 // Ensure the error is in the V8 style with the error's string representation
        578 // prepended to the stack.
        579 error.stack = errorStr + '\n' + frames.join('\n');
        580 return error;
        581};
        582
        583
        584/**
        585 * Parses an Error object's stack trace.
        586 * @param {string} stack The stack trace.
        587 * @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The
        588 * unrecognized frames will be nulled out.
        589 * @private
        590 */
        591webdriver.stacktrace.parse_ = function(stack) {
        592 if (!stack) {
        593 return [];
        594 }
        595
        596 var lines = stack.
        597 replace(/\s*$/, '').
        598 split('\n');
        599 var frames = [];
        600 for (var i = 0; i < lines.length; i++) {
        601 var frame = webdriver.stacktrace.parseStackFrame_(lines[i]);
        602 // The first two frames will be:
        603 // webdriver.stacktrace.Snapshot()
        604 // webdriver.stacktrace.get()
        605 frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_);
        606 }
        607 return frames;
        608};
        609
        610
        611/**
        612 * Gets the native stack trace if available otherwise follows the call chain.
        613 * The generated trace will exclude all frames up to and including the call to
        614 * this function.
        615 * @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace.
        616 */
        617webdriver.stacktrace.get = function() {
        618 return new webdriver.stacktrace.Snapshot(1).getStacktrace();
        619};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/testing/asserts.js.src.html b/docs/source/lib/webdriver/testing/asserts.js.src.html index 5d37bbd..328b759 100644 --- a/docs/source/lib/webdriver/testing/asserts.js.src.html +++ b/docs/source/lib/webdriver/testing/asserts.js.src.html @@ -1 +1 @@ -asserts.js

        lib/webdriver/testing/asserts.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Assertions and expectation utilities for use in WebDriver test
        17 * cases.
        18 */
        19
        20goog.provide('webdriver.testing.Assertion');
        21goog.provide('webdriver.testing.ContainsMatcher');
        22goog.provide('webdriver.testing.NegatedAssertion');
        23goog.provide('webdriver.testing.assert');
        24goog.provide('webdriver.testing.asserts');
        25
        26goog.require('goog.array');
        27goog.require('goog.labs.testing.CloseToMatcher');
        28goog.require('goog.labs.testing.EndsWithMatcher');
        29goog.require('goog.labs.testing.EqualToMatcher');
        30goog.require('goog.labs.testing.EqualsMatcher');
        31goog.require('goog.labs.testing.GreaterThanEqualToMatcher');
        32goog.require('goog.labs.testing.GreaterThanMatcher');
        33goog.require('goog.labs.testing.LessThanEqualToMatcher');
        34goog.require('goog.labs.testing.LessThanMatcher');
        35goog.require('goog.labs.testing.InstanceOfMatcher');
        36goog.require('goog.labs.testing.IsNotMatcher');
        37goog.require('goog.labs.testing.IsNullMatcher');
        38goog.require('goog.labs.testing.IsNullOrUndefinedMatcher');
        39goog.require('goog.labs.testing.IsUndefinedMatcher');
        40goog.require('goog.labs.testing.Matcher');
        41goog.require('goog.labs.testing.ObjectEqualsMatcher');
        42goog.require('goog.labs.testing.RegexMatcher');
        43goog.require('goog.labs.testing.StartsWithMatcher');
        44goog.require('goog.labs.testing.assertThat');
        45goog.require('goog.string');
        46goog.require('webdriver.promise');
        47
        48
        49/**
        50 * Accepts strins or array-like structures that contain {@code value}.
        51 * @param {*} value The value to check for.
        52 * @constructor
        53 * @implements {goog.labs.testing.Matcher}
        54 */
        55webdriver.testing.ContainsMatcher = function(value) {
        56 /** @private {*} */
        57 this.value_ = value;
        58};
        59
        60
        61/** @override */
        62webdriver.testing.ContainsMatcher.prototype.matches = function(actualValue) {
        63 if (goog.isString(actualValue)) {
        64 return goog.string.contains(
        65 actualValue, /** @type {string} */(this.value_));
        66 } else {
        67 return goog.array.contains(
        68 /** @type {goog.array.ArrayLike} */(actualValue), this.value_);
        69 }
        70};
        71
        72
        73/** @override */
        74webdriver.testing.ContainsMatcher.prototype.describe = function(actualValue) {
        75 return actualValue + ' does not contain ' + this.value_;
        76};
        77
        78
        79
        80/**
        81 * Utility for performing assertions against a given {@code value}. If the
        82 * value is a {@link webdriver.promise.Promise}, this assertion will wait
        83 * for it to resolve before applying any matchers.
        84 * @param {*} value The value to wrap and apply matchers to.
        85 * @constructor
        86 */
        87webdriver.testing.Assertion = function(value) {
        88
        89 /** @private {*} */
        90 this.value_ = value;
        91
        92 if (!(this instanceof webdriver.testing.NegatedAssertion)) {
        93 /**
        94 * A self reference provided for writing fluent assertions:
        95 * webdriver.testing.assert(x).is.equalTo(y);
        96 * @type {!webdriver.testing.Assertion}
        97 */
        98 this.is = this;
        99
        100 /**
        101 * Negates any matchers applied to this instance's value:
        102 * webdriver.testing.assert(x).not.equalTo(y);
        103 * @type {!webdriver.testing.NegatedAssertion}
        104 */
        105 this.not = new webdriver.testing.NegatedAssertion(value);
        106 }
        107};
        108
        109
        110/**
        111 * Wraps an object literal implementing the Matcher interface. This is used
        112 * to appease the Closure compiler, which will not treat an object literal as
        113 * implementing an interface.
        114 * @param {{matches: function(*): boolean, describe: function(): string}} obj
        115 * The object literal to delegate to.
        116 * @constructor
        117 * @implements {goog.labs.testing.Matcher}
        118 * @private
        119 */
        120webdriver.testing.Assertion.DelegatingMatcher_ = function(obj) {
        121
        122 /** @override */
        123 this.matches = function(value) {
        124 return obj.matches(value);
        125 };
        126
        127 /** @override */
        128 this.describe = function() {
        129 return obj.describe();
        130 };
        131};
        132
        133
        134/**
        135 * Asserts that the given {@code matcher} accepts the value wrapped by this
        136 * instance. If the wrapped value is a promise, this function will defer
        137 * applying the assertion until the value has been resolved. Otherwise, it
        138 * will be applied immediately.
        139 * @param {!goog.labs.testing.Matcher} matcher The matcher to apply
        140 * @param {string=} opt_message A message to include if the matcher does not
        141 * accept the value wrapped by this assertion.
        142 * @return {webdriver.promise.Promise} The deferred assertion result, or
        143 * {@code null} if the assertion was immediately applied.
        144 * @protected
        145 */
        146webdriver.testing.Assertion.prototype.apply = function(matcher, opt_message) {
        147 var result = null;
        148 if (webdriver.promise.isPromise(this.value_)) {
        149 result = webdriver.promise.when(this.value_, function(value) {
        150 goog.labs.testing.assertThat(value, matcher, opt_message);
        151 });
        152 } else {
        153 goog.labs.testing.assertThat(this.value_, matcher, opt_message);
        154 }
        155 return result;
        156};
        157
        158
        159/**
        160 * Asserts that the value managed by this assertion is a number strictly
        161 * greater than {@code value}.
        162 * @param {number} value The minimum value.
        163 * @param {string=} opt_message A message to include if the matcher does not
        164 * accept the value wrapped by this assertion.
        165 * @return {webdriver.promise.Promise} The assertion result.
        166 */
        167webdriver.testing.Assertion.prototype.greaterThan = function(
        168 value, opt_message) {
        169 return this.apply(
        170 new goog.labs.testing.GreaterThanMatcher(value), opt_message);
        171};
        172
        173
        174/**
        175 * Asserts that the value managed by this assertion is a number >= the given
        176 * value.
        177 * @param {number} value The minimum value.
        178 * @param {string=} opt_message A message to include if the matcher does not
        179 * accept the value wrapped by this assertion.
        180 * @return {webdriver.promise.Promise} The assertion result.
        181 */
        182webdriver.testing.Assertion.prototype.greaterThanEqualTo = function(
        183 value, opt_message) {
        184 return this.apply(
        185 new goog.labs.testing.GreaterThanEqualToMatcher(value), opt_message);
        186};
        187
        188
        189/**
        190 * Asserts that the value managed by this assertion is a number strictly less
        191 * than the given value.
        192 * @param {number} value The maximum value.
        193 * @param {string=} opt_message A message to include if the matcher does not
        194 * accept the value wrapped by this assertion.
        195 * @return {webdriver.promise.Promise} The assertion result.
        196 */
        197webdriver.testing.Assertion.prototype.lessThan = function(value, opt_message) {
        198 return this.apply(
        199 new goog.labs.testing.LessThanMatcher(value), opt_message);
        200};
        201
        202
        203/**
        204 * Asserts that the value managed by this assertion is a number <= the given
        205 * value.
        206 * @param {number} value The maximum value.
        207 * @param {string=} opt_message A message to include if the matcher does not
        208 * accept the value wrapped by this assertion.
        209 * @return {webdriver.promise.Promise} The assertion result.
        210 */
        211webdriver.testing.Assertion.prototype.lessThanEqualTo = function(
        212 value, opt_message) {
        213 return this.apply(
        214 new goog.labs.testing.LessThanEqualToMatcher(value), opt_message);
        215};
        216
        217
        218/**
        219 * Asserts that the wrapped value is a number within a given distance of an
        220 * expected value.
        221 * @param {number} value The expected value.
        222 * @param {number} range The maximum amount the actual value is permitted to
        223 * differ from the expected value.
        224 * @param {string=} opt_message A message to include if the matcher does not
        225 * accept the value wrapped by this assertion.
        226 * @return {webdriver.promise.Promise} The assertion result.
        227 */
        228webdriver.testing.Assertion.prototype.closeTo = function(
        229 value, range, opt_message) {
        230 return this.apply(
        231 new goog.labs.testing.CloseToMatcher(value, range), opt_message);
        232};
        233
        234
        235/**
        236 * Asserts that the wrapped value is an instance of the given class.
        237 * @param {!Function} ctor The expected class constructor.
        238 * @param {string=} opt_message A message to include if the matcher does not
        239 * accept the value wrapped by this assertion.
        240 * @return {webdriver.promise.Promise} The assertion result.
        241 */
        242webdriver.testing.Assertion.prototype.instanceOf = function(ctor, opt_message) {
        243 return this.apply(
        244 new goog.labs.testing.InstanceOfMatcher(ctor), opt_message);
        245};
        246
        247
        248/**
        249 * Asserts that the wrapped value is null.
        250 * @param {string=} opt_message A message to include if the matcher does not
        251 * accept the value wrapped by this assertion.
        252 * @return {webdriver.promise.Promise} The assertion result.
        253 */
        254webdriver.testing.Assertion.prototype.isNull = function(opt_message) {
        255 return this.apply(new goog.labs.testing.IsNullMatcher(), opt_message);
        256};
        257
        258
        259/**
        260 * Asserts that the wrapped value is undefined.
        261 * @param {string=} opt_message A message to include if the matcher does not
        262 * accept the value wrapped by this assertion.
        263 * @return {webdriver.promise.Promise} The assertion result.
        264 */
        265webdriver.testing.Assertion.prototype.isUndefined = function(opt_message) {
        266 return this.apply(new goog.labs.testing.IsUndefinedMatcher(), opt_message);
        267};
        268
        269
        270/**
        271 * Asserts that the wrapped value is null or undefined.
        272 * @param {string=} opt_message A message to include if the matcher does not
        273 * accept the value wrapped by this assertion.
        274 * @return {webdriver.promise.Promise} The assertion result.
        275 */
        276webdriver.testing.Assertion.prototype.isNullOrUndefined = function(
        277 opt_message) {
        278 return this.apply(
        279 new goog.labs.testing.IsNullOrUndefinedMatcher(), opt_message);
        280};
        281
        282
        283/**
        284 * Asserts that the wrapped value is a string or array-like structure
        285 * containing the given value.
        286 * @param {*} value The expected value.
        287 * @param {string=} opt_message A message to include if the matcher does not
        288 * accept the value wrapped by this assertion.
        289 * @return {webdriver.promise.Promise} The assertion result.
        290 */
        291webdriver.testing.Assertion.prototype.contains = function(value, opt_message) {
        292 return this.apply(
        293 new webdriver.testing.ContainsMatcher(value), opt_message);
        294};
        295
        296
        297/**
        298 * Asserts that the wrapped value is a string ending with the given suffix.
        299 * @param {string} suffix The expected suffix.
        300 * @param {string=} opt_message A message to include if the matcher does not
        301 * accept the value wrapped by this assertion.
        302 * @return {webdriver.promise.Promise} The assertion result.
        303 */
        304webdriver.testing.Assertion.prototype.endsWith = function(
        305 suffix, opt_message) {
        306 return this.apply(
        307 new goog.labs.testing.EndsWithMatcher(suffix), opt_message);
        308};
        309
        310
        311/**
        312 * Asserts that the wrapped value is a string starting with the given prefix.
        313 * @param {string} prefix The expected prefix.
        314 * @param {string=} opt_message A message to include if the matcher does not
        315 * accept the value wrapped by this assertion.
        316 * @return {webdriver.promise.Promise} The assertion result.
        317 */
        318webdriver.testing.Assertion.prototype.startsWith = function(
        319 prefix, opt_message) {
        320 return this.apply(
        321 new goog.labs.testing.StartsWithMatcher(prefix), opt_message);
        322};
        323
        324
        325/**
        326 * Asserts that the wrapped value is a string that matches the given RegExp.
        327 * @param {!RegExp} regex The regex to test.
        328 * @param {string=} opt_message A message to include if the matcher does not
        329 * accept the value wrapped by this assertion.
        330 * @return {webdriver.promise.Promise} The assertion result.
        331 */
        332webdriver.testing.Assertion.prototype.matches = function(regex, opt_message) {
        333 return this.apply(new goog.labs.testing.RegexMatcher(regex), opt_message);
        334};
        335
        336
        337/**
        338 * Asserts that the value managed by this assertion is strictly equal to the
        339 * given {@code value}.
        340 * @param {*} value The expected value.
        341 * @param {string=} opt_message A message to include if the matcher does not
        342 * accept the value wrapped by this assertion.
        343 * @return {webdriver.promise.Promise} The assertion result.
        344 */
        345webdriver.testing.Assertion.prototype.equalTo = function(value, opt_message) {
        346 return this.apply(webdriver.testing.asserts.equalTo(value), opt_message);
        347};
        348
        349
        350/**
        351 * Asserts that the value managed by this assertion is strictly true.
        352 * @return {webdriver.promise.Promise} The assertion result.
        353 */
        354webdriver.testing.Assertion.prototype.isTrue = function() {
        355 return this.equalTo(true);
        356};
        357
        358
        359/**
        360 * Asserts that the value managed by this assertion is strictly false.
        361 * @return {webdriver.promise.Promise} The assertion result.
        362 */
        363webdriver.testing.Assertion.prototype.isFalse = function() {
        364 return this.equalTo(false);
        365};
        366
        367
        368
        369/**
        370 * An assertion that negates any applied matchers.
        371 * @param {*} value The value to perform assertions on.
        372 * @constructor
        373 * @extends {webdriver.testing.Assertion}
        374 */
        375webdriver.testing.NegatedAssertion = function(value) {
        376 goog.base(this, value);
        377 this.value = value;
        378};
        379goog.inherits(
        380 webdriver.testing.NegatedAssertion, webdriver.testing.Assertion);
        381
        382
        383/** @override */
        384webdriver.testing.NegatedAssertion.prototype.apply = function(
        385 matcher, opt_message) {
        386 matcher = new goog.labs.testing.IsNotMatcher(matcher);
        387 return goog.base(this, 'apply', matcher, opt_message);
        388};
        389
        390
        391
        392/**
        393 * Creates a new assertion.
        394 * @param {*} value The value to perform an assertion on.
        395 * @return {!webdriver.testing.Assertion} The new assertion.
        396 */
        397webdriver.testing.assert = function(value) {
        398 return new webdriver.testing.Assertion(value);
        399};
        400
        401
        402/**
        403 * Registers a new assertion to expose from the
        404 * {@link webdriver.testing.Assertion} prototype.
        405 * @param {string} name The assertion name.
        406 * @param {(function(new: goog.labs.testing.Matcher, *)|
        407 * {matches: function(*): boolean,
        408 * describe: function(): string})} matcherTemplate Either the
        409 * matcher constructor to use, or an object literal defining a matcher.
        410 */
        411webdriver.testing.assert.register = function(name, matcherTemplate) {
        412 webdriver.testing.Assertion.prototype[name] = function(value, opt_message) {
        413 var matcher;
        414 if (goog.isFunction(matcherTemplate)) {
        415 var ctor = /** @type {function(new: goog.labs.testing.Matcher, *)} */ (
        416 matcherTemplate);
        417 matcher = new ctor(value);
        418 } else {
        419 matcher = new webdriver.testing.Assertion.DelegatingMatcher_(value);
        420 }
        421 return this.apply(matcher, opt_message);
        422 };
        423};
        424
        425
        426/**
        427 * Asserts that a matcher accepts a given value. This function has two
        428 * signatures based on the number of arguments:
        429 *
        430 * Two arguments:
        431 * assertThat(actualValue, matcher)
        432 * Three arguments:
        433 * assertThat(failureMessage, actualValue, matcher)
        434 *
        435 * @param {*} failureMessageOrActualValue Either a failure message or the value
        436 * to apply to the given matcher.
        437 * @param {*} actualValueOrMatcher Either the value to apply to the given
        438 * matcher, or the matcher itself.
        439 * @param {goog.labs.testing.Matcher=} opt_matcher The matcher to use;
        440 * ignored unless this function is invoked with three arguments.
        441 * @return {!webdriver.promise.Promise} The assertion result.
        442 * @deprecated Use webdriver.testing.asserts.assert instead.
        443 */
        444webdriver.testing.asserts.assertThat = function(
        445 failureMessageOrActualValue, actualValueOrMatcher, opt_matcher) {
        446 var args = goog.array.slice(arguments, 0);
        447
        448 var message = args.length > 2 ? args.shift() : '';
        449 if (message) message += '\n';
        450
        451 var actualValue = args.shift();
        452 var matcher = args.shift();
        453
        454 return webdriver.promise.when(actualValue, function(value) {
        455 goog.labs.testing.assertThat(value, matcher, message);
        456 });
        457};
        458
        459
        460/**
        461 * Creates an equality matcher.
        462 * @param {*} expected The expected value.
        463 * @return {!goog.labs.testing.Matcher} The new matcher.
        464 */
        465webdriver.testing.asserts.equalTo = function(expected) {
        466 if (goog.isString(expected)) {
        467 return new goog.labs.testing.EqualsMatcher(expected);
        468 } else if (goog.isNumber(expected)) {
        469 return new goog.labs.testing.EqualToMatcher(expected);
        470 } else {
        471 return new goog.labs.testing.ObjectEqualsMatcher(
        472 /** @type {!Object} */ (expected));
        473 }
        474};
        475
        476
        477goog.exportSymbol('assertThat', webdriver.testing.asserts.assertThat);
        478// Mappings for goog.labs.testing matcher functions to the legacy
        479// webdriver.testing.asserts matchers.
        480goog.exportSymbol('contains', containsString);
        481goog.exportSymbol('equalTo', webdriver.testing.asserts.equalTo);
        482goog.exportSymbol('equals', webdriver.testing.asserts.equalTo);
        483goog.exportSymbol('is', webdriver.testing.asserts.equalTo);
        484goog.exportSymbol('not', isNot);
        485goog.exportSymbol('or', anyOf);
        \ No newline at end of file +asserts.js

        lib/webdriver/testing/asserts.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Assertions and expectation utilities for use in WebDriver test
        20 * cases.
        21 */
        22
        23goog.provide('webdriver.testing.Assertion');
        24goog.provide('webdriver.testing.ContainsMatcher');
        25goog.provide('webdriver.testing.NegatedAssertion');
        26goog.provide('webdriver.testing.assert');
        27goog.provide('webdriver.testing.asserts');
        28
        29goog.require('goog.array');
        30goog.require('goog.labs.testing.CloseToMatcher');
        31goog.require('goog.labs.testing.EndsWithMatcher');
        32goog.require('goog.labs.testing.EqualToMatcher');
        33goog.require('goog.labs.testing.EqualsMatcher');
        34goog.require('goog.labs.testing.GreaterThanEqualToMatcher');
        35goog.require('goog.labs.testing.GreaterThanMatcher');
        36goog.require('goog.labs.testing.LessThanEqualToMatcher');
        37goog.require('goog.labs.testing.LessThanMatcher');
        38goog.require('goog.labs.testing.InstanceOfMatcher');
        39goog.require('goog.labs.testing.IsNotMatcher');
        40goog.require('goog.labs.testing.IsNullMatcher');
        41goog.require('goog.labs.testing.IsNullOrUndefinedMatcher');
        42goog.require('goog.labs.testing.IsUndefinedMatcher');
        43goog.require('goog.labs.testing.Matcher');
        44goog.require('goog.labs.testing.ObjectEqualsMatcher');
        45goog.require('goog.labs.testing.RegexMatcher');
        46goog.require('goog.labs.testing.StartsWithMatcher');
        47goog.require('goog.labs.testing.assertThat');
        48goog.require('goog.string');
        49goog.require('webdriver.promise');
        50
        51
        52/**
        53 * Accepts strins or array-like structures that contain {@code value}.
        54 * @param {*} value The value to check for.
        55 * @constructor
        56 * @implements {goog.labs.testing.Matcher}
        57 */
        58webdriver.testing.ContainsMatcher = function(value) {
        59 /** @private {*} */
        60 this.value_ = value;
        61};
        62
        63
        64/** @override */
        65webdriver.testing.ContainsMatcher.prototype.matches = function(actualValue) {
        66 if (goog.isString(actualValue)) {
        67 return goog.string.contains(
        68 actualValue, /** @type {string} */(this.value_));
        69 } else {
        70 return goog.array.contains(
        71 /** @type {goog.array.ArrayLike} */(actualValue), this.value_);
        72 }
        73};
        74
        75
        76/** @override */
        77webdriver.testing.ContainsMatcher.prototype.describe = function(actualValue) {
        78 return actualValue + ' does not contain ' + this.value_;
        79};
        80
        81
        82
        83/**
        84 * Utility for performing assertions against a given {@code value}. If the
        85 * value is a {@link webdriver.promise.Promise}, this assertion will wait
        86 * for it to resolve before applying any matchers.
        87 * @param {*} value The value to wrap and apply matchers to.
        88 * @constructor
        89 */
        90webdriver.testing.Assertion = function(value) {
        91
        92 /** @private {*} */
        93 this.value_ = value;
        94
        95 if (!(this instanceof webdriver.testing.NegatedAssertion)) {
        96 /**
        97 * A self reference provided for writing fluent assertions:
        98 * webdriver.testing.assert(x).is.equalTo(y);
        99 * @type {!webdriver.testing.Assertion}
        100 */
        101 this.is = this;
        102
        103 /**
        104 * Negates any matchers applied to this instance's value:
        105 * webdriver.testing.assert(x).not.equalTo(y);
        106 * @type {!webdriver.testing.NegatedAssertion}
        107 */
        108 this.not = new webdriver.testing.NegatedAssertion(value);
        109 }
        110};
        111
        112
        113/**
        114 * Wraps an object literal implementing the Matcher interface. This is used
        115 * to appease the Closure compiler, which will not treat an object literal as
        116 * implementing an interface.
        117 * @param {{matches: function(*): boolean, describe: function(): string}} obj
        118 * The object literal to delegate to.
        119 * @constructor
        120 * @implements {goog.labs.testing.Matcher}
        121 * @private
        122 */
        123webdriver.testing.Assertion.DelegatingMatcher_ = function(obj) {
        124
        125 /** @override */
        126 this.matches = function(value) {
        127 return obj.matches(value);
        128 };
        129
        130 /** @override */
        131 this.describe = function() {
        132 return obj.describe();
        133 };
        134};
        135
        136
        137/**
        138 * Asserts that the given {@code matcher} accepts the value wrapped by this
        139 * instance. If the wrapped value is a promise, this function will defer
        140 * applying the assertion until the value has been resolved. Otherwise, it
        141 * will be applied immediately.
        142 * @param {!goog.labs.testing.Matcher} matcher The matcher to apply
        143 * @param {string=} opt_message A message to include if the matcher does not
        144 * accept the value wrapped by this assertion.
        145 * @return {webdriver.promise.Promise} The deferred assertion result, or
        146 * {@code null} if the assertion was immediately applied.
        147 * @protected
        148 */
        149webdriver.testing.Assertion.prototype.apply = function(matcher, opt_message) {
        150 var result = null;
        151 if (webdriver.promise.isPromise(this.value_)) {
        152 result = webdriver.promise.when(this.value_, function(value) {
        153 goog.labs.testing.assertThat(value, matcher, opt_message);
        154 });
        155 } else {
        156 goog.labs.testing.assertThat(this.value_, matcher, opt_message);
        157 }
        158 return result;
        159};
        160
        161
        162/**
        163 * Asserts that the value managed by this assertion is a number strictly
        164 * greater than {@code value}.
        165 * @param {number} value The minimum value.
        166 * @param {string=} opt_message A message to include if the matcher does not
        167 * accept the value wrapped by this assertion.
        168 * @return {webdriver.promise.Promise} The assertion result.
        169 */
        170webdriver.testing.Assertion.prototype.greaterThan = function(
        171 value, opt_message) {
        172 return this.apply(
        173 new goog.labs.testing.GreaterThanMatcher(value), opt_message);
        174};
        175
        176
        177/**
        178 * Asserts that the value managed by this assertion is a number >= the given
        179 * value.
        180 * @param {number} value The minimum value.
        181 * @param {string=} opt_message A message to include if the matcher does not
        182 * accept the value wrapped by this assertion.
        183 * @return {webdriver.promise.Promise} The assertion result.
        184 */
        185webdriver.testing.Assertion.prototype.greaterThanEqualTo = function(
        186 value, opt_message) {
        187 return this.apply(
        188 new goog.labs.testing.GreaterThanEqualToMatcher(value), opt_message);
        189};
        190
        191
        192/**
        193 * Asserts that the value managed by this assertion is a number strictly less
        194 * than the given value.
        195 * @param {number} value The maximum value.
        196 * @param {string=} opt_message A message to include if the matcher does not
        197 * accept the value wrapped by this assertion.
        198 * @return {webdriver.promise.Promise} The assertion result.
        199 */
        200webdriver.testing.Assertion.prototype.lessThan = function(value, opt_message) {
        201 return this.apply(
        202 new goog.labs.testing.LessThanMatcher(value), opt_message);
        203};
        204
        205
        206/**
        207 * Asserts that the value managed by this assertion is a number <= the given
        208 * value.
        209 * @param {number} value The maximum value.
        210 * @param {string=} opt_message A message to include if the matcher does not
        211 * accept the value wrapped by this assertion.
        212 * @return {webdriver.promise.Promise} The assertion result.
        213 */
        214webdriver.testing.Assertion.prototype.lessThanEqualTo = function(
        215 value, opt_message) {
        216 return this.apply(
        217 new goog.labs.testing.LessThanEqualToMatcher(value), opt_message);
        218};
        219
        220
        221/**
        222 * Asserts that the wrapped value is a number within a given distance of an
        223 * expected value.
        224 * @param {number} value The expected value.
        225 * @param {number} range The maximum amount the actual value is permitted to
        226 * differ from the expected value.
        227 * @param {string=} opt_message A message to include if the matcher does not
        228 * accept the value wrapped by this assertion.
        229 * @return {webdriver.promise.Promise} The assertion result.
        230 */
        231webdriver.testing.Assertion.prototype.closeTo = function(
        232 value, range, opt_message) {
        233 return this.apply(
        234 new goog.labs.testing.CloseToMatcher(value, range), opt_message);
        235};
        236
        237
        238/**
        239 * Asserts that the wrapped value is an instance of the given class.
        240 * @param {!Function} ctor The expected class constructor.
        241 * @param {string=} opt_message A message to include if the matcher does not
        242 * accept the value wrapped by this assertion.
        243 * @return {webdriver.promise.Promise} The assertion result.
        244 */
        245webdriver.testing.Assertion.prototype.instanceOf = function(ctor, opt_message) {
        246 return this.apply(
        247 new goog.labs.testing.InstanceOfMatcher(ctor), opt_message);
        248};
        249
        250
        251/**
        252 * Asserts that the wrapped value is null.
        253 * @param {string=} opt_message A message to include if the matcher does not
        254 * accept the value wrapped by this assertion.
        255 * @return {webdriver.promise.Promise} The assertion result.
        256 */
        257webdriver.testing.Assertion.prototype.isNull = function(opt_message) {
        258 return this.apply(new goog.labs.testing.IsNullMatcher(), opt_message);
        259};
        260
        261
        262/**
        263 * Asserts that the wrapped value is undefined.
        264 * @param {string=} opt_message A message to include if the matcher does not
        265 * accept the value wrapped by this assertion.
        266 * @return {webdriver.promise.Promise} The assertion result.
        267 */
        268webdriver.testing.Assertion.prototype.isUndefined = function(opt_message) {
        269 return this.apply(new goog.labs.testing.IsUndefinedMatcher(), opt_message);
        270};
        271
        272
        273/**
        274 * Asserts that the wrapped value is null or undefined.
        275 * @param {string=} opt_message A message to include if the matcher does not
        276 * accept the value wrapped by this assertion.
        277 * @return {webdriver.promise.Promise} The assertion result.
        278 */
        279webdriver.testing.Assertion.prototype.isNullOrUndefined = function(
        280 opt_message) {
        281 return this.apply(
        282 new goog.labs.testing.IsNullOrUndefinedMatcher(), opt_message);
        283};
        284
        285
        286/**
        287 * Asserts that the wrapped value is a string or array-like structure
        288 * containing the given value.
        289 * @param {*} value The expected value.
        290 * @param {string=} opt_message A message to include if the matcher does not
        291 * accept the value wrapped by this assertion.
        292 * @return {webdriver.promise.Promise} The assertion result.
        293 */
        294webdriver.testing.Assertion.prototype.contains = function(value, opt_message) {
        295 return this.apply(
        296 new webdriver.testing.ContainsMatcher(value), opt_message);
        297};
        298
        299
        300/**
        301 * Asserts that the wrapped value is a string ending with the given suffix.
        302 * @param {string} suffix The expected suffix.
        303 * @param {string=} opt_message A message to include if the matcher does not
        304 * accept the value wrapped by this assertion.
        305 * @return {webdriver.promise.Promise} The assertion result.
        306 */
        307webdriver.testing.Assertion.prototype.endsWith = function(
        308 suffix, opt_message) {
        309 return this.apply(
        310 new goog.labs.testing.EndsWithMatcher(suffix), opt_message);
        311};
        312
        313
        314/**
        315 * Asserts that the wrapped value is a string starting with the given prefix.
        316 * @param {string} prefix The expected prefix.
        317 * @param {string=} opt_message A message to include if the matcher does not
        318 * accept the value wrapped by this assertion.
        319 * @return {webdriver.promise.Promise} The assertion result.
        320 */
        321webdriver.testing.Assertion.prototype.startsWith = function(
        322 prefix, opt_message) {
        323 return this.apply(
        324 new goog.labs.testing.StartsWithMatcher(prefix), opt_message);
        325};
        326
        327
        328/**
        329 * Asserts that the wrapped value is a string that matches the given RegExp.
        330 * @param {!RegExp} regex The regex to test.
        331 * @param {string=} opt_message A message to include if the matcher does not
        332 * accept the value wrapped by this assertion.
        333 * @return {webdriver.promise.Promise} The assertion result.
        334 */
        335webdriver.testing.Assertion.prototype.matches = function(regex, opt_message) {
        336 return this.apply(new goog.labs.testing.RegexMatcher(regex), opt_message);
        337};
        338
        339
        340/**
        341 * Asserts that the value managed by this assertion is strictly equal to the
        342 * given {@code value}.
        343 * @param {*} value The expected value.
        344 * @param {string=} opt_message A message to include if the matcher does not
        345 * accept the value wrapped by this assertion.
        346 * @return {webdriver.promise.Promise} The assertion result.
        347 */
        348webdriver.testing.Assertion.prototype.equalTo = function(value, opt_message) {
        349 return this.apply(webdriver.testing.asserts.equalTo(value), opt_message);
        350};
        351
        352
        353/**
        354 * Asserts that the value managed by this assertion is strictly true.
        355 * @return {webdriver.promise.Promise} The assertion result.
        356 */
        357webdriver.testing.Assertion.prototype.isTrue = function() {
        358 return this.equalTo(true);
        359};
        360
        361
        362/**
        363 * Asserts that the value managed by this assertion is strictly false.
        364 * @return {webdriver.promise.Promise} The assertion result.
        365 */
        366webdriver.testing.Assertion.prototype.isFalse = function() {
        367 return this.equalTo(false);
        368};
        369
        370
        371
        372/**
        373 * An assertion that negates any applied matchers.
        374 * @param {*} value The value to perform assertions on.
        375 * @constructor
        376 * @extends {webdriver.testing.Assertion}
        377 */
        378webdriver.testing.NegatedAssertion = function(value) {
        379 webdriver.testing.NegatedAssertion.base(this, 'constructor', value);
        380 this.value = value;
        381};
        382goog.inherits(
        383 webdriver.testing.NegatedAssertion, webdriver.testing.Assertion);
        384
        385
        386/** @override */
        387webdriver.testing.NegatedAssertion.prototype.apply = function(
        388 matcher, opt_message) {
        389 matcher = new goog.labs.testing.IsNotMatcher(matcher);
        390 return webdriver.testing.NegatedAssertion.base(this, 'apply', matcher,
        391 opt_message);
        392};
        393
        394/**
        395 * Creates a new assertion.
        396 * @param {*} value The value to perform an assertion on.
        397 * @return {!webdriver.testing.Assertion} The new assertion.
        398 */
        399webdriver.testing.assert = function(value) {
        400 return new webdriver.testing.Assertion(value);
        401};
        402
        403
        404/**
        405 * Registers a new assertion to expose from the
        406 * {@link webdriver.testing.Assertion} prototype.
        407 * @param {string} name The assertion name.
        408 * @param {(function(new: goog.labs.testing.Matcher, *)|
        409 * {matches: function(*): boolean,
        410 * describe: function(): string})} matcherTemplate Either the
        411 * matcher constructor to use, or an object literal defining a matcher.
        412 */
        413webdriver.testing.assert.register = function(name, matcherTemplate) {
        414 webdriver.testing.Assertion.prototype[name] = function(value, opt_message) {
        415 var matcher;
        416 if (goog.isFunction(matcherTemplate)) {
        417 var ctor = /** @type {function(new: goog.labs.testing.Matcher, *)} */ (
        418 matcherTemplate);
        419 matcher = new ctor(value);
        420 } else {
        421 matcher = new webdriver.testing.Assertion.DelegatingMatcher_(value);
        422 }
        423 return this.apply(matcher, opt_message);
        424 };
        425};
        426
        427
        428/**
        429 * Asserts that a matcher accepts a given value. This function has two
        430 * signatures based on the number of arguments:
        431 *
        432 * Two arguments:
        433 * assertThat(actualValue, matcher)
        434 * Three arguments:
        435 * assertThat(failureMessage, actualValue, matcher)
        436 *
        437 * @param {*} failureMessageOrActualValue Either a failure message or the value
        438 * to apply to the given matcher.
        439 * @param {*} actualValueOrMatcher Either the value to apply to the given
        440 * matcher, or the matcher itself.
        441 * @param {goog.labs.testing.Matcher=} opt_matcher The matcher to use;
        442 * ignored unless this function is invoked with three arguments.
        443 * @return {!webdriver.promise.Promise} The assertion result.
        444 * @deprecated Use webdriver.testing.asserts.assert instead.
        445 */
        446webdriver.testing.asserts.assertThat = function(
        447 failureMessageOrActualValue, actualValueOrMatcher, opt_matcher) {
        448 var args = goog.array.slice(arguments, 0);
        449
        450 var message = args.length > 2 ? args.shift() : '';
        451 if (message) message += '\n';
        452
        453 var actualValue = args.shift();
        454 var matcher = args.shift();
        455
        456 return webdriver.promise.when(actualValue, function(value) {
        457 goog.labs.testing.assertThat(value, matcher, message);
        458 });
        459};
        460
        461
        462/**
        463 * Creates an equality matcher.
        464 * @param {*} expected The expected value.
        465 * @return {!goog.labs.testing.Matcher} The new matcher.
        466 */
        467webdriver.testing.asserts.equalTo = function(expected) {
        468 if (goog.isString(expected)) {
        469 return new goog.labs.testing.EqualsMatcher(expected);
        470 } else if (goog.isNumber(expected)) {
        471 return new goog.labs.testing.EqualToMatcher(expected);
        472 } else {
        473 return new goog.labs.testing.ObjectEqualsMatcher(
        474 /** @type {!Object} */ (expected));
        475 }
        476};
        477
        478
        479goog.exportSymbol('assertThat', webdriver.testing.asserts.assertThat);
        480// Mappings for goog.labs.testing matcher functions to the legacy
        481// webdriver.testing.asserts matchers.
        482goog.exportSymbol('contains', containsString);
        483goog.exportSymbol('equalTo', webdriver.testing.asserts.equalTo);
        484goog.exportSymbol('equals', webdriver.testing.asserts.equalTo);
        485goog.exportSymbol('is', webdriver.testing.asserts.equalTo);
        486goog.exportSymbol('not', isNot);
        487goog.exportSymbol('or', anyOf);
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/testing/testcase.js.src.html b/docs/source/lib/webdriver/testing/testcase.js.src.html new file mode 100644 index 0000000..334dcf7 --- /dev/null +++ b/docs/source/lib/webdriver/testing/testcase.js.src.html @@ -0,0 +1 @@ +testcase.js

        lib/webdriver/testing/testcase.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines a special test case that runs each test inside of a
        17 * {@code webdriver.Application}. This allows each phase to schedule
        18 * asynchronous actions that run to completion before the next phase of the
        19 * test.
        20 *
        21 * This file requires the global {@code G_testRunner} to be initialized before
        22 * use. This can be accomplished by also importing
        23 * {@link webdriver.testing.jsunit}. This namespace is not required by default
        24 * to improve interoperability with other namespaces that may initialize
        25 * G_testRunner.
        26 */
        27
        28goog.provide('webdriver.testing.TestCase');
        29
        30goog.require('goog.testing.TestCase');
        31goog.require('webdriver.promise.ControlFlow');
        32/** @suppress {extraRequire} Imported for user convenience. */
        33goog.require('webdriver.testing.asserts');
        34
        35
        36
        37/**
        38 * Constructs a test case that synchronizes each test case with the singleton
        39 * {@code webdriver.promise.ControlFlow}.
        40 *
        41 * @param {!webdriver.testing.Client} client The test client to use for
        42 * reporting test results.
        43 * @param {string=} opt_name The name of the test case, defaults to
        44 * 'Untitled Test Case'.
        45 * @constructor
        46 * @extends {goog.testing.TestCase}
        47 */
        48webdriver.testing.TestCase = function(client, opt_name) {
        49 goog.base(this, opt_name);
        50
        51 /** @private {!webdriver.testing.Client} */
        52 this.client_ = client;
        53};
        54goog.inherits(webdriver.testing.TestCase, goog.testing.TestCase);
        55
        56
        57/**
        58 * Executes the next test inside its own {@code webdriver.Application}.
        59 * @override
        60 */
        61webdriver.testing.TestCase.prototype.cycleTests = function() {
        62 var test = this.next();
        63 if (!test) {
        64 this.finalize();
        65 return;
        66 }
        67
        68 goog.testing.TestCase.currentTestName = test.name;
        69 this.result_.runCount++;
        70 this.log('Running test: ' + test.name);
        71 this.client_.sendTestStartedEvent(test.name);
        72
        73 var self = this;
        74 var hadError = false;
        75 var app = webdriver.promise.controlFlow();
        76
        77 this.runSingleTest_(test, onError).then(function() {
        78 hadError || self.doSuccess(test);
        79 self.timeout(function() {
        80 self.cycleTests();
        81 }, 100);
        82 });
        83
        84 function onError(e) {
        85 hadError = true;
        86 self.doError(test, app.annotateError(e));
        87 // Note: result_ is a @protected field but still uses the trailing
        88 // underscore.
        89 var err = self.result_.errors[self.result_.errors.length - 1];
        90 self.client_.sendErrorEvent(err.toString());
        91 }
        92};
        93
        94
        95/** @override */
        96webdriver.testing.TestCase.prototype.logError = function(name, opt_e) {
        97 var errMsg = null;
        98 var stack = null;
        99 if (opt_e) {
        100 this.log(opt_e);
        101 if (goog.isString(opt_e)) {
        102 errMsg = opt_e;
        103 } else {
        104 // In case someone calls this function directly, make sure we have a
        105 // properly annotated error.
        106 webdriver.promise.controlFlow().annotateError(opt_e);
        107 errMsg = opt_e.toString();
        108 stack = opt_e.stack.substring(errMsg.length + 1);
        109 }
        110 } else {
        111 errMsg = 'An unknown error occurred';
        112 }
        113 var err = new goog.testing.TestCase.Error(name, errMsg, stack);
        114
        115 // Avoid double logging.
        116 if (!opt_e || !opt_e['isJsUnitException'] ||
        117 !opt_e['loggedJsUnitException']) {
        118 this.saveMessage(err.toString());
        119 }
        120
        121 if (opt_e && opt_e['isJsUnitException']) {
        122 opt_e['loggedJsUnitException'] = true;
        123 }
        124
        125 return err;
        126};
        127
        128
        129/**
        130 * Executes a single test, scheduling each phase with the global application.
        131 * Each phase will wait for the application to go idle before moving on to the
        132 * next test phase. This function models the follow basic test flow:
        133 *
        134 * try {
        135 * this.setUp.call(test.scope);
        136 * test.ref.call(test.scope);
        137 * } catch (ex) {
        138 * onError(ex);
        139 * } finally {
        140 * try {
        141 * this.tearDown.call(test.scope);
        142 * } catch (e) {
        143 * onError(e);
        144 * }
        145 * }
        146 *
        147 * @param {!goog.testing.TestCase.Test} test The test to run.
        148 * @param {function(*)} onError The function to call each time an error is
        149 * detected.
        150 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        151 * test has finished running.
        152 * @private
        153 */
        154webdriver.testing.TestCase.prototype.runSingleTest_ = function(test, onError) {
        155 var flow = webdriver.promise.controlFlow();
        156 flow.clearHistory();
        157
        158 return execute(test.name + '.setUp()', this.setUp)().
        159 then(execute(test.name + '()', test.ref)).
        160 thenCatch(onError).
        161 then(execute(test.name + '.tearDown()', this.tearDown)).
        162 thenCatch(onError);
        163
        164 function execute(description, fn) {
        165 return function() {
        166 return flow.execute(goog.bind(fn, test.scope), description);
        167 }
        168 }
        169};
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/touchsequence.js.src.html b/docs/source/lib/webdriver/touchsequence.js.src.html new file mode 100644 index 0000000..21fdb0f --- /dev/null +++ b/docs/source/lib/webdriver/touchsequence.js.src.html @@ -0,0 +1 @@ +touchsequence.js

        lib/webdriver/touchsequence.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18goog.provide('webdriver.TouchSequence');
        19
        20goog.require('goog.array');
        21goog.require('webdriver.Command');
        22goog.require('webdriver.CommandName');
        23
        24
        25
        26/**
        27 * Class for defining sequences of user touch interactions. Each sequence
        28 * will not be executed until {@link #perform} is called.
        29 *
        30 * Example:
        31 *
        32 * new webdriver.TouchSequence(driver).
        33 * tapAndHold({x: 0, y: 0}).
        34 * move({x: 3, y: 4}).
        35 * release({x: 10, y: 10}).
        36 * perform();
        37 *
        38 * @param {!webdriver.WebDriver} driver The driver instance to use.
        39 * @constructor
        40 */
        41webdriver.TouchSequence = function(driver) {
        42
        43 /** @private {!webdriver.WebDriver} */
        44 this.driver_ = driver;
        45
        46 /** @private {!Array<{description: string, command: !webdriver.Command}>} */
        47 this.touchActions_ = [];
        48};
        49
        50
        51/**
        52 * Schedules an action to be executed each time {@link #perform} is called on
        53 * this instance.
        54 * @param {string} description A description of the command.
        55 * @param {!webdriver.Command} command The command.
        56 * @private
        57 */
        58webdriver.TouchSequence.prototype.schedule_ = function(description, command) {
        59 this.touchActions_.push({
        60 description: description,
        61 command: command
        62 });
        63};
        64
        65
        66/**
        67 * Executes this action sequence.
        68 * @return {!webdriver.promise.Promise} A promise that will be resolved once
        69 * this sequence has completed.
        70 */
        71webdriver.TouchSequence.prototype.perform = function() {
        72 // Make a protected copy of the scheduled actions. This will protect against
        73 // users defining additional commands before this sequence is actually
        74 // executed.
        75 var actions = goog.array.clone(this.touchActions_);
        76 var driver = this.driver_;
        77 return driver.controlFlow().execute(function() {
        78 goog.array.forEach(actions, function(action) {
        79 driver.schedule(action.command, action.description);
        80 });
        81 }, 'TouchSequence.perform');
        82};
        83
        84
        85/**
        86 * Taps an element.
        87 *
        88 * @param {!webdriver.WebElement} elem The element to tap.
        89 * @return {!webdriver.TouchSequence} A self reference.
        90 */
        91webdriver.TouchSequence.prototype.tap = function(elem) {
        92 var command = new webdriver.Command(webdriver.CommandName.TOUCH_SINGLE_TAP).
        93 setParameter('element', elem.getRawId());
        94
        95 this.schedule_('tap', command);
        96 return this;
        97};
        98
        99
        100/**
        101 * Double taps an element.
        102 *
        103 * @param {!webdriver.WebElement} elem The element to double tap.
        104 * @return {!webdriver.TouchSequence} A self reference.
        105 */
        106webdriver.TouchSequence.prototype.doubleTap = function(elem) {
        107 var command = new webdriver.Command(webdriver.CommandName.TOUCH_DOUBLE_TAP).
        108 setParameter('element', elem.getRawId());
        109
        110 this.schedule_('doubleTap', command);
        111 return this;
        112};
        113
        114
        115/**
        116 * Long press on an element.
        117 *
        118 * @param {!webdriver.WebElement} elem The element to long press.
        119 * @return {!webdriver.TouchSequence} A self reference.
        120 */
        121webdriver.TouchSequence.prototype.longPress = function(elem) {
        122 var command = new webdriver.Command(webdriver.CommandName.TOUCH_LONG_PRESS).
        123 setParameter('element', elem.getRawId());
        124
        125 this.schedule_('longPress', command);
        126 return this;
        127};
        128
        129
        130/**
        131 * Touch down at the given location.
        132 *
        133 * @param {{x: number, y: number}} location The location to touch down at.
        134 * @return {!webdriver.TouchSequence} A self reference.
        135 */
        136webdriver.TouchSequence.prototype.tapAndHold = function(location) {
        137 var command = new webdriver.Command(webdriver.CommandName.TOUCH_DOWN).
        138 setParameter('x', location.x).
        139 setParameter('y', location.y);
        140
        141 this.schedule_('tapAndHold', command);
        142 return this;
        143};
        144
        145
        146/**
        147 * Move a held {@linkplain #tapAndHold touch} to the specified location.
        148 *
        149 * @param {{x: number, y: number}} location The location to move to.
        150 * @return {!webdriver.TouchSequence} A self reference.
        151 */
        152webdriver.TouchSequence.prototype.move = function(location) {
        153 var command = new webdriver.Command(webdriver.CommandName.TOUCH_MOVE).
        154 setParameter('x', location.x).
        155 setParameter('y', location.y);
        156
        157 this.schedule_('move', command);
        158 return this;
        159};
        160
        161
        162/**
        163 * Release a held {@linkplain #tapAndHold touch} at the specified location.
        164 *
        165 * @param {{x: number, y: number}} location The location to release at.
        166 * @return {!webdriver.TouchSequence} A self reference.
        167 */
        168webdriver.TouchSequence.prototype.release = function(location) {
        169 var command = new webdriver.Command(webdriver.CommandName.TOUCH_UP).
        170 setParameter('x', location.x).
        171 setParameter('y', location.y);
        172
        173 this.schedule_('release', command);
        174 return this;
        175};
        176
        177
        178/**
        179 * Scrolls the touch screen by the given offset.
        180 *
        181 * @param {{x: number, y: number}} offset The offset to scroll to.
        182 * @return {!webdriver.TouchSequence} A self reference.
        183 */
        184webdriver.TouchSequence.prototype.scroll = function(offset) {
        185 var command = new webdriver.Command(webdriver.CommandName.TOUCH_SCROLL).
        186 setParameter('xoffset', offset.x).
        187 setParameter('yoffset', offset.y);
        188
        189 this.schedule_('scroll', command);
        190 return this;
        191};
        192
        193
        194/**
        195 * Scrolls the touch screen, starting on `elem` and moving by the specified
        196 * offset.
        197 *
        198 * @param {!webdriver.WebElement} elem The element where scroll starts.
        199 * @param {{x: number, y: number}} offset The offset to scroll to.
        200 * @return {!webdriver.TouchSequence} A self reference.
        201 */
        202webdriver.TouchSequence.prototype.scrollFromElement = function(elem, offset) {
        203 var command = new webdriver.Command(webdriver.CommandName.TOUCH_SCROLL).
        204 setParameter('element', elem.getRawId()).
        205 setParameter('xoffset', offset.x).
        206 setParameter('yoffset', offset.y);
        207
        208 this.schedule_('scrollFromElement', command);
        209 return this;
        210};
        211
        212
        213/**
        214 * Flick, starting anywhere on the screen, at speed xspeed and yspeed.
        215 *
        216 * @param {{xspeed: number, yspeed: number}} speed The speed to flick in each
        217 direction, in pixels per second.
        218 * @return {!webdriver.TouchSequence} A self reference.
        219 */
        220webdriver.TouchSequence.prototype.flick = function(speed) {
        221 var command = new webdriver.Command(webdriver.CommandName.TOUCH_FLICK).
        222 setParameter('xspeed', speed.xspeed).
        223 setParameter('yspeed', speed.yspeed);
        224
        225 this.schedule_('flick', command);
        226 return this;
        227};
        228
        229
        230/**
        231 * Flick starting at elem and moving by x and y at specified speed.
        232 *
        233 * @param {!webdriver.WebElement} elem The element where flick starts.
        234 * @param {{x: number, y: number}} offset The offset to flick to.
        235 * @param {number} speed The speed to flick at in pixels per second.
        236 * @return {!webdriver.TouchSequence} A self reference.
        237 */
        238webdriver.TouchSequence.prototype.flickElement = function(elem, offset, speed) {
        239 var command = new webdriver.Command(webdriver.CommandName.TOUCH_FLICK).
        240 setParameter('element', elem.getRawId()).
        241 setParameter('xoffset', offset.x).
        242 setParameter('yoffset', offset.y).
        243 setParameter('speed', speed);
        244
        245 this.schedule_('flickElement', command);
        246 return this;
        247};
        248
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/until.js.src.html b/docs/source/lib/webdriver/until.js.src.html new file mode 100644 index 0000000..bfe9ce0 --- /dev/null +++ b/docs/source/lib/webdriver/until.js.src.html @@ -0,0 +1 @@ +until.js

        lib/webdriver/until.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines common conditions for use with
        20 * {@link webdriver.WebDriver#wait WebDriver wait}.
        21 *
        22 * Sample usage:
        23 *
        24 * driver.get('http://www.google.com/ncr');
        25 *
        26 * var query = driver.wait(until.elementLocated(By.name('q')));
        27 * query.sendKeys('webdriver\n');
        28 *
        29 * driver.wait(until.titleIs('webdriver - Google Search'));
        30 *
        31 * To define a custom condition, simply call WebDriver.wait with a function
        32 * that will eventually return a truthy-value (neither null, undefined, false,
        33 * 0, or the empty string):
        34 *
        35 * driver.wait(function() {
        36 * return driver.getTitle().then(function(title) {
        37 * return title === 'webdriver - Google Search';
        38 * });
        39 * }, 1000);
        40 */
        41
        42goog.provide('webdriver.until');
        43
        44goog.require('bot.ErrorCode');
        45goog.require('goog.array');
        46goog.require('goog.string');
        47goog.require('webdriver.Locator');
        48
        49
        50
        51goog.scope(function() {
        52
        53var until = webdriver.until;
        54
        55
        56/**
        57 * Defines a condition to
        58 * @param {string} message A descriptive error message. Should complete the
        59 * sentence "Waiting [...]"
        60 * @param {function(!webdriver.WebDriver): OUT} fn The condition function to
        61 * evaluate on each iteration of the wait loop.
        62 * @constructor
        63 * @struct
        64 * @final
        65 * @template OUT
        66 */
        67until.Condition = function(message, fn) {
        68 /** @private {string} */
        69 this.description_ = 'Waiting ' + message;
        70
        71 /** @type {function(!webdriver.WebDriver): OUT} */
        72 this.fn = fn;
        73};
        74
        75
        76/** @return {string} A description of this condition. */
        77until.Condition.prototype.description = function() {
        78 return this.description_;
        79};
        80
        81
        82/**
        83 * Creates a condition that will wait until the input driver is able to switch
        84 * to the designated frame. The target frame may be specified as
        85 *
        86 * 1. a numeric index into
        87 * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames)
        88 * for the currently selected frame.
        89 * 2. a {@link webdriver.WebElement}, which must reference a FRAME or IFRAME
        90 * element on the current page.
        91 * 3. a locator which may be used to first locate a FRAME or IFRAME on the
        92 * current page before attempting to switch to it.
        93 *
        94 * Upon successful resolution of this condition, the driver will be left
        95 * focused on the new frame.
        96 *
        97 * @param {!(number|webdriver.WebElement|
        98 * webdriver.Locator|webdriver.By.Hash|
        99 * function(!webdriver.WebDriver): !webdriver.WebElement)} frame
        100 * The frame identifier.
        101 * @return {!until.Condition.<boolean>} A new condition.
        102 */
        103until.ableToSwitchToFrame = function(frame) {
        104 var condition;
        105 if (goog.isNumber(frame) || frame instanceof webdriver.WebElement) {
        106 condition = attemptToSwitchFrames;
        107 } else {
        108 condition = function(driver) {
        109 var locator =
        110 /** @type {!(webdriver.Locator|webdriver.By.Hash|Function)} */(frame);
        111 return driver.findElements(locator).then(function(els) {
        112 if (els.length) {
        113 return attemptToSwitchFrames(driver, els[0]);
        114 }
        115 });
        116 };
        117 }
        118
        119 return new until.Condition('to be able to switch to frame', condition);
        120
        121 function attemptToSwitchFrames(driver, frame) {
        122 return driver.switchTo().frame(frame).then(
        123 function() { return true; },
        124 function(e) {
        125 if (e && e.code !== bot.ErrorCode.NO_SUCH_FRAME) {
        126 throw e;
        127 }
        128 });
        129 }
        130};
        131
        132
        133/**
        134 * Creates a condition that waits for an alert to be opened. Upon success, the
        135 * returned promise will be fulfilled with the handle for the opened alert.
        136 *
        137 * @return {!until.Condition.<!webdriver.Alert>} The new condition.
        138 */
        139until.alertIsPresent = function() {
        140 return new until.Condition('for alert to be present', function(driver) {
        141 return driver.switchTo().alert().thenCatch(function(e) {
        142 if (e && e.code !== bot.ErrorCode.NO_SUCH_ALERT) {
        143 throw e;
        144 }
        145 });
        146 });
        147};
        148
        149
        150/**
        151 * Creates a condition that will wait for the current page's title to match the
        152 * given value.
        153 *
        154 * @param {string} title The expected page title.
        155 * @return {!until.Condition.<boolean>} The new condition.
        156 */
        157until.titleIs = function(title) {
        158 return new until.Condition(
        159 'for title to be ' + goog.string.quote(title),
        160 function(driver) {
        161 return driver.getTitle().then(function(t) {
        162 return t === title;
        163 });
        164 });
        165};
        166
        167
        168/**
        169 * Creates a condition that will wait for the current page's title to contain
        170 * the given substring.
        171 *
        172 * @param {string} substr The substring that should be present in the page
        173 * title.
        174 * @return {!until.Condition.<boolean>} The new condition.
        175 */
        176until.titleContains = function(substr) {
        177 return new until.Condition(
        178 'for title to contain ' + goog.string.quote(substr),
        179 function(driver) {
        180 return driver.getTitle().then(function(title) {
        181 return title.indexOf(substr) !== -1;
        182 });
        183 });
        184};
        185
        186
        187/**
        188 * Creates a condition that will wait for the current page's title to match the
        189 * given regular expression.
        190 *
        191 * @param {!RegExp} regex The regular expression to test against.
        192 * @return {!until.Condition.<boolean>} The new condition.
        193 */
        194until.titleMatches = function(regex) {
        195 return new until.Condition('for title to match ' + regex, function(driver) {
        196 return driver.getTitle().then(function(title) {
        197 return regex.test(title);
        198 });
        199 });
        200};
        201
        202
        203/**
        204 * Creates a condition that will loop until an element is
        205 * {@link webdriver.WebDriver#findElement found} with the given locator.
        206 *
        207 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
        208 * to use.
        209 * @return {!until.Condition.<!webdriver.WebElement>} The new condition.
        210 */
        211until.elementLocated = function(locator) {
        212 locator = webdriver.Locator.checkLocator(locator);
        213 var locatorStr = goog.isFunction(locator) ? 'by function()' : locator + '';
        214 return new until.Condition('for element to be located ' + locatorStr,
        215 function(driver) {
        216 return driver.findElements(locator).then(function(elements) {
        217 return elements[0];
        218 });
        219 });
        220};
        221
        222
        223/**
        224 * Creates a condition that will loop until at least one element is
        225 * {@link webdriver.WebDriver#findElement found} with the given locator.
        226 *
        227 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
        228 * to use.
        229 * @return {!until.Condition.<!Array.<!webdriver.WebElement>>} The new
        230 * condition.
        231 */
        232until.elementsLocated = function(locator) {
        233 locator = webdriver.Locator.checkLocator(locator);
        234 var locatorStr = goog.isFunction(locator) ? 'by function()' : locator + '';
        235 return new until.Condition(
        236 'for at least one element to be located ' + locatorStr,
        237 function(driver) {
        238 return driver.findElements(locator).then(function(elements) {
        239 return elements.length > 0 ? elements : null;
        240 });
        241 });
        242};
        243
        244
        245/**
        246 * Creates a condition that will wait for the given element to become stale. An
        247 * element is considered stale once it is removed from the DOM, or a new page
        248 * has loaded.
        249 *
        250 * @param {!webdriver.WebElement} element The element that should become stale.
        251 * @return {!until.Condition.<boolean>} The new condition.
        252 */
        253until.stalenessOf = function(element) {
        254 return new until.Condition('element to become stale', function() {
        255 return element.getTagName().then(
        256 function() { return false; },
        257 function(e) {
        258 if (e.code === bot.ErrorCode.STALE_ELEMENT_REFERENCE) {
        259 return true;
        260 }
        261 throw e;
        262 });
        263 });
        264};
        265
        266
        267/**
        268 * Creates a condition that will wait for the given element to become visible.
        269 *
        270 * @param {!webdriver.WebElement} element The element to test.
        271 * @return {!until.Condition.<boolean>} The new condition.
        272 * @see webdriver.WebDriver#isDisplayed
        273 */
        274until.elementIsVisible = function(element) {
        275 return new until.Condition('until element is visible', function() {
        276 return element.isDisplayed();
        277 });
        278};
        279
        280
        281/**
        282 * Creates a condition that will wait for the given element to be in the DOM,
        283 * yet not visible to the user.
        284 *
        285 * @param {!webdriver.WebElement} element The element to test.
        286 * @return {!until.Condition.<boolean>} The new condition.
        287 * @see webdriver.WebDriver#isDisplayed
        288 */
        289until.elementIsNotVisible = function(element) {
        290 return new until.Condition('until element is not visible', function() {
        291 return element.isDisplayed().then(function(v) {
        292 return !v;
        293 });
        294 });
        295};
        296
        297
        298/**
        299 * Creates a condition that will wait for the given element to be enabled.
        300 *
        301 * @param {!webdriver.WebElement} element The element to test.
        302 * @return {!until.Condition.<boolean>} The new condition.
        303 * @see webdriver.WebDriver#isEnabled
        304 */
        305until.elementIsEnabled = function(element) {
        306 return new until.Condition('until element is enabled', function() {
        307 return element.isEnabled();
        308 });
        309};
        310
        311
        312/**
        313 * Creates a condition that will wait for the given element to be disabled.
        314 *
        315 * @param {!webdriver.WebElement} element The element to test.
        316 * @return {!until.Condition.<boolean>} The new condition.
        317 * @see webdriver.WebDriver#isEnabled
        318 */
        319until.elementIsDisabled = function(element) {
        320 return new until.Condition('until element is disabled', function() {
        321 return element.isEnabled().then(function(v) {
        322 return !v;
        323 });
        324 });
        325};
        326
        327
        328/**
        329 * Creates a condition that will wait for the given element to be selected.
        330 * @param {!webdriver.WebElement} element The element to test.
        331 * @return {!until.Condition.<boolean>} The new condition.
        332 * @see webdriver.WebDriver#isSelected
        333 */
        334until.elementIsSelected = function(element) {
        335 return new until.Condition('until element is selected', function() {
        336 return element.isSelected();
        337 });
        338};
        339
        340
        341/**
        342 * Creates a condition that will wait for the given element to be deselected.
        343 *
        344 * @param {!webdriver.WebElement} element The element to test.
        345 * @return {!until.Condition.<boolean>} The new condition.
        346 * @see webdriver.WebDriver#isSelected
        347 */
        348until.elementIsNotSelected = function(element) {
        349 return new until.Condition('until element is not selected', function() {
        350 return element.isSelected().then(function(v) {
        351 return !v;
        352 });
        353 });
        354};
        355
        356
        357/**
        358 * Creates a condition that will wait for the given element's
        359 * {@link webdriver.WebDriver#getText visible text} to match the given
        360 * {@code text} exactly.
        361 *
        362 * @param {!webdriver.WebElement} element The element to test.
        363 * @param {string} text The expected text.
        364 * @return {!until.Condition.<boolean>} The new condition.
        365 * @see webdriver.WebDriver#getText
        366 */
        367until.elementTextIs = function(element, text) {
        368 return new until.Condition('until element text is', function() {
        369 return element.getText().then(function(t) {
        370 return t === text;
        371 });
        372 });
        373};
        374
        375
        376/**
        377 * Creates a condition that will wait for the given element's
        378 * {@link webdriver.WebDriver#getText visible text} to contain the given
        379 * substring.
        380 *
        381 * @param {!webdriver.WebElement} element The element to test.
        382 * @param {string} substr The substring to search for.
        383 * @return {!until.Condition.<boolean>} The new condition.
        384 * @see webdriver.WebDriver#getText
        385 */
        386until.elementTextContains = function(element, substr) {
        387 return new until.Condition('until element text contains', function() {
        388 return element.getText().then(function(t) {
        389 return t.indexOf(substr) != -1;
        390 });
        391 });
        392};
        393
        394
        395/**
        396 * Creates a condition that will wait for the given element's
        397 * {@link webdriver.WebDriver#getText visible text} to match a regular
        398 * expression.
        399 *
        400 * @param {!webdriver.WebElement} element The element to test.
        401 * @param {!RegExp} regex The regular expression to test against.
        402 * @return {!until.Condition.<boolean>} The new condition.
        403 * @see webdriver.WebDriver#getText
        404 */
        405until.elementTextMatches = function(element, regex) {
        406 return new until.Condition('until element text matches', function() {
        407 return element.getText().then(function(t) {
        408 return regex.test(t);
        409 });
        410 });
        411};
        412}); // goog.scope
        \ No newline at end of file diff --git a/docs/source/lib/webdriver/webdriver.js.src.html b/docs/source/lib/webdriver/webdriver.js.src.html index f65ebea..8a5ecc8 100644 --- a/docs/source/lib/webdriver/webdriver.js.src.html +++ b/docs/source/lib/webdriver/webdriver.js.src.html @@ -1 +1 @@ -webdriver.js

        lib/webdriver/webdriver.js

        1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview The heart of the WebDriver JavaScript API.
        17 */
        18
        19goog.provide('webdriver.Alert');
        20goog.provide('webdriver.UnhandledAlertError');
        21goog.provide('webdriver.WebDriver');
        22goog.provide('webdriver.WebElement');
        23
        24goog.require('bot.Error');
        25goog.require('bot.ErrorCode');
        26goog.require('bot.response');
        27goog.require('goog.array');
        28goog.require('goog.object');
        29goog.require('webdriver.ActionSequence');
        30goog.require('webdriver.Command');
        31goog.require('webdriver.CommandName');
        32goog.require('webdriver.Key');
        33goog.require('webdriver.Locator');
        34goog.require('webdriver.Session');
        35goog.require('webdriver.logging');
        36goog.require('webdriver.promise');
        37
        38
        39//////////////////////////////////////////////////////////////////////////////
        40//
        41// webdriver.WebDriver
        42//
        43//////////////////////////////////////////////////////////////////////////////
        44
        45
        46
        47/**
        48 * Creates a new WebDriver client, which provides control over a browser.
        49 *
        50 * Every WebDriver command returns a {@code webdriver.promise.Promise} that
        51 * represents the result of that command. Callbacks may be registered on this
        52 * object to manipulate the command result or catch an expected error. Any
        53 * commands scheduled with a callback are considered sub-commands and will
        54 * execute before the next command in the current frame. For example:
        55 * <pre><code>
        56 * var message = [];
        57 * driver.call(message.push, message, 'a').then(function() {
        58 * driver.call(message.push, message, 'b');
        59 * });
        60 * driver.call(message.push, message, 'c');
        61 * driver.call(function() {
        62 * alert('message is abc? ' + (message.join('') == 'abc'));
        63 * });
        64 * </code></pre>
        65 *
        66 * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a
        67 * known session or a promise that will be resolved to a session.
        68 * @param {!webdriver.CommandExecutor} executor The executor to use when
        69 * sending commands to the browser.
        70 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
        71 * schedule commands through. Defaults to the active flow object.
        72 * @constructor
        73 */
        74webdriver.WebDriver = function(session, executor, opt_flow) {
        75
        76 /** @private {!(webdriver.Session|webdriver.promise.Promise)} */
        77 this.session_ = session;
        78
        79 /** @private {!webdriver.CommandExecutor} */
        80 this.executor_ = executor;
        81
        82 /** @private {!webdriver.promise.ControlFlow} */
        83 this.flow_ = opt_flow || webdriver.promise.controlFlow();
        84};
        85
        86
        87/**
        88 * Creates a new WebDriver client for an existing session.
        89 * @param {!webdriver.CommandExecutor} executor Command executor to use when
        90 * querying for session details.
        91 * @param {string} sessionId ID of the session to attach to.
        92 * @return {!webdriver.WebDriver} A new client for the specified session.
        93 */
        94webdriver.WebDriver.attachToSession = function(executor, sessionId) {
        95 return webdriver.WebDriver.acquireSession_(executor,
        96 new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
        97 setParameter('sessionId', sessionId),
        98 'WebDriver.attachToSession()');
        99};
        100
        101
        102/**
        103 * Creates a new WebDriver session.
        104 * @param {!webdriver.CommandExecutor} executor The executor to create the new
        105 * session with.
        106 * @param {!webdriver.Capabilities} desiredCapabilities The desired
        107 * capabilities for the new session.
        108 * @return {!webdriver.WebDriver} The driver for the newly created session.
        109 */
        110webdriver.WebDriver.createSession = function(executor, desiredCapabilities) {
        111 return webdriver.WebDriver.acquireSession_(executor,
        112 new webdriver.Command(webdriver.CommandName.NEW_SESSION).
        113 setParameter('desiredCapabilities', desiredCapabilities),
        114 'WebDriver.createSession()');
        115};
        116
        117
        118/**
        119 * Sends a command to the server that is expected to return the details for a
        120 * {@link webdriver.Session}. This may either be an existing session, or a
        121 * newly created one.
        122 * @param {!webdriver.CommandExecutor} executor Command executor to use when
        123 * querying for session details.
        124 * @param {!webdriver.Command} command The command to send to fetch the session
        125 * details.
        126 * @param {string} description A descriptive debug label for this action.
        127 * @return {!webdriver.WebDriver} A new WebDriver client for the session.
        128 * @private
        129 */
        130webdriver.WebDriver.acquireSession_ = function(executor, command, description) {
        131 var session = webdriver.promise.controlFlow().execute(function() {
        132 return webdriver.WebDriver.executeCommand_(executor, command).
        133 then(function(response) {
        134 bot.response.checkResponse(response);
        135 return new webdriver.Session(response['sessionId'],
        136 response['value']);
        137 });
        138 }, description);
        139 return new webdriver.WebDriver(session, executor);
        140};
        141
        142
        143/**
        144 * Converts an object to its JSON representation in the WebDriver wire protocol.
        145 * When converting values of type object, the following steps will be taken:
        146 * <ol>
        147 * <li>if the object provides a "toWireValue" function, the return value will
        148 * be returned in its fully resolved state (e.g. this function may return
        149 * promise values)</li>
        150 * <li>if the object provides a "toJSON" function, the return value of this
        151 * function will be returned</li>
        152 * <li>otherwise, the value of each key will be recursively converted according
        153 * to the rules above.</li>
        154 * </ol>
        155 *
        156 * @param {*} obj The object to convert.
        157 * @return {!webdriver.promise.Promise} A promise that will resolve to the
        158 * input value's JSON representation.
        159 * @private
        160 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
        161 */
        162webdriver.WebDriver.toWireValue_ = function(obj) {
        163 switch (goog.typeOf(obj)) {
        164 case 'array':
        165 return webdriver.promise.fullyResolved(
        166 goog.array.map(/** @type {!Array} */ (obj),
        167 webdriver.WebDriver.toWireValue_));
        168 case 'object':
        169 if (goog.isFunction(obj.toWireValue)) {
        170 return webdriver.promise.fullyResolved(obj.toWireValue());
        171 }
        172 if (goog.isFunction(obj.toJSON)) {
        173 return webdriver.promise.fulfilled(obj.toJSON());
        174 }
        175 if (goog.isNumber(obj.nodeType) && goog.isString(obj.nodeName)) {
        176 throw Error([
        177 'Invalid argument type: ', obj.nodeName, '(', obj.nodeType, ')'
        178 ].join(''));
        179 }
        180 return webdriver.promise.fullyResolved(
        181 goog.object.map(/** @type {!Object} */ (obj),
        182 webdriver.WebDriver.toWireValue_));
        183 case 'function':
        184 return webdriver.promise.fulfilled('' + obj);
        185 case 'undefined':
        186 return webdriver.promise.fulfilled(null);
        187 default:
        188 return webdriver.promise.fulfilled(obj);
        189 }
        190};
        191
        192
        193/**
        194 * Converts a value from its JSON representation according to the WebDriver wire
        195 * protocol. Any JSON object containing a
        196 * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a
        197 * {@code webdriver.WebElement} object. All other values will be passed through
        198 * as is.
        199 * @param {!webdriver.WebDriver} driver The driver instance to use as the
        200 * parent of any unwrapped {@code webdriver.WebElement} values.
        201 * @param {*} value The value to convert.
        202 * @return {*} The converted value.
        203 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
        204 * @private
        205 */
        206webdriver.WebDriver.fromWireValue_ = function(driver, value) {
        207 if (goog.isArray(value)) {
        208 value = goog.array.map(/**@type {goog.array.ArrayLike}*/ (value),
        209 goog.partial(webdriver.WebDriver.fromWireValue_, driver));
        210 } else if (value && goog.isObject(value) && !goog.isFunction(value)) {
        211 if (webdriver.WebElement.ELEMENT_KEY in value) {
        212 value = new webdriver.WebElement(driver,
        213 value[webdriver.WebElement.ELEMENT_KEY]);
        214 } else {
        215 value = goog.object.map(/**@type {!Object}*/ (value),
        216 goog.partial(webdriver.WebDriver.fromWireValue_, driver));
        217 }
        218 }
        219 return value;
        220};
        221
        222
        223/**
        224 * Translates a command to its wire-protocol representation before passing it
        225 * to the given {@code executor} for execution.
        226 * @param {!webdriver.CommandExecutor} executor The executor to use.
        227 * @param {!webdriver.Command} command The command to execute.
        228 * @return {!webdriver.promise.Promise} A promise that will resolve with the
        229 * command response.
        230 * @private
        231 */
        232webdriver.WebDriver.executeCommand_ = function(executor, command) {
        233 return webdriver.promise.fullyResolved(command.getParameters()).
        234 then(webdriver.WebDriver.toWireValue_).
        235 then(function(parameters) {
        236 command.setParameters(parameters);
        237 return webdriver.promise.checkedNodeCall(
        238 goog.bind(executor.execute, executor, command));
        239 });
        240};
        241
        242
        243/**
        244 * @return {!webdriver.promise.ControlFlow} The control flow used by this
        245 * instance.
        246 */
        247webdriver.WebDriver.prototype.controlFlow = function() {
        248 return this.flow_;
        249};
        250
        251
        252/**
        253 * Schedules a {@code webdriver.Command} to be executed by this driver's
        254 * {@code webdriver.CommandExecutor}.
        255 * @param {!webdriver.Command} command The command to schedule.
        256 * @param {string} description A description of the command for debugging.
        257 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        258 * the command result.
        259 */
        260webdriver.WebDriver.prototype.schedule = function(command, description) {
        261 var self = this;
        262
        263 checkHasNotQuit();
        264 command.setParameter('sessionId', this.session_);
        265
        266 var flow = this.flow_;
        267 return flow.execute(function() {
        268 // A call to WebDriver.quit() may have been scheduled in the same event
        269 // loop as this |command|, which would prevent us from detecting that the
        270 // driver has quit above. Therefore, we need to make another quick check.
        271 // We still check above so we can fail as early as possible.
        272 checkHasNotQuit();
        273 return webdriver.WebDriver.executeCommand_(self.executor_, command);
        274 }, description).then(function(response) {
        275 try {
        276 bot.response.checkResponse(response);
        277 } catch (ex) {
        278 var value = response['value'];
        279 if (ex.code === bot.ErrorCode.MODAL_DIALOG_OPENED) {
        280 var text = value && value['alert'] ? value['alert']['text'] : '';
        281 throw new webdriver.UnhandledAlertError(ex.message,
        282 new webdriver.Alert(self, text));
        283 }
        284 throw ex;
        285 }
        286 return webdriver.WebDriver.fromWireValue_(self, response['value']);
        287 });
        288
        289 function checkHasNotQuit() {
        290 if (!self.session_) {
        291 throw new Error('This driver instance does not have a valid session ID ' +
        292 '(did you call WebDriver.quit()?) and may no longer be ' +
        293 'used.');
        294 }
        295 }
        296};
        297
        298
        299// ----------------------------------------------------------------------------
        300// Client command functions:
        301// ----------------------------------------------------------------------------
        302
        303
        304/**
        305 * @return {!webdriver.promise.Promise} A promise for this client's session.
        306 */
        307webdriver.WebDriver.prototype.getSession = function() {
        308 return webdriver.promise.when(this.session_);
        309};
        310
        311
        312/**
        313 * @return {!webdriver.promise.Promise} A promise that will resolve with the
        314 * this instance's capabilities.
        315 */
        316webdriver.WebDriver.prototype.getCapabilities = function() {
        317 return webdriver.promise.when(this.session_, function(session) {
        318 return session.getCapabilities();
        319 });
        320};
        321
        322
        323/**
        324 * Schedules a command to quit the current session. After calling quit, this
        325 * instance will be invalidated and may no longer be used to issue commands
        326 * against the browser.
        327 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        328 * the command has completed.
        329 */
        330webdriver.WebDriver.prototype.quit = function() {
        331 var result = this.schedule(
        332 new webdriver.Command(webdriver.CommandName.QUIT),
        333 'WebDriver.quit()');
        334 // Delete our session ID when the quit command finishes; this will allow us to
        335 // throw an error when attemnpting to use a driver post-quit.
        336 return result.thenFinally(goog.bind(function() {
        337 delete this.session_;
        338 }, this));
        339};
        340
        341
        342/**
        343 * Creates a new action sequence using this driver. The sequence will not be
        344 * scheduled for execution until {@link webdriver.ActionSequence#perform} is
        345 * called. Example:
        346 * <pre><code>
        347 * driver.actions().
        348 * mouseDown(element1).
        349 * mouseMove(element2).
        350 * mouseUp().
        351 * perform();
        352 * </code></pre>
        353 * @return {!webdriver.ActionSequence} A new action sequence for this instance.
        354 */
        355webdriver.WebDriver.prototype.actions = function() {
        356 return new webdriver.ActionSequence(this);
        357};
        358
        359
        360/**
        361 * Schedules a command to execute JavaScript in the context of the currently
        362 * selected frame or window. The script fragment will be executed as the body
        363 * of an anonymous function. If the script is provided as a function object,
        364 * that function will be converted to a string for injection into the target
        365 * window.
        366 *
        367 * Any arguments provided in addition to the script will be included as script
        368 * arguments and may be referenced using the {@code arguments} object.
        369 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
        370 * Arrays and objects may also be used as script arguments as long as each item
        371 * adheres to the types previously mentioned.
        372 *
        373 * The script may refer to any variables accessible from the current window.
        374 * Furthermore, the script will execute in the window's context, thus
        375 * {@code document} may be used to refer to the current document. Any local
        376 * variables will not be available once the script has finished executing,
        377 * though global variables will persist.
        378 *
        379 * If the script has a return value (i.e. if the script contains a return
        380 * statement), then the following steps will be taken for resolving this
        381 * functions return value:
        382 * <ul>
        383 * <li>For a HTML element, the value will resolve to a
        384 * {@code webdriver.WebElement}</li>
        385 * <li>Null and undefined return values will resolve to null</li>
        386 * <li>Booleans, numbers, and strings will resolve as is</li>
        387 * <li>Functions will resolve to their string representation</li>
        388 * <li>For arrays and objects, each member item will be converted according to
        389 * the rules above</li>
        390 * </ul>
        391 *
        392 * @param {!(string|Function)} script The script to execute.
        393 * @param {...*} var_args The arguments to pass to the script.
        394 * @return {!webdriver.promise.Promise} A promise that will resolve to the
        395 * scripts return value.
        396 */
        397webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
        398 if (goog.isFunction(script)) {
        399 script = 'return (' + script + ').apply(null, arguments);';
        400 }
        401 return this.schedule(
        402 new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).
        403 setParameter('script', script).
        404 setParameter('args', goog.array.slice(arguments, 1)),
        405 'WebDriver.executeScript()');
        406};
        407
        408
        409/**
        410 * Schedules a command to execute asynchronous JavaScript in the context of the
        411 * currently selected frame or window. The script fragment will be executed as
        412 * the body of an anonymous function. If the script is provided as a function
        413 * object, that function will be converted to a string for injection into the
        414 * target window.
        415 *
        416 * Any arguments provided in addition to the script will be included as script
        417 * arguments and may be referenced using the {@code arguments} object.
        418 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
        419 * Arrays and objects may also be used as script arguments as long as each item
        420 * adheres to the types previously mentioned.
        421 *
        422 * Unlike executing synchronous JavaScript with
        423 * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with
        424 * this function must explicitly signal they are finished by invoking the
        425 * provided callback. This callback will always be injected into the
        426 * executed function as the last argument, and thus may be referenced with
        427 * {@code arguments[arguments.length - 1]}. The following steps will be taken
        428 * for resolving this functions return value against the first argument to the
        429 * script's callback function:
        430 * <ul>
        431 * <li>For a HTML element, the value will resolve to a
        432 * {@code webdriver.WebElement}</li>
        433 * <li>Null and undefined return values will resolve to null</li>
        434 * <li>Booleans, numbers, and strings will resolve as is</li>
        435 * <li>Functions will resolve to their string representation</li>
        436 * <li>For arrays and objects, each member item will be converted according to
        437 * the rules above</li>
        438 * </ul>
        439 *
        440 * Example #1: Performing a sleep that is synchronized with the currently
        441 * selected window:
        442 * <code><pre>
        443 * var start = new Date().getTime();
        444 * driver.executeAsyncScript(
        445 * 'window.setTimeout(arguments[arguments.length - 1], 500);').
        446 * then(function() {
        447 * console.log('Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        448 * });
        449 * </pre></code>
        450 *
        451 * Example #2: Synchronizing a test with an AJAX application:
        452 * <code><pre>
        453 * var button = driver.findElement(By.id('compose-button'));
        454 * button.click();
        455 * driver.executeAsyncScript(
        456 * 'var callback = arguments[arguments.length - 1];' +
        457 * 'mailClient.getComposeWindowWidget().onload(callback);');
        458 * driver.switchTo().frame('composeWidget');
        459 * driver.findElement(By.id('to')).sendKEys('dog@example.com');
        460 * </pre></code>
        461 *
        462 * Example #3: Injecting a XMLHttpRequest and waiting for the result. In this
        463 * example, the inject script is specified with a function literal. When using
        464 * this format, the function is converted to a string for injection, so it
        465 * should not reference any symbols not defined in the scope of the page under
        466 * test.
        467 * <code><pre>
        468 * driver.executeAsyncScript(function() {
        469 * var callback = arguments[arguments.length - 1];
        470 * var xhr = new XMLHttpRequest();
        471 * xhr.open("GET", "/resource/data.json", true);
        472 * xhr.onreadystatechange = function() {
        473 * if (xhr.readyState == 4) {
        474 * callback(xhr.resposneText);
        475 * }
        476 * }
        477 * xhr.send('');
        478 * }).then(function(str) {
        479 * console.log(JSON.parse(str)['food']);
        480 * });
        481 * </pre></code>
        482 *
        483 * @param {!(string|Function)} script The script to execute.
        484 * @param {...*} var_args The arguments to pass to the script.
        485 * @return {!webdriver.promise.Promise} A promise that will resolve to the
        486 * scripts return value.
        487 */
        488webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {
        489 if (goog.isFunction(script)) {
        490 script = 'return (' + script + ').apply(null, arguments);';
        491 }
        492 return this.schedule(
        493 new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).
        494 setParameter('script', script).
        495 setParameter('args', goog.array.slice(arguments, 1)),
        496 'WebDriver.executeScript()');
        497};
        498
        499
        500/**
        501 * Schedules a command to execute a custom function.
        502 * @param {!Function} fn The function to execute.
        503 * @param {Object=} opt_scope The object in whose scope to execute the function.
        504 * @param {...*} var_args Any arguments to pass to the function.
        505 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        506 * function's result.
        507 */
        508webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {
        509 var args = goog.array.slice(arguments, 2);
        510 var flow = this.flow_;
        511 return flow.execute(function() {
        512 return webdriver.promise.fullyResolved(args).then(function(args) {
        513 return fn.apply(opt_scope, args);
        514 });
        515 }, 'WebDriver.call(' + (fn.name || 'function') + ')');
        516};
        517
        518
        519/**
        520 * Schedules a command to wait for a condition to hold, as defined by some
        521 * user supplied function. If any errors occur while evaluating the wait, they
        522 * will be allowed to propagate.
        523 * @param {function():boolean} fn The function to evaluate as a wait condition.
        524 * @param {number} timeout How long to wait for the condition to be true.
        525 * @param {string=} opt_message An optional message to use if the wait times
        526 * out.
        527 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        528 * wait condition has been satisfied.
        529 */
        530webdriver.WebDriver.prototype.wait = function(fn, timeout, opt_message) {
        531 return this.flow_.wait(fn, timeout, opt_message);
        532};
        533
        534
        535/**
        536 * Schedules a command to make the driver sleep for the given amount of time.
        537 * @param {number} ms The amount of time, in milliseconds, to sleep.
        538 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        539 * sleep has finished.
        540 */
        541webdriver.WebDriver.prototype.sleep = function(ms) {
        542 return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
        543};
        544
        545
        546/**
        547 * Schedules a command to retrieve they current window handle.
        548 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        549 * current window handle.
        550 */
        551webdriver.WebDriver.prototype.getWindowHandle = function() {
        552 return this.schedule(
        553 new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),
        554 'WebDriver.getWindowHandle()');
        555};
        556
        557
        558/**
        559 * Schedules a command to retrieve the current list of available window handles.
        560 * @return {!webdriver.promise.Promise} A promise that will be resolved with an
        561 * array of window handles.
        562 */
        563webdriver.WebDriver.prototype.getAllWindowHandles = function() {
        564 return this.schedule(
        565 new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),
        566 'WebDriver.getAllWindowHandles()');
        567};
        568
        569
        570/**
        571 * Schedules a command to retrieve the current page's source. The page source
        572 * returned is a representation of the underlying DOM: do not expect it to be
        573 * formatted or escaped in the same way as the response sent from the web
        574 * server.
        575 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        576 * current page source.
        577 */
        578webdriver.WebDriver.prototype.getPageSource = function() {
        579 return this.schedule(
        580 new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),
        581 'WebDriver.getAllWindowHandles()');
        582};
        583
        584
        585/**
        586 * Schedules a command to close the current window.
        587 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        588 * this command has completed.
        589 */
        590webdriver.WebDriver.prototype.close = function() {
        591 return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),
        592 'WebDriver.close()');
        593};
        594
        595
        596/**
        597 * Schedules a command to navigate to the given URL.
        598 * @param {string} url The fully qualified URL to open.
        599 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        600 * document has finished loading.
        601 */
        602webdriver.WebDriver.prototype.get = function(url) {
        603 return this.navigate().to(url);
        604};
        605
        606
        607/**
        608 * Schedules a command to retrieve the URL of the current page.
        609 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        610 * current URL.
        611 */
        612webdriver.WebDriver.prototype.getCurrentUrl = function() {
        613 return this.schedule(
        614 new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),
        615 'WebDriver.getCurrentUrl()');
        616};
        617
        618
        619/**
        620 * Schedules a command to retrieve the current page's title.
        621 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        622 * current page's title.
        623 */
        624webdriver.WebDriver.prototype.getTitle = function() {
        625 return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),
        626 'WebDriver.getTitle()');
        627};
        628
        629
        630/**
        631 * Schedule a command to find an element on the page. If the element cannot be
        632 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
        633 * by the driver. Unlike other commands, this error cannot be suppressed. In
        634 * other words, scheduling a command to find an element doubles as an assert
        635 * that the element is present on the page. To test whether an element is
        636 * present on the page, use {@link #isElementPresent} instead.
        637 *
        638 * <p>The search criteria for an element may be defined using one of the
        639 * factories in the {@link webdriver.By} namespace, or as a short-hand
        640 * {@link webdriver.By.Hash} object. For example, the following two statements
        641 * are equivalent:
        642 * <code><pre>
        643 * var e1 = driver.findElement(By.id('foo'));
        644 * var e2 = driver.findElement({id:'foo'});
        645 * </pre></code>
        646 *
        647 * <p>You may also provide a custom locator function, which takes as input
        648 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
        649 * promise that will resolve to a WebElement. For example, to find the first
        650 * visible link on a page, you could write:
        651 * <code><pre>
        652 * var link = driver.findElement(firstVisibleLink);
        653 *
        654 * function firstVisibleLink(driver) {
        655 * var links = driver.findElements(By.tagName('a'));
        656 * return webdriver.promise.filter(links, function(link) {
        657 * return links.isDisplayed();
        658 * }).then(function(visibleLinks) {
        659 * return visibleLinks[0];
        660 * });
        661 * }
        662 * </pre></code>
        663 *
        664 * <p>When running in the browser, a WebDriver cannot manipulate DOM elements
        665 * directly; it may do so only through a {@link webdriver.WebElement} reference.
        666 * This function may be used to generate a WebElement from a DOM element. A
        667 * reference to the DOM element will be stored in a known location and this
        668 * driver will attempt to retrieve it through {@link #executeScript}. If the
        669 * element cannot be found (eg, it belongs to a different document than the
        670 * one this instance is currently focused on), a
        671 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.
        672 *
        673 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The
        674 * locator to use.
        675 * @return {!webdriver.WebElement} A WebElement that can be used to issue
        676 * commands against the located element. If the element is not found, the
        677 * element will be invalidated and all scheduled commands aborted.
        678 */
        679webdriver.WebDriver.prototype.findElement = function(locator) {
        680 var id;
        681 if ('nodeType' in locator && 'ownerDocument' in locator) {
        682 var element = /** @type {!Element} */ (locator);
        683 id = this.findDomElement_(element).
        684 then(function(elements) {
        685 if (!elements.length) {
        686 throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
        687 'Unable to locate element. Is WebDriver focused on its ' +
        688 'ownerDocument\'s frame?');
        689 }
        690 return elements[0];
        691 });
        692 } else {
        693 locator = webdriver.Locator.checkLocator(locator);
        694 if (goog.isFunction(locator)) {
        695 id = this.findElementInternal_(locator, this);
        696 } else {
        697 var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).
        698 setParameter('using', locator.using).
        699 setParameter('value', locator.value);
        700 id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');
        701 }
        702 }
        703 return new webdriver.WebElement(this, id);
        704};
        705
        706
        707/**
        708 * @param {!Function} locatorFn The locator function to use.
        709 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
        710 * context.
        711 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A
        712 * promise that will resolve to a list of WebElements.
        713 * @private
        714 */
        715webdriver.WebDriver.prototype.findElementInternal_ = function(
        716 locatorFn, context) {
        717 return this.call(goog.partial(locatorFn, context)).then(function(result) {
        718 if (goog.isArray(result)) {
        719 result = result[0];
        720 }
        721 if (!(result instanceof webdriver.WebElement)) {
        722 throw new TypeError('Custom locator did not return a WebElement');
        723 }
        724 return result;
        725 });
        726};
        727
        728
        729/**
        730 * Locates a DOM element so that commands may be issued against it using the
        731 * {@link webdriver.WebElement} class. This is accomplished by storing a
        732 * reference to the element in an object on the element's ownerDocument.
        733 * {@link #executeScript} will then be used to create a WebElement from this
        734 * reference. This requires this driver to currently be focused on the
        735 * ownerDocument's window+frame.
        736
        737 * @param {!Element} element The element to locate.
        738 * @return {!webdriver.promise.Promise} A promise that will be resolved
        739 * with the located WebElement.
        740 * @private
        741 */
        742webdriver.WebDriver.prototype.findDomElement_ = function(element) {
        743 var doc = element.ownerDocument;
        744 var store = doc['$webdriver$'] = doc['$webdriver$'] || {};
        745 var id = Math.floor(Math.random() * goog.now()).toString(36);
        746 store[id] = element;
        747 element[id] = id;
        748
        749 function cleanUp() {
        750 delete store[id];
        751 }
        752
        753 function lookupElement(id) {
        754 var store = document['$webdriver$'];
        755 if (!store) {
        756 return null;
        757 }
        758
        759 var element = store[id];
        760 if (!element || element[id] !== id) {
        761 return [];
        762 }
        763 return [element];
        764 }
        765
        766 return this.executeScript(lookupElement, id).
        767 then(function(value) {
        768 cleanUp();
        769 if (value.length && !(value[0] instanceof webdriver.WebElement)) {
        770 throw new Error('JS locator script result was not a WebElement');
        771 }
        772 return value;
        773 }, cleanUp);
        774};
        775
        776
        777/**
        778 * Schedules a command to test if an element is present on the page.
        779 *
        780 * <p>If given a DOM element, this function will check if it belongs to the
        781 * document the driver is currently focused on. Otherwise, the function will
        782 * test if at least one element can be found with the given search criteria.
        783 *
        784 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|
        785 * Function)} locatorOrElement The locator to use, or the actual
        786 * DOM element to be located by the server.
        787 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
        788 * with whether the element is present on the page.
        789 */
        790webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {
        791 var findElement =
        792 ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) ?
        793 this.findDomElement_(/** @type {!Element} */ (locatorOrElement)) :
        794 this.findElements.apply(this, arguments);
        795 return findElement.then(function(result) {
        796 return !!result.length;
        797 });
        798};
        799
        800
        801/**
        802 * Schedule a command to search for multiple elements on the page.
        803 *
        804 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
        805 * strategy to use when searching for the element.
        806 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        807 * promise that will resolve to an array of WebElements.
        808 */
        809webdriver.WebDriver.prototype.findElements = function(locator) {
        810 locator = webdriver.Locator.checkLocator(locator);
        811 if (goog.isFunction(locator)) {
        812 return this.findElementsInternal_(locator, this);
        813 } else {
        814 var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).
        815 setParameter('using', locator.using).
        816 setParameter('value', locator.value);
        817 return this.schedule(command, 'WebDriver.findElements(' + locator + ')');
        818 }
        819};
        820
        821
        822/**
        823 * @param {!Function} locatorFn The locator function to use.
        824 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
        825 * context.
        826 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        827 * promise that will resolve to an array of WebElements.
        828 * @private
        829 */
        830webdriver.WebDriver.prototype.findElementsInternal_ = function(
        831 locatorFn, context) {
        832 return this.call(goog.partial(locatorFn, context)).then(function(result) {
        833 if (result instanceof webdriver.WebElement) {
        834 return [result];
        835 }
        836
        837 if (!goog.isArray(result)) {
        838 return [];
        839 }
        840
        841 return goog.array.filter(result, function(item) {
        842 return item instanceof webdriver.WebElement;
        843 });
        844 });
        845};
        846
        847
        848/**
        849 * Schedule a command to take a screenshot. The driver makes a best effort to
        850 * return a screenshot of the following, in order of preference:
        851 * <ol>
        852 * <li>Entire page
        853 * <li>Current window
        854 * <li>Visible portion of the current frame
        855 * <li>The screenshot of the entire display containing the browser
        856 * </ol>
        857 *
        858 * @return {!webdriver.promise.Promise} A promise that will be resolved to the
        859 * screenshot as a base-64 encoded PNG.
        860 */
        861webdriver.WebDriver.prototype.takeScreenshot = function() {
        862 return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),
        863 'WebDriver.takeScreenshot()');
        864};
        865
        866
        867/**
        868 * @return {!webdriver.WebDriver.Options} The options interface for this
        869 * instance.
        870 */
        871webdriver.WebDriver.prototype.manage = function() {
        872 return new webdriver.WebDriver.Options(this);
        873};
        874
        875
        876/**
        877 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this
        878 * instance.
        879 */
        880webdriver.WebDriver.prototype.navigate = function() {
        881 return new webdriver.WebDriver.Navigation(this);
        882};
        883
        884
        885/**
        886 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for
        887 * this instance.
        888 */
        889webdriver.WebDriver.prototype.switchTo = function() {
        890 return new webdriver.WebDriver.TargetLocator(this);
        891};
        892
        893
        894
        895/**
        896 * Interface for navigating back and forth in the browser history.
        897 * @param {!webdriver.WebDriver} driver The parent driver.
        898 * @constructor
        899 */
        900webdriver.WebDriver.Navigation = function(driver) {
        901
        902 /** @private {!webdriver.WebDriver} */
        903 this.driver_ = driver;
        904};
        905
        906
        907/**
        908 * Schedules a command to navigate to a new URL.
        909 * @param {string} url The URL to navigate to.
        910 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        911 * URL has been loaded.
        912 */
        913webdriver.WebDriver.Navigation.prototype.to = function(url) {
        914 return this.driver_.schedule(
        915 new webdriver.Command(webdriver.CommandName.GET).
        916 setParameter('url', url),
        917 'WebDriver.navigate().to(' + url + ')');
        918};
        919
        920
        921/**
        922 * Schedules a command to move backwards in the browser history.
        923 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        924 * navigation event has completed.
        925 */
        926webdriver.WebDriver.Navigation.prototype.back = function() {
        927 return this.driver_.schedule(
        928 new webdriver.Command(webdriver.CommandName.GO_BACK),
        929 'WebDriver.navigate().back()');
        930};
        931
        932
        933/**
        934 * Schedules a command to move forwards in the browser history.
        935 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        936 * navigation event has completed.
        937 */
        938webdriver.WebDriver.Navigation.prototype.forward = function() {
        939 return this.driver_.schedule(
        940 new webdriver.Command(webdriver.CommandName.GO_FORWARD),
        941 'WebDriver.navigate().forward()');
        942};
        943
        944
        945/**
        946 * Schedules a command to refresh the current page.
        947 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        948 * navigation event has completed.
        949 */
        950webdriver.WebDriver.Navigation.prototype.refresh = function() {
        951 return this.driver_.schedule(
        952 new webdriver.Command(webdriver.CommandName.REFRESH),
        953 'WebDriver.navigate().refresh()');
        954};
        955
        956
        957
        958/**
        959 * Provides methods for managing browser and driver state.
        960 * @param {!webdriver.WebDriver} driver The parent driver.
        961 * @constructor
        962 */
        963webdriver.WebDriver.Options = function(driver) {
        964
        965 /** @private {!webdriver.WebDriver} */
        966 this.driver_ = driver;
        967};
        968
        969
        970/**
        971 * Schedules a command to add a cookie.
        972 * @param {string} name The cookie name.
        973 * @param {string} value The cookie value.
        974 * @param {string=} opt_path The cookie path.
        975 * @param {string=} opt_domain The cookie domain.
        976 * @param {boolean=} opt_isSecure Whether the cookie is secure.
        977 * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as
        978 * a number, should be in milliseconds since midnight, January 1, 1970 UTC.
        979 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        980 * cookie has been added to the page.
        981 */
        982webdriver.WebDriver.Options.prototype.addCookie = function(
        983 name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
        984 // We do not allow '=' or ';' in the name.
        985 if (/[;=]/.test(name)) {
        986 throw Error('Invalid cookie name "' + name + '"');
        987 }
        988
        989 // We do not allow ';' in value.
        990 if (/;/.test(value)) {
        991 throw Error('Invalid cookie value "' + value + '"');
        992 }
        993
        994 var cookieString = name + '=' + value +
        995 (opt_domain ? ';domain=' + opt_domain : '') +
        996 (opt_path ? ';path=' + opt_path : '') +
        997 (opt_isSecure ? ';secure' : '');
        998
        999 var expiry;
        1000 if (goog.isDef(opt_expiry)) {
        1001 var expiryDate;
        1002 if (goog.isNumber(opt_expiry)) {
        1003 expiryDate = new Date(opt_expiry);
        1004 } else {
        1005 expiryDate = /** @type {!Date} */ (opt_expiry);
        1006 opt_expiry = expiryDate.getTime();
        1007 }
        1008 cookieString += ';expires=' + expiryDate.toUTCString();
        1009 // Convert from milliseconds to seconds.
        1010 expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
        1011 }
        1012
        1013 return this.driver_.schedule(
        1014 new webdriver.Command(webdriver.CommandName.ADD_COOKIE).
        1015 setParameter('cookie', {
        1016 'name': name,
        1017 'value': value,
        1018 'path': opt_path,
        1019 'domain': opt_domain,
        1020 'secure': !!opt_isSecure,
        1021 'expiry': expiry
        1022 }),
        1023 'WebDriver.manage().addCookie(' + cookieString + ')');
        1024};
        1025
        1026
        1027/**
        1028 * Schedules a command to delete all cookies visible to the current page.
        1029 * @return {!webdriver.promise.Promise} A promise that will be resolved when all
        1030 * cookies have been deleted.
        1031 */
        1032webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {
        1033 return this.driver_.schedule(
        1034 new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),
        1035 'WebDriver.manage().deleteAllCookies()');
        1036};
        1037
        1038
        1039/**
        1040 * Schedules a command to delete the cookie with the given name. This command is
        1041 * a no-op if there is no cookie with the given name visible to the current
        1042 * page.
        1043 * @param {string} name The name of the cookie to delete.
        1044 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1045 * cookie has been deleted.
        1046 */
        1047webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {
        1048 return this.driver_.schedule(
        1049 new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).
        1050 setParameter('name', name),
        1051 'WebDriver.manage().deleteCookie(' + name + ')');
        1052};
        1053
        1054
        1055/**
        1056 * Schedules a command to retrieve all cookies visible to the current page.
        1057 * Each cookie will be returned as a JSON object as described by the WebDriver
        1058 * wire protocol.
        1059 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1060 * cookies visible to the current page.
        1061 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object
        1062 */
        1063webdriver.WebDriver.Options.prototype.getCookies = function() {
        1064 return this.driver_.schedule(
        1065 new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),
        1066 'WebDriver.manage().getCookies()');
        1067};
        1068
        1069
        1070/**
        1071 * Schedules a command to retrieve the cookie with the given name. Returns null
        1072 * if there is no such cookie. The cookie will be returned as a JSON object as
        1073 * described by the WebDriver wire protocol.
        1074 * @param {string} name The name of the cookie to retrieve.
        1075 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1076 * named cookie, or {@code null} if there is no such cookie.
        1077 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object
        1078 */
        1079webdriver.WebDriver.Options.prototype.getCookie = function(name) {
        1080 return this.getCookies().then(function(cookies) {
        1081 return goog.array.find(cookies, function(cookie) {
        1082 return cookie && cookie['name'] == name;
        1083 });
        1084 });
        1085};
        1086
        1087
        1088/**
        1089 * @return {!webdriver.WebDriver.Logs} The interface for managing driver
        1090 * logs.
        1091 */
        1092webdriver.WebDriver.Options.prototype.logs = function() {
        1093 return new webdriver.WebDriver.Logs(this.driver_);
        1094};
        1095
        1096
        1097/**
        1098 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver
        1099 * timeouts.
        1100 */
        1101webdriver.WebDriver.Options.prototype.timeouts = function() {
        1102 return new webdriver.WebDriver.Timeouts(this.driver_);
        1103};
        1104
        1105
        1106/**
        1107 * @return {!webdriver.WebDriver.Window} The interface for managing the
        1108 * current window.
        1109 */
        1110webdriver.WebDriver.Options.prototype.window = function() {
        1111 return new webdriver.WebDriver.Window(this.driver_);
        1112};
        1113
        1114
        1115
        1116/**
        1117 * An interface for managing timeout behavior for WebDriver instances.
        1118 * @param {!webdriver.WebDriver} driver The parent driver.
        1119 * @constructor
        1120 */
        1121webdriver.WebDriver.Timeouts = function(driver) {
        1122
        1123 /** @private {!webdriver.WebDriver} */
        1124 this.driver_ = driver;
        1125};
        1126
        1127
        1128/**
        1129 * Specifies the amount of time the driver should wait when searching for an
        1130 * element if it is not immediately present.
        1131 * <p/>
        1132 * When searching for a single element, the driver should poll the page
        1133 * until the element has been found, or this timeout expires before failing
        1134 * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
        1135 * for multiple elements, the driver should poll the page until at least one
        1136 * element has been found or this timeout has expired.
        1137 * <p/>
        1138 * Setting the wait timeout to 0 (its default value), disables implicit
        1139 * waiting.
        1140 * <p/>
        1141 * Increasing the implicit wait timeout should be used judiciously as it
        1142 * will have an adverse effect on test run time, especially when used with
        1143 * slower location strategies like XPath.
        1144 *
        1145 * @param {number} ms The amount of time to wait, in milliseconds.
        1146 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1147 * implicit wait timeout has been set.
        1148 */
        1149webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {
        1150 return this.driver_.schedule(
        1151 new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).
        1152 setParameter('ms', ms < 0 ? 0 : ms),
        1153 'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');
        1154};
        1155
        1156
        1157/**
        1158 * Sets the amount of time to wait, in milliseconds, for an asynchronous script
        1159 * to finish execution before returning an error. If the timeout is less than or
        1160 * equal to 0, the script will be allowed to run indefinitely.
        1161 *
        1162 * @param {number} ms The amount of time to wait, in milliseconds.
        1163 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1164 * script timeout has been set.
        1165 */
        1166webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {
        1167 return this.driver_.schedule(
        1168 new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).
        1169 setParameter('ms', ms < 0 ? 0 : ms),
        1170 'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');
        1171};
        1172
        1173
        1174/**
        1175 * Sets the amount of time to wait for a page load to complete before returning
        1176 * an error. If the timeout is negative, page loads may be indefinite.
        1177 * @param {number} ms The amount of time to wait, in milliseconds.
        1178 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        1179 * the timeout has been set.
        1180 */
        1181webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {
        1182 return this.driver_.schedule(
        1183 new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).
        1184 setParameter('type', 'page load').
        1185 setParameter('ms', ms),
        1186 'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');
        1187};
        1188
        1189
        1190
        1191/**
        1192 * An interface for managing the current window.
        1193 * @param {!webdriver.WebDriver} driver The parent driver.
        1194 * @constructor
        1195 */
        1196webdriver.WebDriver.Window = function(driver) {
        1197
        1198 /** @private {!webdriver.WebDriver} */
        1199 this.driver_ = driver;
        1200};
        1201
        1202
        1203/**
        1204 * Retrieves the window's current position, relative to the top left corner of
        1205 * the screen.
        1206 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1207 * window's position in the form of a {x:number, y:number} object literal.
        1208 */
        1209webdriver.WebDriver.Window.prototype.getPosition = function() {
        1210 return this.driver_.schedule(
        1211 new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).
        1212 setParameter('windowHandle', 'current'),
        1213 'WebDriver.manage().window().getPosition()');
        1214};
        1215
        1216
        1217/**
        1218 * Repositions the current window.
        1219 * @param {number} x The desired horizontal position, relative to the left side
        1220 * of the screen.
        1221 * @param {number} y The desired vertical position, relative to the top of the
        1222 * of the screen.
        1223 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1224 * command has completed.
        1225 */
        1226webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {
        1227 return this.driver_.schedule(
        1228 new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).
        1229 setParameter('windowHandle', 'current').
        1230 setParameter('x', x).
        1231 setParameter('y', y),
        1232 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
        1233};
        1234
        1235
        1236/**
        1237 * Retrieves the window's current size.
        1238 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1239 * window's size in the form of a {width:number, height:number} object
        1240 * literal.
        1241 */
        1242webdriver.WebDriver.Window.prototype.getSize = function() {
        1243 return this.driver_.schedule(
        1244 new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).
        1245 setParameter('windowHandle', 'current'),
        1246 'WebDriver.manage().window().getSize()');
        1247};
        1248
        1249
        1250/**
        1251 * Resizes the current window.
        1252 * @param {number} width The desired window width.
        1253 * @param {number} height The desired window height.
        1254 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1255 * command has completed.
        1256 */
        1257webdriver.WebDriver.Window.prototype.setSize = function(width, height) {
        1258 return this.driver_.schedule(
        1259 new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).
        1260 setParameter('windowHandle', 'current').
        1261 setParameter('width', width).
        1262 setParameter('height', height),
        1263 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
        1264};
        1265
        1266
        1267/**
        1268 * Maximizes the current window.
        1269 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1270 * command has completed.
        1271 */
        1272webdriver.WebDriver.Window.prototype.maximize = function() {
        1273 return this.driver_.schedule(
        1274 new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).
        1275 setParameter('windowHandle', 'current'),
        1276 'WebDriver.manage().window().maximize()');
        1277};
        1278
        1279
        1280/**
        1281 * Interface for managing WebDriver log records.
        1282 * @param {!webdriver.WebDriver} driver The parent driver.
        1283 * @constructor
        1284 */
        1285webdriver.WebDriver.Logs = function(driver) {
        1286
        1287 /** @private {!webdriver.WebDriver} */
        1288 this.driver_ = driver;
        1289};
        1290
        1291
        1292/**
        1293 * Fetches available log entries for the given type.
        1294 *
        1295 * <p/>Note that log buffers are reset after each call, meaning that
        1296 * available log entries correspond to those entries not yet returned for a
        1297 * given log type. In practice, this means that this call will return the
        1298 * available log entries since the last call, or from the start of the
        1299 * session.
        1300 *
        1301 * @param {!webdriver.logging.Type} type The desired log type.
        1302 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A
        1303 * promise that will resolve to a list of log entries for the specified
        1304 * type.
        1305 */
        1306webdriver.WebDriver.Logs.prototype.get = function(type) {
        1307 return this.driver_.schedule(
        1308 new webdriver.Command(webdriver.CommandName.GET_LOG).
        1309 setParameter('type', type),
        1310 'WebDriver.manage().logs().get(' + type + ')').
        1311 then(function(entries) {
        1312 return goog.array.map(entries, function(entry) {
        1313 if (!(entry instanceof webdriver.logging.Entry)) {
        1314 return new webdriver.logging.Entry(
        1315 entry['level'], entry['message'], entry['timestamp']);
        1316 }
        1317 return entry;
        1318 });
        1319 });
        1320};
        1321
        1322
        1323/**
        1324 * Retrieves the log types available to this driver.
        1325 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A
        1326 * promise that will resolve to a list of available log types.
        1327 */
        1328webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {
        1329 return this.driver_.schedule(
        1330 new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),
        1331 'WebDriver.manage().logs().getAvailableLogTypes()');
        1332};
        1333
        1334
        1335
        1336/**
        1337 * An interface for changing the focus of the driver to another frame or window.
        1338 * @param {!webdriver.WebDriver} driver The parent driver.
        1339 * @constructor
        1340 */
        1341webdriver.WebDriver.TargetLocator = function(driver) {
        1342
        1343 /** @private {!webdriver.WebDriver} */
        1344 this.driver_ = driver;
        1345};
        1346
        1347
        1348/**
        1349 * Schedules a command retrieve the {@code document.activeElement} element on
        1350 * the current document, or {@code document.body} if activeElement is not
        1351 * available.
        1352 * @return {!webdriver.WebElement} The active element.
        1353 */
        1354webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {
        1355 var id = this.driver_.schedule(
        1356 new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),
        1357 'WebDriver.switchTo().activeElement()');
        1358 return new webdriver.WebElement(this.driver_, id);
        1359};
        1360
        1361
        1362/**
        1363 * Schedules a command to switch focus of all future commands to the first frame
        1364 * on the page.
        1365 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1366 * driver has changed focus to the default content.
        1367 */
        1368webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {
        1369 return this.driver_.schedule(
        1370 new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
        1371 setParameter('id', null),
        1372 'WebDriver.switchTo().defaultContent()');
        1373};
        1374
        1375
        1376/**
        1377 * Schedules a command to switch the focus of all future commands to another
        1378 * frame on the page.
        1379 * <p/>
        1380 * If the frame is specified by a number, the command will switch to the frame
        1381 * by its (zero-based) index into the {@code window.frames} collection.
        1382 * <p/>
        1383 * If the frame is specified by a string, the command will select the frame by
        1384 * its name or ID. To select sub-frames, simply separate the frame names/IDs by
        1385 * dots. As an example, "main.child" will select the frame with the name "main"
        1386 * and then its child "child".
        1387 * <p/>
        1388 * If the specified frame can not be found, the deferred result will errback
        1389 * with a {@code bot.ErrorCode.NO_SUCH_FRAME} error.
        1390 * @param {string|number} nameOrIndex The frame locator.
        1391 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1392 * driver has changed focus to the specified frame.
        1393 */
        1394webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {
        1395 return this.driver_.schedule(
        1396 new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
        1397 setParameter('id', nameOrIndex),
        1398 'WebDriver.switchTo().frame(' + nameOrIndex + ')');
        1399};
        1400
        1401
        1402/**
        1403 * Schedules a command to switch the focus of all future commands to another
        1404 * window. Windows may be specified by their {@code window.name} attribute or
        1405 * by its handle (as returned by {@code webdriver.WebDriver#getWindowHandles}).
        1406 * <p/>
        1407 * If the specificed window can not be found, the deferred result will errback
        1408 * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error.
        1409 * @param {string} nameOrHandle The name or window handle of the window to
        1410 * switch focus to.
        1411 * @return {!webdriver.promise.Promise} A promise that will be resolved when the
        1412 * driver has changed focus to the specified window.
        1413 */
        1414webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {
        1415 return this.driver_.schedule(
        1416 new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).
        1417 setParameter('name', nameOrHandle),
        1418 'WebDriver.switchTo().window(' + nameOrHandle + ')');
        1419};
        1420
        1421
        1422/**
        1423 * Schedules a command to change focus to the active alert dialog. This command
        1424 * will return a {@link bot.ErrorCode.NO_MODAL_DIALOG_OPEN} error if a modal
        1425 * dialog is not currently open.
        1426 * @return {!webdriver.Alert} The open alert.
        1427 */
        1428webdriver.WebDriver.TargetLocator.prototype.alert = function() {
        1429 var text = this.driver_.schedule(
        1430 new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),
        1431 'WebDriver.switchTo().alert()');
        1432 return new webdriver.Alert(this.driver_, text);
        1433};
        1434
        1435
        1436/**
        1437 * Simulate pressing many keys at once in a "chord". Takes a sequence of
        1438 * {@link webdriver.Key}s or strings, appends each of the values to a string,
        1439 * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns
        1440 * the resultant string.
        1441 *
        1442 * Note: when the low-level webdriver key handlers see Keys.NULL, active
        1443 * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.
        1444 *
        1445 * @param {...string} var_args The key sequence to concatenate.
        1446 * @return {string} The null-terminated key sequence.
        1447 * @see http://code.google.com/p/webdriver/issues/detail?id=79
        1448 */
        1449webdriver.Key.chord = function(var_args) {
        1450 var sequence = goog.array.reduce(
        1451 goog.array.slice(arguments, 0),
        1452 function(str, key) {
        1453 return str + key;
        1454 }, '');
        1455 sequence += webdriver.Key.NULL;
        1456 return sequence;
        1457};
        1458
        1459
        1460//////////////////////////////////////////////////////////////////////////////
        1461//
        1462// webdriver.WebElement
        1463//
        1464//////////////////////////////////////////////////////////////////////////////
        1465
        1466
        1467
        1468/**
        1469 * Represents a DOM element. WebElements can be found by searching from the
        1470 * document root using a {@code webdriver.WebDriver} instance, or by searching
        1471 * under another {@code webdriver.WebElement}:
        1472 * <pre><code>
        1473 * driver.get('http://www.google.com');
        1474 * var searchForm = driver.findElement(By.tagName('form'));
        1475 * var searchBox = searchForm.findElement(By.name('q'));
        1476 * searchBox.sendKeys('webdriver');
        1477 * </code></pre>
        1478 *
        1479 * The WebElement is implemented as a promise for compatibility with the promise
        1480 * API. It will always resolve itself when its internal state has been fully
        1481 * resolved and commands may be issued against the element. This can be used to
        1482 * catch errors when an element cannot be located on the page:
        1483 * <pre><code>
        1484 * driver.findElement(By.id('not-there')).then(function(element) {
        1485 * alert('Found an element that was not expected to be there!');
        1486 * }, function(error) {
        1487 * alert('The element was not found, as expected');
        1488 * });
        1489 * </code></pre>
        1490 *
        1491 * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
        1492 * element.
        1493 * @param {!(string|webdriver.promise.Promise)} id Either the opaque ID for the
        1494 * underlying DOM element assigned by the server, or a promise that will
        1495 * resolve to that ID or another WebElement.
        1496 * @constructor
        1497 * @extends {webdriver.promise.Deferred}
        1498 */
        1499webdriver.WebElement = function(driver, id) {
        1500 webdriver.promise.Deferred.call(this, null, driver.controlFlow());
        1501
        1502 /**
        1503 * The parent WebDriver instance for this element.
        1504 * @private {!webdriver.WebDriver}
        1505 */
        1506 this.driver_ = driver;
        1507
        1508 // This class is responsible for resolving itself; delete the resolve and
        1509 // reject methods so they may not be accessed by consumers of this class.
        1510 var fulfill = goog.partial(this.fulfill, this);
        1511 var reject = this.reject;
        1512 delete this.promise;
        1513 delete this.fulfill;
        1514 delete this.reject;
        1515
        1516 /**
        1517 * A promise that resolves to the JSON representation of this WebElement's
        1518 * ID, as defined by the WebDriver wire protocol.
        1519 * @private {!webdriver.promise.Promise}
        1520 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
        1521 */
        1522 this.id_ = webdriver.promise.when(id, function(id) {
        1523 if (id instanceof webdriver.WebElement) {
        1524 return id.id_;
        1525 } else if (goog.isDef(id[webdriver.WebElement.ELEMENT_KEY])) {
        1526 return id;
        1527 }
        1528
        1529 var json = {};
        1530 json[webdriver.WebElement.ELEMENT_KEY] = id;
        1531 return json;
        1532 });
        1533
        1534 // This WebElement should not be resolved until its ID has been
        1535 // fully resolved.
        1536 this.id_.then(fulfill, reject);
        1537};
        1538goog.inherits(webdriver.WebElement, webdriver.promise.Deferred);
        1539
        1540
        1541/**
        1542 * The property key used in the wire protocol to indicate that a JSON object
        1543 * contains the ID of a WebElement.
        1544 * @type {string}
        1545 * @const
        1546 */
        1547webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';
        1548
        1549
        1550/**
        1551 * Compares to WebElements for equality.
        1552 * @param {!webdriver.WebElement} a A WebElement.
        1553 * @param {!webdriver.WebElement} b A WebElement.
        1554 * @return {!webdriver.promise.Promise} A promise that will be resolved to
        1555 * whether the two WebElements are equal.
        1556 */
        1557webdriver.WebElement.equals = function(a, b) {
        1558 if (a == b) {
        1559 return webdriver.promise.fulfilled(true);
        1560 }
        1561 return webdriver.promise.fullyResolved([a.id_, b.id_]).then(function(ids) {
        1562 // If the two element's have the same ID, they should be considered
        1563 // equal. Otherwise, they may still be equivalent, but we'll need to
        1564 // ask the server to check for us.
        1565 if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==
        1566 ids[1][webdriver.WebElement.ELEMENT_KEY]) {
        1567 return true;
        1568 }
        1569
        1570 var command = new webdriver.Command(
        1571 webdriver.CommandName.ELEMENT_EQUALS);
        1572 command.setParameter('other', b);
        1573 return a.schedule_(command, 'webdriver.WebElement.equals()');
        1574 });
        1575};
        1576
        1577
        1578/**
        1579 * @return {!webdriver.WebDriver} The parent driver for this instance.
        1580 */
        1581webdriver.WebElement.prototype.getDriver = function() {
        1582 return this.driver_;
        1583};
        1584
        1585
        1586/**
        1587 * @return {!webdriver.promise.Promise} A promise that resolves to this
        1588 * element's JSON representation as defined by the WebDriver wire protocol.
        1589 * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol
        1590 */
        1591webdriver.WebElement.prototype.toWireValue = function() {
        1592 return this.id_;
        1593};
        1594
        1595
        1596/**
        1597 * Schedules a command that targets this element with the parent WebDriver
        1598 * instance. Will ensure this element's ID is included in the command parameters
        1599 * under the "id" key.
        1600 * @param {!webdriver.Command} command The command to schedule.
        1601 * @param {string} description A description of the command for debugging.
        1602 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1603 * the command result.
        1604 * @see webdriver.WebDriver.prototype.schedule
        1605 * @private
        1606 */
        1607webdriver.WebElement.prototype.schedule_ = function(command, description) {
        1608 command.setParameter('id', this.id_);
        1609 return this.driver_.schedule(command, description);
        1610};
        1611
        1612
        1613/**
        1614 * Schedule a command to find a descendant of this element. If the element
        1615 * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will
        1616 * be returned by the driver. Unlike other commands, this error cannot be
        1617 * suppressed. In other words, scheduling a command to find an element doubles
        1618 * as an assert that the element is present on the page. To test whether an
        1619 * element is present on the page, use {@code #isElementPresent} instead.
        1620 *
        1621 * <p>The search criteria for an element may be defined using one of the
        1622 * factories in the {@link webdriver.By} namespace, or as a short-hand
        1623 * {@link webdriver.By.Hash} object. For example, the following two statements
        1624 * are equivalent:
        1625 * <code><pre>
        1626 * var e1 = element.findElement(By.id('foo'));
        1627 * var e2 = element.findElement({id:'foo'});
        1628 * </pre></code>
        1629 *
        1630 * <p>You may also provide a custom locator function, which takes as input
        1631 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
        1632 * promise that will resolve to a WebElement. For example, to find the first
        1633 * visible link on a page, you could write:
        1634 * <code><pre>
        1635 * var link = element.findElement(firstVisibleLink);
        1636 *
        1637 * function firstVisibleLink(element) {
        1638 * var links = element.findElements(By.tagName('a'));
        1639 * return webdriver.promise.filter(links, function(link) {
        1640 * return links.isDisplayed();
        1641 * }).then(function(visibleLinks) {
        1642 * return visibleLinks[0];
        1643 * });
        1644 * }
        1645 * </pre></code>
        1646 *
        1647 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1648 * locator strategy to use when searching for the element.
        1649 * @return {!webdriver.WebElement} A WebElement that can be used to issue
        1650 * commands against the located element. If the element is not found, the
        1651 * element will be invalidated and all scheduled commands aborted.
        1652 */
        1653webdriver.WebElement.prototype.findElement = function(locator) {
        1654 locator = webdriver.Locator.checkLocator(locator);
        1655 var id;
        1656 if (goog.isFunction(locator)) {
        1657 id = this.driver_.findElementInternal_(locator, this);
        1658 } else {
        1659 var command = new webdriver.Command(
        1660 webdriver.CommandName.FIND_CHILD_ELEMENT).
        1661 setParameter('using', locator.using).
        1662 setParameter('value', locator.value);
        1663 id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');
        1664 }
        1665 return new webdriver.WebElement(this.driver_, id);
        1666};
        1667
        1668
        1669/**
        1670 * Schedules a command to test if there is at least one descendant of this
        1671 * element that matches the given search criteria.
        1672 *
        1673 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1674 * locator strategy to use when searching for the element.
        1675 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        1676 * resolved with whether an element could be located on the page.
        1677 */
        1678webdriver.WebElement.prototype.isElementPresent = function(locator) {
        1679 return this.findElements(locator).then(function(result) {
        1680 return !!result.length;
        1681 });
        1682};
        1683
        1684
        1685/**
        1686 * Schedules a command to find all of the descendants of this element that
        1687 * match the given search criteria.
        1688 *
        1689 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1690 * locator strategy to use when searching for the elements.
        1691 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        1692 * promise that will resolve to an array of WebElements.
        1693 */
        1694webdriver.WebElement.prototype.findElements = function(locator) {
        1695 locator = webdriver.Locator.checkLocator(locator);
        1696 if (goog.isFunction(locator)) {
        1697 return this.driver_.findElementsInternal_(locator, this);
        1698 } else {
        1699 var command = new webdriver.Command(
        1700 webdriver.CommandName.FIND_CHILD_ELEMENTS).
        1701 setParameter('using', locator.using).
        1702 setParameter('value', locator.value);
        1703 return this.schedule_(command, 'WebElement.findElements(' + locator + ')');
        1704 }
        1705};
        1706
        1707
        1708/**
        1709 * Schedules a command to click on this element.
        1710 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        1711 * the click command has completed.
        1712 */
        1713webdriver.WebElement.prototype.click = function() {
        1714 return this.schedule_(
        1715 new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),
        1716 'WebElement.click()');
        1717};
        1718
        1719
        1720/**
        1721 * Schedules a command to type a sequence on the DOM element represented by this
        1722 * instance.
        1723 * <p/>
        1724 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
        1725 * processed in the keysequence, that key state is toggled until one of the
        1726 * following occurs:
        1727 * <ul>
        1728 * <li>The modifier key is encountered again in the sequence. At this point the
        1729 * state of the key is toggled (along with the appropriate keyup/down events).
        1730 * </li>
        1731 * <li>The {@code webdriver.Key.NULL} key is encountered in the sequence. When
        1732 * this key is encountered, all modifier keys current in the down state are
        1733 * released (with accompanying keyup events). The NULL key can be used to
        1734 * simulate common keyboard shortcuts:
        1735 * <code><pre>
        1736 * element.sendKeys("text was",
        1737 * webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
        1738 * "now text is");
        1739 * // Alternatively:
        1740 * element.sendKeys("text was",
        1741 * webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
        1742 * "now text is");
        1743 * </pre></code></li>
        1744 * <li>The end of the keysequence is encountered. When there are no more keys
        1745 * to type, all depressed modifier keys are released (with accompanying keyup
        1746 * events).
        1747 * </li>
        1748 * </ul>
        1749 * <strong>Note:</strong> On browsers where native keyboard events are not yet
        1750 * supported (e.g. Firefox on OS X), key events will be synthesized. Special
        1751 * punctionation keys will be synthesized according to a standard QWERTY en-us
        1752 * keyboard layout.
        1753 *
        1754 * @param {...string} var_args The sequence of keys to
        1755 * type. All arguments will be joined into a single sequence (var_args is
        1756 * permitted for convenience).
        1757 * @return {!webdriver.promise.Promise} A promise that will be resolved when all
        1758 * keys have been typed.
        1759 */
        1760webdriver.WebElement.prototype.sendKeys = function(var_args) {
        1761 // Coerce every argument to a string. This protects us from users that
        1762 // ignore the jsdoc and give us a number (which ends up causing problems on
        1763 // the server, which requires strings).
        1764 var keys = webdriver.promise.fullyResolved(goog.array.slice(arguments, 0)).
        1765 then(function(args) {
        1766 return goog.array.map(goog.array.slice(args, 0), function(key) {
        1767 return key + '';
        1768 });
        1769 });
        1770 return this.schedule_(
        1771 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
        1772 setParameter('value', keys),
        1773 'WebElement.sendKeys(' + keys + ')');
        1774};
        1775
        1776
        1777/**
        1778 * Schedules a command to query for the tag/node name of this element.
        1779 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1780 * element's tag name.
        1781 */
        1782webdriver.WebElement.prototype.getTagName = function() {
        1783 return this.schedule_(
        1784 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),
        1785 'WebElement.getTagName()');
        1786};
        1787
        1788
        1789/**
        1790 * Schedules a command to query for the computed style of the element
        1791 * represented by this instance. If the element inherits the named style from
        1792 * its parent, the parent will be queried for its value. Where possible, color
        1793 * values will be converted to their hex representation (e.g. #00ff00 instead of
        1794 * rgb(0, 255, 0)).
        1795 * <p/>
        1796 * <em>Warning:</em> the value returned will be as the browser interprets it, so
        1797 * it may be tricky to form a proper assertion.
        1798 *
        1799 * @param {string} cssStyleProperty The name of the CSS style property to look
        1800 * up.
        1801 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1802 * requested CSS value.
        1803 */
        1804webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {
        1805 var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
        1806 return this.schedule_(
        1807 new webdriver.Command(name).
        1808 setParameter('propertyName', cssStyleProperty),
        1809 'WebElement.getCssValue(' + cssStyleProperty + ')');
        1810};
        1811
        1812
        1813/**
        1814 * Schedules a command to query for the value of the given attribute of the
        1815 * element. Will return the current value, even if it has been modified after
        1816 * the page has been loaded. More exactly, this method will return the value of
        1817 * the given attribute, unless that attribute is not present, in which case the
        1818 * value of the property with the same name is returned. If neither value is
        1819 * set, null is returned (for example, the "value" property of a textarea
        1820 * element). The "style" attribute is converted as best can be to a
        1821 * text representation with a trailing semi-colon. The following are deemed to
        1822 * be "boolean" attributes and will return either "true" or null:
        1823 *
        1824 * <p>async, autofocus, autoplay, checked, compact, complete, controls, declare,
        1825 * defaultchecked, defaultselected, defer, disabled, draggable, ended,
        1826 * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
        1827 * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
        1828 * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
        1829 * selected, spellcheck, truespeed, willvalidate
        1830 *
        1831 * <p>Finally, the following commonly mis-capitalized attribute/property names
        1832 * are evaluated as expected:
        1833 * <ul>
        1834 * <li>"class"
        1835 * <li>"readonly"
        1836 * </ul>
        1837 * @param {string} attributeName The name of the attribute to query.
        1838 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1839 * attribute's value. The returned value will always be either a string or
        1840 * null.
        1841 */
        1842webdriver.WebElement.prototype.getAttribute = function(attributeName) {
        1843 return this.schedule_(
        1844 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).
        1845 setParameter('name', attributeName),
        1846 'WebElement.getAttribute(' + attributeName + ')');
        1847};
        1848
        1849
        1850/**
        1851 * Get the visible (i.e. not hidden by CSS) innerText of this element, including
        1852 * sub-elements, without any leading or trailing whitespace.
        1853 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1854 * element's visible text.
        1855 */
        1856webdriver.WebElement.prototype.getText = function() {
        1857 return this.schedule_(
        1858 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),
        1859 'WebElement.getText()');
        1860};
        1861
        1862
        1863/**
        1864 * Schedules a command to compute the size of this element's bounding box, in
        1865 * pixels.
        1866 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1867 * element's size as a {@code {width:number, height:number}} object.
        1868 */
        1869webdriver.WebElement.prototype.getSize = function() {
        1870 return this.schedule_(
        1871 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),
        1872 'WebElement.getSize()');
        1873};
        1874
        1875
        1876/**
        1877 * Schedules a command to compute the location of this element in page space.
        1878 * @return {!webdriver.promise.Promise} A promise that will be resolved to the
        1879 * element's location as a {@code {x:number, y:number}} object.
        1880 */
        1881webdriver.WebElement.prototype.getLocation = function() {
        1882 return this.schedule_(
        1883 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),
        1884 'WebElement.getLocation()');
        1885};
        1886
        1887
        1888/**
        1889 * Schedules a command to query whether the DOM element represented by this
        1890 * instance is enabled, as dicted by the {@code disabled} attribute.
        1891 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1892 * whether this element is currently enabled.
        1893 */
        1894webdriver.WebElement.prototype.isEnabled = function() {
        1895 return this.schedule_(
        1896 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),
        1897 'WebElement.isEnabled()');
        1898};
        1899
        1900
        1901/**
        1902 * Schedules a command to query whether this element is selected.
        1903 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1904 * whether this element is currently selected.
        1905 */
        1906webdriver.WebElement.prototype.isSelected = function() {
        1907 return this.schedule_(
        1908 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),
        1909 'WebElement.isSelected()');
        1910};
        1911
        1912
        1913/**
        1914 * Schedules a command to submit the form containing this element (or this
        1915 * element if it is a FORM element). This command is a no-op if the element is
        1916 * not contained in a form.
        1917 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        1918 * the form has been submitted.
        1919 */
        1920webdriver.WebElement.prototype.submit = function() {
        1921 return this.schedule_(
        1922 new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),
        1923 'WebElement.submit()');
        1924};
        1925
        1926
        1927/**
        1928 * Schedules a command to clear the {@code value} of this element. This command
        1929 * has no effect if the underlying DOM element is neither a text INPUT element
        1930 * nor a TEXTAREA element.
        1931 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        1932 * the element has been cleared.
        1933 */
        1934webdriver.WebElement.prototype.clear = function() {
        1935 return this.schedule_(
        1936 new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),
        1937 'WebElement.clear()');
        1938};
        1939
        1940
        1941/**
        1942 * Schedules a command to test whether this element is currently displayed.
        1943 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1944 * whether this element is currently visible on the page.
        1945 */
        1946webdriver.WebElement.prototype.isDisplayed = function() {
        1947 return this.schedule_(
        1948 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),
        1949 'WebElement.isDisplayed()');
        1950};
        1951
        1952
        1953/**
        1954 * Schedules a command to retrieve the outer HTML of this element.
        1955 * @return {!webdriver.promise.Promise} A promise that will be resolved with
        1956 * the element's outer HTML.
        1957 */
        1958webdriver.WebElement.prototype.getOuterHtml = function() {
        1959 return this.driver_.executeScript(function() {
        1960 var element = arguments[0];
        1961 if ('outerHTML' in element) {
        1962 return element.outerHTML;
        1963 } else {
        1964 var div = element.ownerDocument.createElement('div');
        1965 div.appendChild(element.cloneNode(true));
        1966 return div.innerHTML;
        1967 }
        1968 }, this);
        1969};
        1970
        1971
        1972/**
        1973 * Schedules a command to retrieve the inner HTML of this element.
        1974 * @return {!webdriver.promise.Promise} A promise that will be resolved with the
        1975 * element's inner HTML.
        1976 */
        1977webdriver.WebElement.prototype.getInnerHtml = function() {
        1978 return this.driver_.executeScript('return arguments[0].innerHTML', this);
        1979};
        1980
        1981
        1982
        1983/**
        1984 * Represents a modal dialog such as {@code alert}, {@code confirm}, or
        1985 * {@code prompt}. Provides functions to retrieve the message displayed with
        1986 * the alert, accept or dismiss the alert, and set the response text (in the
        1987 * case of {@code prompt}).
        1988 * @param {!webdriver.WebDriver} driver The driver controlling the browser this
        1989 * alert is attached to.
        1990 * @param {!(string|webdriver.promise.Promise)} text Either the message text
        1991 * displayed with this alert, or a promise that will be resolved to said
        1992 * text.
        1993 * @constructor
        1994 * @extends {webdriver.promise.Deferred}
        1995 */
        1996webdriver.Alert = function(driver, text) {
        1997 goog.base(this, null, driver.controlFlow());
        1998
        1999 /** @private {!webdriver.WebDriver} */
        2000 this.driver_ = driver;
        2001
        2002 // This class is responsible for resolving itself; delete the resolve and
        2003 // reject methods so they may not be accessed by consumers of this class.
        2004 var fulfill = goog.partial(this.fulfill, this);
        2005 var reject = this.reject;
        2006 delete this.promise;
        2007 delete this.fulfill;
        2008 delete this.reject;
        2009
        2010 /** @private {!webdriver.promise.Promise} */
        2011 this.text_ = webdriver.promise.when(text);
        2012
        2013 // Make sure this instance is resolved when its displayed text is.
        2014 this.text_.then(fulfill, reject);
        2015};
        2016goog.inherits(webdriver.Alert, webdriver.promise.Deferred);
        2017
        2018
        2019/**
        2020 * Retrieves the message text displayed with this alert. For instance, if the
        2021 * alert were opened with alert("hello"), then this would return "hello".
        2022 * @return {!webdriver.promise.Promise} A promise that will be resolved to the
        2023 * text displayed with this alert.
        2024 */
        2025webdriver.Alert.prototype.getText = function() {
        2026 return this.text_;
        2027};
        2028
        2029
        2030/**
        2031 * Accepts this alert.
        2032 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        2033 * this command has completed.
        2034 */
        2035webdriver.Alert.prototype.accept = function() {
        2036 return this.driver_.schedule(
        2037 new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),
        2038 'WebDriver.switchTo().alert().accept()');
        2039};
        2040
        2041
        2042/**
        2043 * Dismisses this alert.
        2044 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        2045 * this command has completed.
        2046 */
        2047webdriver.Alert.prototype.dismiss = function() {
        2048 return this.driver_.schedule(
        2049 new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),
        2050 'WebDriver.switchTo().alert().dismiss()');
        2051};
        2052
        2053
        2054/**
        2055 * Sets the response text on this alert. This command will return an error if
        2056 * the underlying alert does not support response text (e.g. window.alert and
        2057 * window.confirm).
        2058 * @param {string} text The text to set.
        2059 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        2060 * this command has completed.
        2061 */
        2062webdriver.Alert.prototype.sendKeys = function(text) {
        2063 return this.driver_.schedule(
        2064 new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).
        2065 setParameter('text', text),
        2066 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
        2067};
        2068
        2069
        2070
        2071/**
        2072 * An error returned to indicate that there is an unhandled modal dialog on the
        2073 * current page.
        2074 * @param {string} message The error message.
        2075 * @param {!webdriver.Alert} alert The alert handle.
        2076 * @constructor
        2077 * @extends {bot.Error}
        2078 */
        2079webdriver.UnhandledAlertError = function(message, alert) {
        2080 goog.base(this, bot.ErrorCode.MODAL_DIALOG_OPENED, message);
        2081
        2082 /** @private {!webdriver.Alert} */
        2083 this.alert_ = alert;
        2084};
        2085goog.inherits(webdriver.UnhandledAlertError, bot.Error);
        2086
        2087
        2088/**
        2089 * @return {!webdriver.Alert} The open alert.
        2090 */
        2091webdriver.UnhandledAlertError.prototype.getAlert = function() {
        2092 return this.alert_;
        2093};
        \ No newline at end of file +webdriver.js

        lib/webdriver/webdriver.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview The heart of the WebDriver JavaScript API.
        20 */
        21
        22goog.provide('webdriver.Alert');
        23goog.provide('webdriver.AlertPromise');
        24goog.provide('webdriver.FileDetector');
        25goog.provide('webdriver.UnhandledAlertError');
        26goog.provide('webdriver.WebDriver');
        27goog.provide('webdriver.WebElement');
        28goog.provide('webdriver.WebElementPromise');
        29
        30goog.require('bot.Error');
        31goog.require('bot.ErrorCode');
        32goog.require('bot.response');
        33goog.require('goog.array');
        34goog.require('goog.object');
        35goog.require('webdriver.ActionSequence');
        36goog.require('webdriver.Command');
        37goog.require('webdriver.CommandName');
        38goog.require('webdriver.Key');
        39goog.require('webdriver.Locator');
        40goog.require('webdriver.Serializable');
        41goog.require('webdriver.Session');
        42goog.require('webdriver.TouchSequence');
        43goog.require('webdriver.logging');
        44goog.require('webdriver.promise');
        45goog.require('webdriver.until');
        46
        47
        48//////////////////////////////////////////////////////////////////////////////
        49//
        50// webdriver.WebDriver
        51//
        52//////////////////////////////////////////////////////////////////////////////
        53
        54
        55
        56/**
        57 * Creates a new WebDriver client, which provides control over a browser.
        58 *
        59 * Every WebDriver command returns a {@code webdriver.promise.Promise} that
        60 * represents the result of that command. Callbacks may be registered on this
        61 * object to manipulate the command result or catch an expected error. Any
        62 * commands scheduled with a callback are considered sub-commands and will
        63 * execute before the next command in the current frame. For example:
        64 *
        65 * var message = [];
        66 * driver.call(message.push, message, 'a').then(function() {
        67 * driver.call(message.push, message, 'b');
        68 * });
        69 * driver.call(message.push, message, 'c');
        70 * driver.call(function() {
        71 * alert('message is abc? ' + (message.join('') == 'abc'));
        72 * });
        73 *
        74 * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a
        75 * known session or a promise that will be resolved to a session.
        76 * @param {!webdriver.CommandExecutor} executor The executor to use when
        77 * sending commands to the browser.
        78 * @param {webdriver.promise.ControlFlow=} opt_flow The flow to
        79 * schedule commands through. Defaults to the active flow object.
        80 * @constructor
        81 */
        82webdriver.WebDriver = function(session, executor, opt_flow) {
        83
        84 /** @private {!(webdriver.Session|webdriver.promise.Promise)} */
        85 this.session_ = session;
        86
        87 /** @private {!webdriver.CommandExecutor} */
        88 this.executor_ = executor;
        89
        90 /** @private {!webdriver.promise.ControlFlow} */
        91 this.flow_ = opt_flow || webdriver.promise.controlFlow();
        92
        93 /** @private {webdriver.FileDetector} */
        94 this.fileDetector_ = null;
        95};
        96
        97
        98/**
        99 * Creates a new WebDriver client for an existing session.
        100 * @param {!webdriver.CommandExecutor} executor Command executor to use when
        101 * querying for session details.
        102 * @param {string} sessionId ID of the session to attach to.
        103 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
        104 * commands should execute under. Defaults to the
        105 * {@link webdriver.promise.controlFlow() currently active} control flow.
        106 * @return {!webdriver.WebDriver} A new client for the specified session.
        107 */
        108webdriver.WebDriver.attachToSession = function(executor, sessionId, opt_flow) {
        109 return webdriver.WebDriver.acquireSession_(executor,
        110 new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION).
        111 setParameter('sessionId', sessionId),
        112 'WebDriver.attachToSession()',
        113 opt_flow);
        114};
        115
        116
        117/**
        118 * Creates a new WebDriver session.
        119 * @param {!webdriver.CommandExecutor} executor The executor to create the new
        120 * session with.
        121 * @param {!webdriver.Capabilities} desiredCapabilities The desired
        122 * capabilities for the new session.
        123 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
        124 * commands should execute under, including the initial session creation.
        125 * Defaults to the {@link webdriver.promise.controlFlow() currently active}
        126 * control flow.
        127 * @return {!webdriver.WebDriver} The driver for the newly created session.
        128 */
        129webdriver.WebDriver.createSession = function(
        130 executor, desiredCapabilities, opt_flow) {
        131 return webdriver.WebDriver.acquireSession_(executor,
        132 new webdriver.Command(webdriver.CommandName.NEW_SESSION).
        133 setParameter('desiredCapabilities', desiredCapabilities),
        134 'WebDriver.createSession()',
        135 opt_flow);
        136};
        137
        138
        139/**
        140 * Sends a command to the server that is expected to return the details for a
        141 * {@link webdriver.Session}. This may either be an existing session, or a
        142 * newly created one.
        143 * @param {!webdriver.CommandExecutor} executor Command executor to use when
        144 * querying for session details.
        145 * @param {!webdriver.Command} command The command to send to fetch the session
        146 * details.
        147 * @param {string} description A descriptive debug label for this action.
        148 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver
        149 * commands should execute under. Defaults to the
        150 * {@link webdriver.promise.controlFlow() currently active} control flow.
        151 * @return {!webdriver.WebDriver} A new WebDriver client for the session.
        152 * @private
        153 */
        154webdriver.WebDriver.acquireSession_ = function(
        155 executor, command, description, opt_flow) {
        156 var flow = opt_flow || webdriver.promise.controlFlow();
        157 var session = flow.execute(function() {
        158 return webdriver.WebDriver.executeCommand_(executor, command).
        159 then(function(response) {
        160 bot.response.checkResponse(response);
        161 return new webdriver.Session(response['sessionId'],
        162 response['value']);
        163 });
        164 }, description);
        165 return new webdriver.WebDriver(session, executor, flow);
        166};
        167
        168
        169/**
        170 * Converts an object to its JSON representation in the WebDriver wire protocol.
        171 * When converting values of type object, the following steps will be taken:
        172 * <ol>
        173 * <li>if the object is a WebElement, the return value will be the element's
        174 * server ID
        175 * <li>if the object is a Serializable, its
        176 * {@link webdriver.Serializable#serialize} function will be invoked and
        177 * this algorithm will recursively be applied to the result
        178 * <li>if the object provides a "toJSON" function, this algorithm will
        179 * recursively be applied to the result of that function
        180 * <li>otherwise, the value of each key will be recursively converted according
        181 * to the rules above.
        182 * </ol>
        183 *
        184 * @param {*} obj The object to convert.
        185 * @return {!webdriver.promise.Promise.<?>} A promise that will resolve to the
        186 * input value's JSON representation.
        187 * @private
        188 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
        189 */
        190webdriver.WebDriver.toWireValue_ = function(obj) {
        191 if (webdriver.promise.isPromise(obj)) {
        192 return obj.then(webdriver.WebDriver.toWireValue_);
        193 }
        194 return webdriver.promise.fulfilled(convertValue(obj));
        195
        196 function convertValue(value) {
        197 switch (goog.typeOf(value)) {
        198 case 'array':
        199 return convertKeys(value, true);
        200 case 'object':
        201 // NB: WebElement is a Serializable, but we know its serialized form
        202 // is a promise for its wire format. This is a micro optimization to
        203 // avoid creating extra promises by recursing on the promised id.
        204 if (value instanceof webdriver.WebElement) {
        205 return value.getId();
        206 }
        207 if (value instanceof webdriver.Serializable) {
        208 return webdriver.WebDriver.toWireValue_(value.serialize());
        209 }
        210 if (goog.isFunction(value.toJSON)) {
        211 return webdriver.WebDriver.toWireValue_(value.toJSON());
        212 }
        213 if (goog.isNumber(value.nodeType) && goog.isString(value.nodeName)) {
        214 throw new TypeError(
        215 'Invalid argument type: ' + value.nodeName +
        216 '(' + value.nodeType + ')');
        217 }
        218 return convertKeys(value, false);
        219 case 'function':
        220 return '' + value;
        221 case 'undefined':
        222 return null;
        223 default:
        224 return value;
        225 }
        226 }
        227
        228 function convertKeys(obj, isArray) {
        229 var numKeys = isArray ? obj.length : goog.object.getCount(obj);
        230 var ret = isArray ? new Array(numKeys) : {};
        231 if (!numKeys) {
        232 return webdriver.promise.fulfilled(ret);
        233 }
        234
        235 var numResolved = 0;
        236 var done = webdriver.promise.defer();
        237
        238 // forEach will stop iteration at undefined, where we want to convert
        239 // these to null and keep iterating.
        240 var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) {
        241 var n = arr.length;
        242 for (var i = 0; i < n; i++) {
        243 fn(arr[i], i);
        244 }
        245 };
        246
        247 forEachKey(obj, function(value, key) {
        248 if (webdriver.promise.isPromise(value)) {
        249 value.then(webdriver.WebDriver.toWireValue_).
        250 then(setValue, done.reject);
        251 } else {
        252 webdriver.promise.asap(convertValue(value), setValue, done.reject);
        253 }
        254
        255 function setValue(value) {
        256 ret[key] = value;
        257 maybeFulfill();
        258 }
        259 });
        260
        261 return done.promise;
        262
        263 function maybeFulfill() {
        264 if (++numResolved === numKeys) {
        265 done.fulfill(ret);
        266 }
        267 }
        268 }
        269};
        270
        271
        272/**
        273 * Converts a value from its JSON representation according to the WebDriver wire
        274 * protocol. Any JSON object containing a
        275 * {@code webdriver.WebElement.ELEMENT_KEY} key will be decoded to a
        276 * {@code webdriver.WebElement} object. All other values will be passed through
        277 * as is.
        278 * @param {!webdriver.WebDriver} driver The driver instance to use as the
        279 * parent of any unwrapped {@code webdriver.WebElement} values.
        280 * @param {*} value The value to convert.
        281 * @return {*} The converted value.
        282 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
        283 * @private
        284 */
        285webdriver.WebDriver.fromWireValue_ = function(driver, value) {
        286 if (goog.isArray(value)) {
        287 value = goog.array.map(/**@type {goog.array.ArrayLike}*/ (value),
        288 goog.partial(webdriver.WebDriver.fromWireValue_, driver));
        289 } else if (value && goog.isObject(value) && !goog.isFunction(value)) {
        290 if (webdriver.WebElement.ELEMENT_KEY in value) {
        291 value = new webdriver.WebElement(driver, value);
        292 } else {
        293 value = goog.object.map(/**@type {!Object}*/ (value),
        294 goog.partial(webdriver.WebDriver.fromWireValue_, driver));
        295 }
        296 }
        297 return value;
        298};
        299
        300
        301/**
        302 * Translates a command to its wire-protocol representation before passing it
        303 * to the given {@code executor} for execution.
        304 * @param {!webdriver.CommandExecutor} executor The executor to use.
        305 * @param {!webdriver.Command} command The command to execute.
        306 * @return {!webdriver.promise.Promise} A promise that will resolve with the
        307 * command response.
        308 * @private
        309 */
        310webdriver.WebDriver.executeCommand_ = function(executor, command) {
        311 return webdriver.WebDriver.toWireValue_(command.getParameters()).
        312 then(function(parameters) {
        313 command.setParameters(parameters);
        314 return webdriver.promise.checkedNodeCall(
        315 goog.bind(executor.execute, executor, command));
        316 });
        317};
        318
        319
        320/**
        321 * @return {!webdriver.promise.ControlFlow} The control flow used by this
        322 * instance.
        323 */
        324webdriver.WebDriver.prototype.controlFlow = function() {
        325 return this.flow_;
        326};
        327
        328
        329/**
        330 * Schedules a {@code webdriver.Command} to be executed by this driver's
        331 * {@code webdriver.CommandExecutor}.
        332 * @param {!webdriver.Command} command The command to schedule.
        333 * @param {string} description A description of the command for debugging.
        334 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
        335 * with the command result.
        336 * @template T
        337 */
        338webdriver.WebDriver.prototype.schedule = function(command, description) {
        339 var self = this;
        340
        341 checkHasNotQuit();
        342 command.setParameter('sessionId', this.session_);
        343
        344 // If any of the command parameters are rejected promises, those
        345 // rejections may be reported as unhandled before the control flow
        346 // attempts to execute the command. To ensure parameters errors
        347 // propagate through the command itself, we resolve all of the
        348 // command parameters now, but suppress any errors until the ControlFlow
        349 // actually executes the command. This addresses scenarios like catching
        350 // an element not found error in:
        351 //
        352 // driver.findElement(By.id('foo')).click().thenCatch(function(e) {
        353 // if (e.code === bot.ErrorCode.NO_SUCH_ELEMENT) {
        354 // // Do something.
        355 // }
        356 // });
        357 var prepCommand = webdriver.WebDriver.toWireValue_(command.getParameters());
        358 prepCommand.thenCatch(goog.nullFunction);
        359
        360 var flow = this.flow_;
        361 var executor = this.executor_;
        362 return flow.execute(function() {
        363 // A call to WebDriver.quit() may have been scheduled in the same event
        364 // loop as this |command|, which would prevent us from detecting that the
        365 // driver has quit above. Therefore, we need to make another quick check.
        366 // We still check above so we can fail as early as possible.
        367 checkHasNotQuit();
        368
        369 // Retrieve resolved command parameters; any previously suppressed errors
        370 // will now propagate up through the control flow as part of the command
        371 // execution.
        372 return prepCommand.then(function(parameters) {
        373 command.setParameters(parameters);
        374 return webdriver.promise.checkedNodeCall(
        375 goog.bind(executor.execute, executor, command));
        376 });
        377 }, description).then(function(response) {
        378 try {
        379 bot.response.checkResponse(response);
        380 } catch (ex) {
        381 var value = response['value'];
        382 if (ex.code === bot.ErrorCode.UNEXPECTED_ALERT_OPEN) {
        383 var text = value && value['alert'] ? value['alert']['text'] : '';
        384 throw new webdriver.UnhandledAlertError(ex.message, text,
        385 new webdriver.Alert(self, text));
        386 }
        387 throw ex;
        388 }
        389 return webdriver.WebDriver.fromWireValue_(self, response['value']);
        390 });
        391
        392 function checkHasNotQuit() {
        393 if (!self.session_) {
        394 throw new Error('This driver instance does not have a valid session ID ' +
        395 '(did you call WebDriver.quit()?) and may no longer be ' +
        396 'used.');
        397 }
        398 }
        399};
        400
        401
        402/**
        403 * Sets the {@linkplain webdriver.FileDetector file detector} that should be
        404 * used with this instance.
        405 * @param {webdriver.FileDetector} detector The detector to use or {@code null}.
        406 */
        407webdriver.WebDriver.prototype.setFileDetector = function(detector) {
        408 this.fileDetector_ = detector;
        409};
        410
        411
        412// ----------------------------------------------------------------------------
        413// Client command functions:
        414// ----------------------------------------------------------------------------
        415
        416
        417/**
        418 * @return {!webdriver.promise.Promise.<!webdriver.Session>} A promise for this
        419 * client's session.
        420 */
        421webdriver.WebDriver.prototype.getSession = function() {
        422 return webdriver.promise.when(this.session_);
        423};
        424
        425
        426/**
        427 * @return {!webdriver.promise.Promise.<!webdriver.Capabilities>} A promise
        428 * that will resolve with the this instance's capabilities.
        429 */
        430webdriver.WebDriver.prototype.getCapabilities = function() {
        431 return webdriver.promise.when(this.session_, function(session) {
        432 return session.getCapabilities();
        433 });
        434};
        435
        436
        437/**
        438 * Schedules a command to quit the current session. After calling quit, this
        439 * instance will be invalidated and may no longer be used to issue commands
        440 * against the browser.
        441 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        442 * when the command has completed.
        443 */
        444webdriver.WebDriver.prototype.quit = function() {
        445 var result = this.schedule(
        446 new webdriver.Command(webdriver.CommandName.QUIT),
        447 'WebDriver.quit()');
        448 // Delete our session ID when the quit command finishes; this will allow us to
        449 // throw an error when attemnpting to use a driver post-quit.
        450 return result.thenFinally(goog.bind(function() {
        451 delete this.session_;
        452 }, this));
        453};
        454
        455
        456/**
        457 * Creates a new action sequence using this driver. The sequence will not be
        458 * scheduled for execution until {@link webdriver.ActionSequence#perform} is
        459 * called. Example:
        460 *
        461 * driver.actions().
        462 * mouseDown(element1).
        463 * mouseMove(element2).
        464 * mouseUp().
        465 * perform();
        466 *
        467 * @return {!webdriver.ActionSequence} A new action sequence for this instance.
        468 */
        469webdriver.WebDriver.prototype.actions = function() {
        470 return new webdriver.ActionSequence(this);
        471};
        472
        473
        474/**
        475 * Creates a new touch sequence using this driver. The sequence will not be
        476 * scheduled for execution until {@link webdriver.TouchSequence#perform} is
        477 * called. Example:
        478 *
        479 * driver.touchActions().
        480 * tap(element1).
        481 * doubleTap(element2).
        482 * perform();
        483 *
        484 * @return {!webdriver.TouchSequence} A new touch sequence for this instance.
        485 */
        486webdriver.WebDriver.prototype.touchActions = function() {
        487 return new webdriver.TouchSequence(this);
        488};
        489
        490
        491/**
        492 * Schedules a command to execute JavaScript in the context of the currently
        493 * selected frame or window. The script fragment will be executed as the body
        494 * of an anonymous function. If the script is provided as a function object,
        495 * that function will be converted to a string for injection into the target
        496 * window.
        497 *
        498 * Any arguments provided in addition to the script will be included as script
        499 * arguments and may be referenced using the {@code arguments} object.
        500 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
        501 * Arrays and objects may also be used as script arguments as long as each item
        502 * adheres to the types previously mentioned.
        503 *
        504 * The script may refer to any variables accessible from the current window.
        505 * Furthermore, the script will execute in the window's context, thus
        506 * {@code document} may be used to refer to the current document. Any local
        507 * variables will not be available once the script has finished executing,
        508 * though global variables will persist.
        509 *
        510 * If the script has a return value (i.e. if the script contains a return
        511 * statement), then the following steps will be taken for resolving this
        512 * functions return value:
        513 *
        514 * - For a HTML element, the value will resolve to a
        515 * {@link webdriver.WebElement}
        516 * - Null and undefined return values will resolve to null</li>
        517 * - Booleans, numbers, and strings will resolve as is</li>
        518 * - Functions will resolve to their string representation</li>
        519 * - For arrays and objects, each member item will be converted according to
        520 * the rules above
        521 *
        522 * @param {!(string|Function)} script The script to execute.
        523 * @param {...*} var_args The arguments to pass to the script.
        524 * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
        525 * scripts return value.
        526 * @template T
        527 */
        528webdriver.WebDriver.prototype.executeScript = function(script, var_args) {
        529 if (goog.isFunction(script)) {
        530 script = 'return (' + script + ').apply(null, arguments);';
        531 }
        532 var args = arguments.length > 1 ? goog.array.slice(arguments, 1) : [];
        533 return this.schedule(
        534 new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT).
        535 setParameter('script', script).
        536 setParameter('args', args),
        537 'WebDriver.executeScript()');
        538};
        539
        540
        541/**
        542 * Schedules a command to execute asynchronous JavaScript in the context of the
        543 * currently selected frame or window. The script fragment will be executed as
        544 * the body of an anonymous function. If the script is provided as a function
        545 * object, that function will be converted to a string for injection into the
        546 * target window.
        547 *
        548 * Any arguments provided in addition to the script will be included as script
        549 * arguments and may be referenced using the {@code arguments} object.
        550 * Arguments may be a boolean, number, string, or {@code webdriver.WebElement}.
        551 * Arrays and objects may also be used as script arguments as long as each item
        552 * adheres to the types previously mentioned.
        553 *
        554 * Unlike executing synchronous JavaScript with {@link #executeScript},
        555 * scripts executed with this function must explicitly signal they are finished
        556 * by invoking the provided callback. This callback will always be injected
        557 * into the executed function as the last argument, and thus may be referenced
        558 * with {@code arguments[arguments.length - 1]}. The following steps will be
        559 * taken for resolving this functions return value against the first argument
        560 * to the script's callback function:
        561 *
        562 * - For a HTML element, the value will resolve to a
        563 * {@link webdriver.WebElement}
        564 * - Null and undefined return values will resolve to null
        565 * - Booleans, numbers, and strings will resolve as is
        566 * - Functions will resolve to their string representation
        567 * - For arrays and objects, each member item will be converted according to
        568 * the rules above
        569 *
        570 * __Example #1:__ Performing a sleep that is synchronized with the currently
        571 * selected window:
        572 *
        573 * var start = new Date().getTime();
        574 * driver.executeAsyncScript(
        575 * 'window.setTimeout(arguments[arguments.length - 1], 500);').
        576 * then(function() {
        577 * console.log(
        578 * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        579 * });
        580 *
        581 * __Example #2:__ Synchronizing a test with an AJAX application:
        582 *
        583 * var button = driver.findElement(By.id('compose-button'));
        584 * button.click();
        585 * driver.executeAsyncScript(
        586 * 'var callback = arguments[arguments.length - 1];' +
        587 * 'mailClient.getComposeWindowWidget().onload(callback);');
        588 * driver.switchTo().frame('composeWidget');
        589 * driver.findElement(By.id('to')).sendKeys('dog@example.com');
        590 *
        591 * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In
        592 * this example, the inject script is specified with a function literal. When
        593 * using this format, the function is converted to a string for injection, so it
        594 * should not reference any symbols not defined in the scope of the page under
        595 * test.
        596 *
        597 * driver.executeAsyncScript(function() {
        598 * var callback = arguments[arguments.length - 1];
        599 * var xhr = new XMLHttpRequest();
        600 * xhr.open("GET", "/resource/data.json", true);
        601 * xhr.onreadystatechange = function() {
        602 * if (xhr.readyState == 4) {
        603 * callback(xhr.responseText);
        604 * }
        605 * }
        606 * xhr.send('');
        607 * }).then(function(str) {
        608 * console.log(JSON.parse(str)['food']);
        609 * });
        610 *
        611 * @param {!(string|Function)} script The script to execute.
        612 * @param {...*} var_args The arguments to pass to the script.
        613 * @return {!webdriver.promise.Promise.<T>} A promise that will resolve to the
        614 * scripts return value.
        615 * @template T
        616 */
        617webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) {
        618 if (goog.isFunction(script)) {
        619 script = 'return (' + script + ').apply(null, arguments);';
        620 }
        621 return this.schedule(
        622 new webdriver.Command(webdriver.CommandName.EXECUTE_ASYNC_SCRIPT).
        623 setParameter('script', script).
        624 setParameter('args', goog.array.slice(arguments, 1)),
        625 'WebDriver.executeScript()');
        626};
        627
        628
        629/**
        630 * Schedules a command to execute a custom function.
        631 * @param {function(...): (T|webdriver.promise.Promise.<T>)} fn The function to
        632 * execute.
        633 * @param {Object=} opt_scope The object in whose scope to execute the function.
        634 * @param {...*} var_args Any arguments to pass to the function.
        635 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved'
        636 * with the function's result.
        637 * @template T
        638 */
        639webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) {
        640 var args = goog.array.slice(arguments, 2);
        641 var flow = this.flow_;
        642 return flow.execute(function() {
        643 return webdriver.promise.fullyResolved(args).then(function(args) {
        644 if (webdriver.promise.isGenerator(fn)) {
        645 args.unshift(fn, opt_scope);
        646 return webdriver.promise.consume.apply(null, args);
        647 }
        648 return fn.apply(opt_scope, args);
        649 });
        650 }, 'WebDriver.call(' + (fn.name || 'function') + ')');
        651};
        652
        653
        654/**
        655 * Schedules a command to wait for a condition to hold. The condition may be
        656 * specified by a {@link webdriver.until.Condition}, as a custom function, or
        657 * as a {@link webdriver.promise.Promise}.
        658 *
        659 * For a {@link webdriver.until.Condition} or function, the wait will repeatedly
        660 * evaluate the condition until it returns a truthy value. If any errors occur
        661 * while evaluating the condition, they will be allowed to propagate. In the
        662 * event a condition returns a {@link webdriver.promise.Promise promise}, the
        663 * polling loop will wait for it to be resolved and use the resolved value for
        664 * whether the condition has been satisified. Note the resolution time for
        665 * a promise is factored into whether a wait has timed out.
        666 *
        667 * *Example:* waiting up to 10 seconds for an element to be present and visible
        668 * on the page.
        669 *
        670 * var button = driver.wait(until.elementLocated(By.id('foo')), 10000);
        671 * button.click();
        672 *
        673 * This function may also be used to block the command flow on the resolution
        674 * of a {@link webdriver.promise.Promise promise}. When given a promise, the
        675 * command will simply wait for its resolution before completing. A timeout may
        676 * be provided to fail the command if the promise does not resolve before the
        677 * timeout expires.
        678 *
        679 * *Example:* Suppose you have a function, `startTestServer`, that returns a
        680 * promise for when a server is ready for requests. You can block a `WebDriver`
        681 * client on this promise with:
        682 *
        683 * var started = startTestServer();
        684 * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds');
        685 * driver.get(getServerUrl());
        686 *
        687 * @param {!(webdriver.promise.Promise<T>|
        688 * webdriver.until.Condition<T>|
        689 * function(!webdriver.WebDriver): T)} condition The condition to
        690 * wait on, defined as a promise, condition object, or a function to
        691 * evaluate as a condition.
        692 * @param {number=} opt_timeout How long to wait for the condition to be true.
        693 * @param {string=} opt_message An optional message to use if the wait times
        694 * out.
        695 * @return {!webdriver.promise.Promise<T>} A promise that will be fulfilled
        696 * with the first truthy value returned by the condition function, or
        697 * rejected if the condition times out.
        698 * @template T
        699 */
        700webdriver.WebDriver.prototype.wait = function(
        701 condition, opt_timeout, opt_message) {
        702 if (webdriver.promise.isPromise(condition)) {
        703 return this.flow_.wait(
        704 /** @type {!webdriver.promise.Promise} */(condition),
        705 opt_timeout, opt_message);
        706 }
        707
        708 var message = opt_message;
        709 var fn = /** @type {!Function} */(condition);
        710 if (condition instanceof webdriver.until.Condition) {
        711 message = message || condition.description();
        712 fn = condition.fn;
        713 }
        714
        715 var driver = this;
        716 return this.flow_.wait(function() {
        717 if (webdriver.promise.isGenerator(fn)) {
        718 return webdriver.promise.consume(fn, null, [driver]);
        719 }
        720 return fn(driver);
        721 }, opt_timeout, message);
        722};
        723
        724
        725/**
        726 * Schedules a command to make the driver sleep for the given amount of time.
        727 * @param {number} ms The amount of time, in milliseconds, to sleep.
        728 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        729 * when the sleep has finished.
        730 */
        731webdriver.WebDriver.prototype.sleep = function(ms) {
        732 return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')');
        733};
        734
        735
        736/**
        737 * Schedules a command to retrieve they current window handle.
        738 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        739 * resolved with the current window handle.
        740 */
        741webdriver.WebDriver.prototype.getWindowHandle = function() {
        742 return this.schedule(
        743 new webdriver.Command(webdriver.CommandName.GET_CURRENT_WINDOW_HANDLE),
        744 'WebDriver.getWindowHandle()');
        745};
        746
        747
        748/**
        749 * Schedules a command to retrieve the current list of available window handles.
        750 * @return {!webdriver.promise.Promise.<!Array.<string>>} A promise that will
        751 * be resolved with an array of window handles.
        752 */
        753webdriver.WebDriver.prototype.getAllWindowHandles = function() {
        754 return this.schedule(
        755 new webdriver.Command(webdriver.CommandName.GET_WINDOW_HANDLES),
        756 'WebDriver.getAllWindowHandles()');
        757};
        758
        759
        760/**
        761 * Schedules a command to retrieve the current page's source. The page source
        762 * returned is a representation of the underlying DOM: do not expect it to be
        763 * formatted or escaped in the same way as the response sent from the web
        764 * server.
        765 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        766 * resolved with the current page source.
        767 */
        768webdriver.WebDriver.prototype.getPageSource = function() {
        769 return this.schedule(
        770 new webdriver.Command(webdriver.CommandName.GET_PAGE_SOURCE),
        771 'WebDriver.getAllWindowHandles()');
        772};
        773
        774
        775/**
        776 * Schedules a command to close the current window.
        777 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        778 * when this command has completed.
        779 */
        780webdriver.WebDriver.prototype.close = function() {
        781 return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE),
        782 'WebDriver.close()');
        783};
        784
        785
        786/**
        787 * Schedules a command to navigate to the given URL.
        788 * @param {string} url The fully qualified URL to open.
        789 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        790 * when the document has finished loading.
        791 */
        792webdriver.WebDriver.prototype.get = function(url) {
        793 return this.navigate().to(url);
        794};
        795
        796
        797/**
        798 * Schedules a command to retrieve the URL of the current page.
        799 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        800 * resolved with the current URL.
        801 */
        802webdriver.WebDriver.prototype.getCurrentUrl = function() {
        803 return this.schedule(
        804 new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL),
        805 'WebDriver.getCurrentUrl()');
        806};
        807
        808
        809/**
        810 * Schedules a command to retrieve the current page's title.
        811 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        812 * resolved with the current page's title.
        813 */
        814webdriver.WebDriver.prototype.getTitle = function() {
        815 return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE),
        816 'WebDriver.getTitle()');
        817};
        818
        819
        820/**
        821 * Schedule a command to find an element on the page. If the element cannot be
        822 * found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will be returned
        823 * by the driver. Unlike other commands, this error cannot be suppressed. In
        824 * other words, scheduling a command to find an element doubles as an assert
        825 * that the element is present on the page. To test whether an element is
        826 * present on the page, use {@link #isElementPresent} instead.
        827 *
        828 * The search criteria for an element may be defined using one of the
        829 * factories in the {@link webdriver.By} namespace, or as a short-hand
        830 * {@link webdriver.By.Hash} object. For example, the following two statements
        831 * are equivalent:
        832 *
        833 * var e1 = driver.findElement(By.id('foo'));
        834 * var e2 = driver.findElement({id:'foo'});
        835 *
        836 * You may also provide a custom locator function, which takes as input
        837 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
        838 * promise that will resolve to a WebElement. For example, to find the first
        839 * visible link on a page, you could write:
        840 *
        841 * var link = driver.findElement(firstVisibleLink);
        842 *
        843 * function firstVisibleLink(driver) {
        844 * var links = driver.findElements(By.tagName('a'));
        845 * return webdriver.promise.filter(links, function(link) {
        846 * return links.isDisplayed();
        847 * }).then(function(visibleLinks) {
        848 * return visibleLinks[0];
        849 * });
        850 * }
        851 *
        852 * When running in the browser, a WebDriver cannot manipulate DOM elements
        853 * directly; it may do so only through a {@link webdriver.WebElement} reference.
        854 * This function may be used to generate a WebElement from a DOM element. A
        855 * reference to the DOM element will be stored in a known location and this
        856 * driver will attempt to retrieve it through {@link #executeScript}. If the
        857 * element cannot be found (eg, it belongs to a different document than the
        858 * one this instance is currently focused on), a
        859 * {@link bot.ErrorCode.NO_SUCH_ELEMENT} error will be returned.
        860 *
        861 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|Function)} locator The
        862 * locator to use.
        863 * @return {!webdriver.WebElement} A WebElement that can be used to issue
        864 * commands against the located element. If the element is not found, the
        865 * element will be invalidated and all scheduled commands aborted.
        866 */
        867webdriver.WebDriver.prototype.findElement = function(locator) {
        868 var id;
        869 if ('nodeType' in locator && 'ownerDocument' in locator) {
        870 var element = /** @type {!Element} */ (locator);
        871 id = this.findDomElement_(element).then(function(element) {
        872 if (!element) {
        873 throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
        874 'Unable to locate element. Is WebDriver focused on its ' +
        875 'ownerDocument\'s frame?');
        876 }
        877 return element;
        878 });
        879 } else {
        880 locator = webdriver.Locator.checkLocator(locator);
        881 if (goog.isFunction(locator)) {
        882 id = this.findElementInternal_(locator, this);
        883 } else {
        884 var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENT).
        885 setParameter('using', locator.using).
        886 setParameter('value', locator.value);
        887 id = this.schedule(command, 'WebDriver.findElement(' + locator + ')');
        888 }
        889 }
        890 return new webdriver.WebElementPromise(this, id);
        891};
        892
        893
        894/**
        895 * @param {!Function} locatorFn The locator function to use.
        896 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
        897 * context.
        898 * @return {!webdriver.promise.Promise.<!webdriver.WebElement>} A
        899 * promise that will resolve to a list of WebElements.
        900 * @private
        901 */
        902webdriver.WebDriver.prototype.findElementInternal_ = function(
        903 locatorFn, context) {
        904 return this.call(goog.partial(locatorFn, context)).then(function(result) {
        905 if (goog.isArray(result)) {
        906 result = result[0];
        907 }
        908 if (!(result instanceof webdriver.WebElement)) {
        909 throw new TypeError('Custom locator did not return a WebElement');
        910 }
        911 return result;
        912 });
        913};
        914
        915
        916/**
        917 * Locates a DOM element so that commands may be issued against it using the
        918 * {@link webdriver.WebElement} class. This is accomplished by storing a
        919 * reference to the element in an object on the element's ownerDocument.
        920 * {@link #executeScript} will then be used to create a WebElement from this
        921 * reference. This requires this driver to currently be focused on the
        922 * ownerDocument's window+frame.
        923
        924 * @param {!Element} element The element to locate.
        925 * @return {!webdriver.promise.Promise.<webdriver.WebElement>} A promise that
        926 * will be fulfilled with the located element, or null if the element
        927 * could not be found.
        928 * @private
        929 */
        930webdriver.WebDriver.prototype.findDomElement_ = function(element) {
        931 var doc = element.ownerDocument;
        932 var store = doc['$webdriver$'] = doc['$webdriver$'] || {};
        933 var id = Math.floor(Math.random() * goog.now()).toString(36);
        934 store[id] = element;
        935 element[id] = id;
        936
        937 function cleanUp() {
        938 delete store[id];
        939 }
        940
        941 function lookupElement(id) {
        942 var store = document['$webdriver$'];
        943 if (!store) {
        944 return null;
        945 }
        946
        947 var element = store[id];
        948 if (!element || element[id] !== id) {
        949 return null;
        950 }
        951 return element;
        952 }
        953
        954 /** @type {!webdriver.promise.Promise.<webdriver.WebElement>} */
        955 var foundElement = this.executeScript(lookupElement, id);
        956 foundElement.thenFinally(cleanUp);
        957 return foundElement;
        958};
        959
        960
        961/**
        962 * Schedules a command to test if an element is present on the page.
        963 *
        964 * If given a DOM element, this function will check if it belongs to the
        965 * document the driver is currently focused on. Otherwise, the function will
        966 * test if at least one element can be found with the given search criteria.
        967 *
        968 * @param {!(webdriver.Locator|webdriver.By.Hash|Element|
        969 * Function)} locatorOrElement The locator to use, or the actual
        970 * DOM element to be located by the server.
        971 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
        972 * with whether the element is present on the page.
        973 */
        974webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) {
        975 if ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) {
        976 return this.findDomElement_(/** @type {!Element} */ (locatorOrElement)).
        977 then(function(result) { return !!result; });
        978 } else {
        979 return this.findElements.apply(this, arguments).then(function(result) {
        980 return !!result.length;
        981 });
        982 }
        983};
        984
        985
        986/**
        987 * Schedule a command to search for multiple elements on the page.
        988 *
        989 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator
        990 * strategy to use when searching for the element.
        991 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        992 * promise that will resolve to an array of WebElements.
        993 */
        994webdriver.WebDriver.prototype.findElements = function(locator) {
        995 locator = webdriver.Locator.checkLocator(locator);
        996 if (goog.isFunction(locator)) {
        997 return this.findElementsInternal_(locator, this);
        998 } else {
        999 var command = new webdriver.Command(webdriver.CommandName.FIND_ELEMENTS).
        1000 setParameter('using', locator.using).
        1001 setParameter('value', locator.value);
        1002 return this.schedule(command, 'WebDriver.findElements(' + locator + ')');
        1003 }
        1004};
        1005
        1006
        1007/**
        1008 * @param {!Function} locatorFn The locator function to use.
        1009 * @param {!(webdriver.WebDriver|webdriver.WebElement)} context The search
        1010 * context.
        1011 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        1012 * promise that will resolve to an array of WebElements.
        1013 * @private
        1014 */
        1015webdriver.WebDriver.prototype.findElementsInternal_ = function(
        1016 locatorFn, context) {
        1017 return this.call(goog.partial(locatorFn, context)).then(function(result) {
        1018 if (result instanceof webdriver.WebElement) {
        1019 return [result];
        1020 }
        1021
        1022 if (!goog.isArray(result)) {
        1023 return [];
        1024 }
        1025
        1026 return goog.array.filter(result, function(item) {
        1027 return item instanceof webdriver.WebElement;
        1028 });
        1029 });
        1030};
        1031
        1032
        1033/**
        1034 * Schedule a command to take a screenshot. The driver makes a best effort to
        1035 * return a screenshot of the following, in order of preference:
        1036 * <ol>
        1037 * <li>Entire page
        1038 * <li>Current window
        1039 * <li>Visible portion of the current frame
        1040 * <li>The screenshot of the entire display containing the browser
        1041 * </ol>
        1042 *
        1043 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        1044 * resolved to the screenshot as a base-64 encoded PNG.
        1045 */
        1046webdriver.WebDriver.prototype.takeScreenshot = function() {
        1047 return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT),
        1048 'WebDriver.takeScreenshot()');
        1049};
        1050
        1051
        1052/**
        1053 * @return {!webdriver.WebDriver.Options} The options interface for this
        1054 * instance.
        1055 */
        1056webdriver.WebDriver.prototype.manage = function() {
        1057 return new webdriver.WebDriver.Options(this);
        1058};
        1059
        1060
        1061/**
        1062 * @return {!webdriver.WebDriver.Navigation} The navigation interface for this
        1063 * instance.
        1064 */
        1065webdriver.WebDriver.prototype.navigate = function() {
        1066 return new webdriver.WebDriver.Navigation(this);
        1067};
        1068
        1069
        1070/**
        1071 * @return {!webdriver.WebDriver.TargetLocator} The target locator interface for
        1072 * this instance.
        1073 */
        1074webdriver.WebDriver.prototype.switchTo = function() {
        1075 return new webdriver.WebDriver.TargetLocator(this);
        1076};
        1077
        1078
        1079
        1080/**
        1081 * Interface for navigating back and forth in the browser history.
        1082 * @param {!webdriver.WebDriver} driver The parent driver.
        1083 * @constructor
        1084 */
        1085webdriver.WebDriver.Navigation = function(driver) {
        1086
        1087 /** @private {!webdriver.WebDriver} */
        1088 this.driver_ = driver;
        1089};
        1090
        1091
        1092/**
        1093 * Schedules a command to navigate to a new URL.
        1094 * @param {string} url The URL to navigate to.
        1095 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1096 * when the URL has been loaded.
        1097 */
        1098webdriver.WebDriver.Navigation.prototype.to = function(url) {
        1099 return this.driver_.schedule(
        1100 new webdriver.Command(webdriver.CommandName.GET).
        1101 setParameter('url', url),
        1102 'WebDriver.navigate().to(' + url + ')');
        1103};
        1104
        1105
        1106/**
        1107 * Schedules a command to move backwards in the browser history.
        1108 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1109 * when the navigation event has completed.
        1110 */
        1111webdriver.WebDriver.Navigation.prototype.back = function() {
        1112 return this.driver_.schedule(
        1113 new webdriver.Command(webdriver.CommandName.GO_BACK),
        1114 'WebDriver.navigate().back()');
        1115};
        1116
        1117
        1118/**
        1119 * Schedules a command to move forwards in the browser history.
        1120 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1121 * when the navigation event has completed.
        1122 */
        1123webdriver.WebDriver.Navigation.prototype.forward = function() {
        1124 return this.driver_.schedule(
        1125 new webdriver.Command(webdriver.CommandName.GO_FORWARD),
        1126 'WebDriver.navigate().forward()');
        1127};
        1128
        1129
        1130/**
        1131 * Schedules a command to refresh the current page.
        1132 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1133 * when the navigation event has completed.
        1134 */
        1135webdriver.WebDriver.Navigation.prototype.refresh = function() {
        1136 return this.driver_.schedule(
        1137 new webdriver.Command(webdriver.CommandName.REFRESH),
        1138 'WebDriver.navigate().refresh()');
        1139};
        1140
        1141
        1142
        1143/**
        1144 * Provides methods for managing browser and driver state.
        1145 * @param {!webdriver.WebDriver} driver The parent driver.
        1146 * @constructor
        1147 */
        1148webdriver.WebDriver.Options = function(driver) {
        1149
        1150 /** @private {!webdriver.WebDriver} */
        1151 this.driver_ = driver;
        1152};
        1153
        1154
        1155/**
        1156 * A JSON description of a browser cookie.
        1157 * @typedef {{
        1158 * name: string,
        1159 * value: string,
        1160 * path: (string|undefined),
        1161 * domain: (string|undefined),
        1162 * secure: (boolean|undefined),
        1163 * expiry: (number|undefined)
        1164 * }}
        1165 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
        1166 */
        1167webdriver.WebDriver.Options.Cookie;
        1168
        1169
        1170/**
        1171 * Schedules a command to add a cookie.
        1172 * @param {string} name The cookie name.
        1173 * @param {string} value The cookie value.
        1174 * @param {string=} opt_path The cookie path.
        1175 * @param {string=} opt_domain The cookie domain.
        1176 * @param {boolean=} opt_isSecure Whether the cookie is secure.
        1177 * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as
        1178 * a number, should be in milliseconds since midnight, January 1, 1970 UTC.
        1179 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1180 * when the cookie has been added to the page.
        1181 */
        1182webdriver.WebDriver.Options.prototype.addCookie = function(
        1183 name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) {
        1184 // We do not allow '=' or ';' in the name.
        1185 if (/[;=]/.test(name)) {
        1186 throw Error('Invalid cookie name "' + name + '"');
        1187 }
        1188
        1189 // We do not allow ';' in value.
        1190 if (/;/.test(value)) {
        1191 throw Error('Invalid cookie value "' + value + '"');
        1192 }
        1193
        1194 var cookieString = name + '=' + value +
        1195 (opt_domain ? ';domain=' + opt_domain : '') +
        1196 (opt_path ? ';path=' + opt_path : '') +
        1197 (opt_isSecure ? ';secure' : '');
        1198
        1199 var expiry;
        1200 if (goog.isDef(opt_expiry)) {
        1201 var expiryDate;
        1202 if (goog.isNumber(opt_expiry)) {
        1203 expiryDate = new Date(opt_expiry);
        1204 } else {
        1205 expiryDate = /** @type {!Date} */ (opt_expiry);
        1206 opt_expiry = expiryDate.getTime();
        1207 }
        1208 cookieString += ';expires=' + expiryDate.toUTCString();
        1209 // Convert from milliseconds to seconds.
        1210 expiry = Math.floor(/** @type {number} */ (opt_expiry) / 1000);
        1211 }
        1212
        1213 return this.driver_.schedule(
        1214 new webdriver.Command(webdriver.CommandName.ADD_COOKIE).
        1215 setParameter('cookie', {
        1216 'name': name,
        1217 'value': value,
        1218 'path': opt_path,
        1219 'domain': opt_domain,
        1220 'secure': !!opt_isSecure,
        1221 'expiry': expiry
        1222 }),
        1223 'WebDriver.manage().addCookie(' + cookieString + ')');
        1224};
        1225
        1226
        1227/**
        1228 * Schedules a command to delete all cookies visible to the current page.
        1229 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1230 * when all cookies have been deleted.
        1231 */
        1232webdriver.WebDriver.Options.prototype.deleteAllCookies = function() {
        1233 return this.driver_.schedule(
        1234 new webdriver.Command(webdriver.CommandName.DELETE_ALL_COOKIES),
        1235 'WebDriver.manage().deleteAllCookies()');
        1236};
        1237
        1238
        1239/**
        1240 * Schedules a command to delete the cookie with the given name. This command is
        1241 * a no-op if there is no cookie with the given name visible to the current
        1242 * page.
        1243 * @param {string} name The name of the cookie to delete.
        1244 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1245 * when the cookie has been deleted.
        1246 */
        1247webdriver.WebDriver.Options.prototype.deleteCookie = function(name) {
        1248 return this.driver_.schedule(
        1249 new webdriver.Command(webdriver.CommandName.DELETE_COOKIE).
        1250 setParameter('name', name),
        1251 'WebDriver.manage().deleteCookie(' + name + ')');
        1252};
        1253
        1254
        1255/**
        1256 * Schedules a command to retrieve all cookies visible to the current page.
        1257 * Each cookie will be returned as a JSON object as described by the WebDriver
        1258 * wire protocol.
        1259 * @return {!webdriver.promise.Promise.<
        1260 * !Array.<webdriver.WebDriver.Options.Cookie>>} A promise that will be
        1261 * resolved with the cookies visible to the current page.
        1262 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
        1263 */
        1264webdriver.WebDriver.Options.prototype.getCookies = function() {
        1265 return this.driver_.schedule(
        1266 new webdriver.Command(webdriver.CommandName.GET_ALL_COOKIES),
        1267 'WebDriver.manage().getCookies()');
        1268};
        1269
        1270
        1271/**
        1272 * Schedules a command to retrieve the cookie with the given name. Returns null
        1273 * if there is no such cookie. The cookie will be returned as a JSON object as
        1274 * described by the WebDriver wire protocol.
        1275 * @param {string} name The name of the cookie to retrieve.
        1276 * @return {!webdriver.promise.Promise.<?webdriver.WebDriver.Options.Cookie>} A
        1277 * promise that will be resolved with the named cookie, or {@code null}
        1278 * if there is no such cookie.
        1279 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object
        1280 */
        1281webdriver.WebDriver.Options.prototype.getCookie = function(name) {
        1282 return this.getCookies().then(function(cookies) {
        1283 return goog.array.find(cookies, function(cookie) {
        1284 return cookie && cookie['name'] == name;
        1285 });
        1286 });
        1287};
        1288
        1289
        1290/**
        1291 * @return {!webdriver.WebDriver.Logs} The interface for managing driver
        1292 * logs.
        1293 */
        1294webdriver.WebDriver.Options.prototype.logs = function() {
        1295 return new webdriver.WebDriver.Logs(this.driver_);
        1296};
        1297
        1298
        1299/**
        1300 * @return {!webdriver.WebDriver.Timeouts} The interface for managing driver
        1301 * timeouts.
        1302 */
        1303webdriver.WebDriver.Options.prototype.timeouts = function() {
        1304 return new webdriver.WebDriver.Timeouts(this.driver_);
        1305};
        1306
        1307
        1308/**
        1309 * @return {!webdriver.WebDriver.Window} The interface for managing the
        1310 * current window.
        1311 */
        1312webdriver.WebDriver.Options.prototype.window = function() {
        1313 return new webdriver.WebDriver.Window(this.driver_);
        1314};
        1315
        1316
        1317
        1318/**
        1319 * An interface for managing timeout behavior for WebDriver instances.
        1320 * @param {!webdriver.WebDriver} driver The parent driver.
        1321 * @constructor
        1322 */
        1323webdriver.WebDriver.Timeouts = function(driver) {
        1324
        1325 /** @private {!webdriver.WebDriver} */
        1326 this.driver_ = driver;
        1327};
        1328
        1329
        1330/**
        1331 * Specifies the amount of time the driver should wait when searching for an
        1332 * element if it is not immediately present.
        1333 *
        1334 * When searching for a single element, the driver should poll the page
        1335 * until the element has been found, or this timeout expires before failing
        1336 * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching
        1337 * for multiple elements, the driver should poll the page until at least one
        1338 * element has been found or this timeout has expired.
        1339 *
        1340 * Setting the wait timeout to 0 (its default value), disables implicit
        1341 * waiting.
        1342 *
        1343 * Increasing the implicit wait timeout should be used judiciously as it
        1344 * will have an adverse effect on test run time, especially when used with
        1345 * slower location strategies like XPath.
        1346 *
        1347 * @param {number} ms The amount of time to wait, in milliseconds.
        1348 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1349 * when the implicit wait timeout has been set.
        1350 */
        1351webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) {
        1352 return this.driver_.schedule(
        1353 new webdriver.Command(webdriver.CommandName.IMPLICITLY_WAIT).
        1354 setParameter('ms', ms < 0 ? 0 : ms),
        1355 'WebDriver.manage().timeouts().implicitlyWait(' + ms + ')');
        1356};
        1357
        1358
        1359/**
        1360 * Sets the amount of time to wait, in milliseconds, for an asynchronous script
        1361 * to finish execution before returning an error. If the timeout is less than or
        1362 * equal to 0, the script will be allowed to run indefinitely.
        1363 *
        1364 * @param {number} ms The amount of time to wait, in milliseconds.
        1365 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1366 * when the script timeout has been set.
        1367 */
        1368webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) {
        1369 return this.driver_.schedule(
        1370 new webdriver.Command(webdriver.CommandName.SET_SCRIPT_TIMEOUT).
        1371 setParameter('ms', ms < 0 ? 0 : ms),
        1372 'WebDriver.manage().timeouts().setScriptTimeout(' + ms + ')');
        1373};
        1374
        1375
        1376/**
        1377 * Sets the amount of time to wait for a page load to complete before returning
        1378 * an error. If the timeout is negative, page loads may be indefinite.
        1379 * @param {number} ms The amount of time to wait, in milliseconds.
        1380 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1381 * when the timeout has been set.
        1382 */
        1383webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) {
        1384 return this.driver_.schedule(
        1385 new webdriver.Command(webdriver.CommandName.SET_TIMEOUT).
        1386 setParameter('type', 'page load').
        1387 setParameter('ms', ms),
        1388 'WebDriver.manage().timeouts().pageLoadTimeout(' + ms + ')');
        1389};
        1390
        1391
        1392
        1393/**
        1394 * An interface for managing the current window.
        1395 * @param {!webdriver.WebDriver} driver The parent driver.
        1396 * @constructor
        1397 */
        1398webdriver.WebDriver.Window = function(driver) {
        1399
        1400 /** @private {!webdriver.WebDriver} */
        1401 this.driver_ = driver;
        1402};
        1403
        1404
        1405/**
        1406 * Retrieves the window's current position, relative to the top left corner of
        1407 * the screen.
        1408 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
        1409 * will be resolved with the window's position in the form of a
        1410 * {x:number, y:number} object literal.
        1411 */
        1412webdriver.WebDriver.Window.prototype.getPosition = function() {
        1413 return this.driver_.schedule(
        1414 new webdriver.Command(webdriver.CommandName.GET_WINDOW_POSITION).
        1415 setParameter('windowHandle', 'current'),
        1416 'WebDriver.manage().window().getPosition()');
        1417};
        1418
        1419
        1420/**
        1421 * Repositions the current window.
        1422 * @param {number} x The desired horizontal position, relative to the left side
        1423 * of the screen.
        1424 * @param {number} y The desired vertical position, relative to the top of the
        1425 * of the screen.
        1426 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1427 * when the command has completed.
        1428 */
        1429webdriver.WebDriver.Window.prototype.setPosition = function(x, y) {
        1430 return this.driver_.schedule(
        1431 new webdriver.Command(webdriver.CommandName.SET_WINDOW_POSITION).
        1432 setParameter('windowHandle', 'current').
        1433 setParameter('x', x).
        1434 setParameter('y', y),
        1435 'WebDriver.manage().window().setPosition(' + x + ', ' + y + ')');
        1436};
        1437
        1438
        1439/**
        1440 * Retrieves the window's current size.
        1441 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
        1442 * promise that will be resolved with the window's size in the form of a
        1443 * {width:number, height:number} object literal.
        1444 */
        1445webdriver.WebDriver.Window.prototype.getSize = function() {
        1446 return this.driver_.schedule(
        1447 new webdriver.Command(webdriver.CommandName.GET_WINDOW_SIZE).
        1448 setParameter('windowHandle', 'current'),
        1449 'WebDriver.manage().window().getSize()');
        1450};
        1451
        1452
        1453/**
        1454 * Resizes the current window.
        1455 * @param {number} width The desired window width.
        1456 * @param {number} height The desired window height.
        1457 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1458 * when the command has completed.
        1459 */
        1460webdriver.WebDriver.Window.prototype.setSize = function(width, height) {
        1461 return this.driver_.schedule(
        1462 new webdriver.Command(webdriver.CommandName.SET_WINDOW_SIZE).
        1463 setParameter('windowHandle', 'current').
        1464 setParameter('width', width).
        1465 setParameter('height', height),
        1466 'WebDriver.manage().window().setSize(' + width + ', ' + height + ')');
        1467};
        1468
        1469
        1470/**
        1471 * Maximizes the current window.
        1472 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1473 * when the command has completed.
        1474 */
        1475webdriver.WebDriver.Window.prototype.maximize = function() {
        1476 return this.driver_.schedule(
        1477 new webdriver.Command(webdriver.CommandName.MAXIMIZE_WINDOW).
        1478 setParameter('windowHandle', 'current'),
        1479 'WebDriver.manage().window().maximize()');
        1480};
        1481
        1482
        1483/**
        1484 * Interface for managing WebDriver log records.
        1485 * @param {!webdriver.WebDriver} driver The parent driver.
        1486 * @constructor
        1487 */
        1488webdriver.WebDriver.Logs = function(driver) {
        1489
        1490 /** @private {!webdriver.WebDriver} */
        1491 this.driver_ = driver;
        1492};
        1493
        1494
        1495/**
        1496 * Fetches available log entries for the given type.
        1497 *
        1498 * Note that log buffers are reset after each call, meaning that available
        1499 * log entries correspond to those entries not yet returned for a given log
        1500 * type. In practice, this means that this call will return the available log
        1501 * entries since the last call, or from the start of the session.
        1502 *
        1503 * @param {!webdriver.logging.Type} type The desired log type.
        1504 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Entry>>} A
        1505 * promise that will resolve to a list of log entries for the specified
        1506 * type.
        1507 */
        1508webdriver.WebDriver.Logs.prototype.get = function(type) {
        1509 return this.driver_.schedule(
        1510 new webdriver.Command(webdriver.CommandName.GET_LOG).
        1511 setParameter('type', type),
        1512 'WebDriver.manage().logs().get(' + type + ')').
        1513 then(function(entries) {
        1514 return goog.array.map(entries, function(entry) {
        1515 if (!(entry instanceof webdriver.logging.Entry)) {
        1516 return new webdriver.logging.Entry(
        1517 entry['level'], entry['message'], entry['timestamp'],
        1518 entry['type']);
        1519 }
        1520 return entry;
        1521 });
        1522 });
        1523};
        1524
        1525
        1526/**
        1527 * Retrieves the log types available to this driver.
        1528 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.logging.Type>>} A
        1529 * promise that will resolve to a list of available log types.
        1530 */
        1531webdriver.WebDriver.Logs.prototype.getAvailableLogTypes = function() {
        1532 return this.driver_.schedule(
        1533 new webdriver.Command(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES),
        1534 'WebDriver.manage().logs().getAvailableLogTypes()');
        1535};
        1536
        1537
        1538
        1539/**
        1540 * An interface for changing the focus of the driver to another frame or window.
        1541 * @param {!webdriver.WebDriver} driver The parent driver.
        1542 * @constructor
        1543 */
        1544webdriver.WebDriver.TargetLocator = function(driver) {
        1545
        1546 /** @private {!webdriver.WebDriver} */
        1547 this.driver_ = driver;
        1548};
        1549
        1550
        1551/**
        1552 * Schedules a command retrieve the {@code document.activeElement} element on
        1553 * the current document, or {@code document.body} if activeElement is not
        1554 * available.
        1555 * @return {!webdriver.WebElementPromise} The active element.
        1556 */
        1557webdriver.WebDriver.TargetLocator.prototype.activeElement = function() {
        1558 var id = this.driver_.schedule(
        1559 new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT),
        1560 'WebDriver.switchTo().activeElement()');
        1561 return new webdriver.WebElementPromise(this.driver_, id);
        1562};
        1563
        1564
        1565/**
        1566 * Schedules a command to switch focus of all future commands to the first frame
        1567 * on the page.
        1568 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1569 * when the driver has changed focus to the default content.
        1570 */
        1571webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() {
        1572 return this.driver_.schedule(
        1573 new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
        1574 setParameter('id', null),
        1575 'WebDriver.switchTo().defaultContent()');
        1576};
        1577
        1578
        1579/**
        1580 * Schedules a command to switch the focus of all future commands to another
        1581 * frame on the page.
        1582 *
        1583 * If the frame is specified by a number, the command will switch to the frame
        1584 * by its (zero-based) index into
        1585 * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames).
        1586 *
        1587 * If the frame is specified by a string, the command will select the frame by
        1588 * its name or ID. To select sub-frames, simply separate the frame names/IDs by
        1589 * dots. As an example, "main.child" will select the frame with the name "main"
        1590 * and then its child "child".
        1591 *
        1592 * If the specified frame can not be found, the deferred result will errback
        1593 * with a {@link bot.ErrorCode.NO_SUCH_FRAME} error.
        1594 *
        1595 * @param {string|number} nameOrIndex The frame locator.
        1596 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1597 * when the driver has changed focus to the specified frame.
        1598 */
        1599webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) {
        1600 return this.driver_.schedule(
        1601 new webdriver.Command(webdriver.CommandName.SWITCH_TO_FRAME).
        1602 setParameter('id', nameOrIndex),
        1603 'WebDriver.switchTo().frame(' + nameOrIndex + ')');
        1604};
        1605
        1606
        1607/**
        1608 * Schedules a command to switch the focus of all future commands to another
        1609 * window. Windows may be specified by their {@code window.name} attribute or
        1610 * by its handle (as returned by {@link webdriver.WebDriver#getWindowHandles}).
        1611 *
        1612 * If the specificed window can not be found, the deferred result will errback
        1613 * with a {@link bot.ErrorCode.NO_SUCH_WINDOW} error.
        1614 *
        1615 * @param {string} nameOrHandle The name or window handle of the window to
        1616 * switch focus to.
        1617 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1618 * when the driver has changed focus to the specified window.
        1619 */
        1620webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) {
        1621 return this.driver_.schedule(
        1622 new webdriver.Command(webdriver.CommandName.SWITCH_TO_WINDOW).
        1623 setParameter('name', nameOrHandle),
        1624 'WebDriver.switchTo().window(' + nameOrHandle + ')');
        1625};
        1626
        1627
        1628/**
        1629 * Schedules a command to change focus to the active alert dialog. This command
        1630 * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog
        1631 * is not currently open.
        1632 * @return {!webdriver.AlertPromise} The open alert.
        1633 */
        1634webdriver.WebDriver.TargetLocator.prototype.alert = function() {
        1635 var text = this.driver_.schedule(
        1636 new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT),
        1637 'WebDriver.switchTo().alert()');
        1638 var driver = this.driver_;
        1639 return new webdriver.AlertPromise(driver, text.then(function(text) {
        1640 return new webdriver.Alert(driver, text);
        1641 }));
        1642};
        1643
        1644
        1645/**
        1646 * Simulate pressing many keys at once in a "chord". Takes a sequence of
        1647 * {@link webdriver.Key}s or strings, appends each of the values to a string,
        1648 * and adds the chord termination key ({@link webdriver.Key.NULL}) and returns
        1649 * the resultant string.
        1650 *
        1651 * Note: when the low-level webdriver key handlers see Keys.NULL, active
        1652 * modifier keys (CTRL/ALT/SHIFT/etc) release via a keyup event.
        1653 *
        1654 * @param {...string} var_args The key sequence to concatenate.
        1655 * @return {string} The null-terminated key sequence.
        1656 * @see http://code.google.com/p/webdriver/issues/detail?id=79
        1657 */
        1658webdriver.Key.chord = function(var_args) {
        1659 var sequence = goog.array.reduce(
        1660 goog.array.slice(arguments, 0),
        1661 function(str, key) {
        1662 return str + key;
        1663 }, '');
        1664 sequence += webdriver.Key.NULL;
        1665 return sequence;
        1666};
        1667
        1668
        1669//////////////////////////////////////////////////////////////////////////////
        1670//
        1671// webdriver.WebElement
        1672//
        1673//////////////////////////////////////////////////////////////////////////////
        1674
        1675
        1676
        1677/**
        1678 * Represents a DOM element. WebElements can be found by searching from the
        1679 * document root using a {@link webdriver.WebDriver} instance, or by searching
        1680 * under another WebElement:
        1681 *
        1682 * driver.get('http://www.google.com');
        1683 * var searchForm = driver.findElement(By.tagName('form'));
        1684 * var searchBox = searchForm.findElement(By.name('q'));
        1685 * searchBox.sendKeys('webdriver');
        1686 *
        1687 * The WebElement is implemented as a promise for compatibility with the promise
        1688 * API. It will always resolve itself when its internal state has been fully
        1689 * resolved and commands may be issued against the element. This can be used to
        1690 * catch errors when an element cannot be located on the page:
        1691 *
        1692 * driver.findElement(By.id('not-there')).then(function(element) {
        1693 * alert('Found an element that was not expected to be there!');
        1694 * }, function(error) {
        1695 * alert('The element was not found, as expected');
        1696 * });
        1697 *
        1698 * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
        1699 * element.
        1700 * @param {!(webdriver.promise.Promise.<webdriver.WebElement.Id>|
        1701 * webdriver.WebElement.Id)} id The server-assigned opaque ID for the
        1702 * underlying DOM element.
        1703 * @constructor
        1704 * @extends {webdriver.Serializable.<webdriver.WebElement.Id>}
        1705 */
        1706webdriver.WebElement = function(driver, id) {
        1707 webdriver.Serializable.call(this);
        1708
        1709 /** @private {!webdriver.WebDriver} */
        1710 this.driver_ = driver;
        1711
        1712 /** @private {!webdriver.promise.Promise.<webdriver.WebElement.Id>} */
        1713 this.id_ = id instanceof webdriver.promise.Promise ?
        1714 id : webdriver.promise.fulfilled(id);
        1715};
        1716goog.inherits(webdriver.WebElement, webdriver.Serializable);
        1717
        1718
        1719/**
        1720 * Wire protocol definition of a WebElement ID.
        1721 * @typedef {{ELEMENT: string}}
        1722 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
        1723 */
        1724webdriver.WebElement.Id;
        1725
        1726
        1727/**
        1728 * The property key used in the wire protocol to indicate that a JSON object
        1729 * contains the ID of a WebElement.
        1730 * @type {string}
        1731 * @const
        1732 */
        1733webdriver.WebElement.ELEMENT_KEY = 'ELEMENT';
        1734
        1735
        1736/**
        1737 * Compares to WebElements for equality.
        1738 * @param {!webdriver.WebElement} a A WebElement.
        1739 * @param {!webdriver.WebElement} b A WebElement.
        1740 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        1741 * resolved to whether the two WebElements are equal.
        1742 */
        1743webdriver.WebElement.equals = function(a, b) {
        1744 if (a == b) {
        1745 return webdriver.promise.fulfilled(true);
        1746 }
        1747 var ids = [a.getId(), b.getId()];
        1748 return webdriver.promise.all(ids).then(function(ids) {
        1749 // If the two element's have the same ID, they should be considered
        1750 // equal. Otherwise, they may still be equivalent, but we'll need to
        1751 // ask the server to check for us.
        1752 if (ids[0][webdriver.WebElement.ELEMENT_KEY] ==
        1753 ids[1][webdriver.WebElement.ELEMENT_KEY]) {
        1754 return true;
        1755 }
        1756
        1757 var command = new webdriver.Command(webdriver.CommandName.ELEMENT_EQUALS);
        1758 command.setParameter('id', ids[0]);
        1759 command.setParameter('other', ids[1]);
        1760 return a.driver_.schedule(command, 'webdriver.WebElement.equals()');
        1761 });
        1762};
        1763
        1764
        1765/**
        1766 * @return {!webdriver.WebDriver} The parent driver for this instance.
        1767 */
        1768webdriver.WebElement.prototype.getDriver = function() {
        1769 return this.driver_;
        1770};
        1771
        1772
        1773/**
        1774 * @return {!webdriver.promise.Promise.<webdriver.WebElement.Id>} A promise
        1775 * that resolves to this element's JSON representation as defined by the
        1776 * WebDriver wire protocol.
        1777 * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol
        1778 */
        1779webdriver.WebElement.prototype.getId = function() {
        1780 return this.id_;
        1781};
        1782
        1783
        1784/**
        1785 * Returns the raw ID string ID for this element.
        1786 * @return {!webdriver.promise.Promise<string>} A promise that resolves to this
        1787 * element's raw ID as a string value.
        1788 * @package
        1789 */
        1790webdriver.WebElement.prototype.getRawId = function() {
        1791 return this.getId().then(function(value) {
        1792 return value['ELEMENT'];
        1793 });
        1794};
        1795
        1796
        1797/** @override */
        1798webdriver.WebElement.prototype.serialize = function() {
        1799 return this.getId();
        1800};
        1801
        1802
        1803/**
        1804 * Schedules a command that targets this element with the parent WebDriver
        1805 * instance. Will ensure this element's ID is included in the command parameters
        1806 * under the "id" key.
        1807 * @param {!webdriver.Command} command The command to schedule.
        1808 * @param {string} description A description of the command for debugging.
        1809 * @return {!webdriver.promise.Promise.<T>} A promise that will be resolved
        1810 * with the command result.
        1811 * @template T
        1812 * @see webdriver.WebDriver.prototype.schedule
        1813 * @private
        1814 */
        1815webdriver.WebElement.prototype.schedule_ = function(command, description) {
        1816 command.setParameter('id', this.getId());
        1817 return this.driver_.schedule(command, description);
        1818};
        1819
        1820
        1821/**
        1822 * Schedule a command to find a descendant of this element. If the element
        1823 * cannot be found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will
        1824 * be returned by the driver. Unlike other commands, this error cannot be
        1825 * suppressed. In other words, scheduling a command to find an element doubles
        1826 * as an assert that the element is present on the page. To test whether an
        1827 * element is present on the page, use {@link #isElementPresent} instead.
        1828 *
        1829 * The search criteria for an element may be defined using one of the
        1830 * factories in the {@link webdriver.By} namespace, or as a short-hand
        1831 * {@link webdriver.By.Hash} object. For example, the following two statements
        1832 * are equivalent:
        1833 *
        1834 * var e1 = element.findElement(By.id('foo'));
        1835 * var e2 = element.findElement({id:'foo'});
        1836 *
        1837 * You may also provide a custom locator function, which takes as input
        1838 * this WebDriver instance and returns a {@link webdriver.WebElement}, or a
        1839 * promise that will resolve to a WebElement. For example, to find the first
        1840 * visible link on a page, you could write:
        1841 *
        1842 * var link = element.findElement(firstVisibleLink);
        1843 *
        1844 * function firstVisibleLink(element) {
        1845 * var links = element.findElements(By.tagName('a'));
        1846 * return webdriver.promise.filter(links, function(link) {
        1847 * return links.isDisplayed();
        1848 * }).then(function(visibleLinks) {
        1849 * return visibleLinks[0];
        1850 * });
        1851 * }
        1852 *
        1853 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1854 * locator strategy to use when searching for the element.
        1855 * @return {!webdriver.WebElement} A WebElement that can be used to issue
        1856 * commands against the located element. If the element is not found, the
        1857 * element will be invalidated and all scheduled commands aborted.
        1858 */
        1859webdriver.WebElement.prototype.findElement = function(locator) {
        1860 locator = webdriver.Locator.checkLocator(locator);
        1861 var id;
        1862 if (goog.isFunction(locator)) {
        1863 id = this.driver_.findElementInternal_(locator, this);
        1864 } else {
        1865 var command = new webdriver.Command(
        1866 webdriver.CommandName.FIND_CHILD_ELEMENT).
        1867 setParameter('using', locator.using).
        1868 setParameter('value', locator.value);
        1869 id = this.schedule_(command, 'WebElement.findElement(' + locator + ')');
        1870 }
        1871 return new webdriver.WebElementPromise(this.driver_, id);
        1872};
        1873
        1874
        1875/**
        1876 * Schedules a command to test if there is at least one descendant of this
        1877 * element that matches the given search criteria.
        1878 *
        1879 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1880 * locator strategy to use when searching for the element.
        1881 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        1882 * resolved with whether an element could be located on the page.
        1883 */
        1884webdriver.WebElement.prototype.isElementPresent = function(locator) {
        1885 return this.findElements(locator).then(function(result) {
        1886 return !!result.length;
        1887 });
        1888};
        1889
        1890
        1891/**
        1892 * Schedules a command to find all of the descendants of this element that
        1893 * match the given search criteria.
        1894 *
        1895 * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The
        1896 * locator strategy to use when searching for the elements.
        1897 * @return {!webdriver.promise.Promise.<!Array.<!webdriver.WebElement>>} A
        1898 * promise that will resolve to an array of WebElements.
        1899 */
        1900webdriver.WebElement.prototype.findElements = function(locator) {
        1901 locator = webdriver.Locator.checkLocator(locator);
        1902 if (goog.isFunction(locator)) {
        1903 return this.driver_.findElementsInternal_(locator, this);
        1904 } else {
        1905 var command = new webdriver.Command(
        1906 webdriver.CommandName.FIND_CHILD_ELEMENTS).
        1907 setParameter('using', locator.using).
        1908 setParameter('value', locator.value);
        1909 return this.schedule_(command, 'WebElement.findElements(' + locator + ')');
        1910 }
        1911};
        1912
        1913
        1914/**
        1915 * Schedules a command to click on this element.
        1916 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1917 * when the click command has completed.
        1918 */
        1919webdriver.WebElement.prototype.click = function() {
        1920 return this.schedule_(
        1921 new webdriver.Command(webdriver.CommandName.CLICK_ELEMENT),
        1922 'WebElement.click()');
        1923};
        1924
        1925
        1926/**
        1927 * Schedules a command to type a sequence on the DOM element represented by this
        1928 * instance.
        1929 *
        1930 * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is
        1931 * processed in the keysequence, that key state is toggled until one of the
        1932 * following occurs:
        1933 *
        1934 * - The modifier key is encountered again in the sequence. At this point the
        1935 * state of the key is toggled (along with the appropriate keyup/down events).
        1936 * - The {@link webdriver.Key.NULL} key is encountered in the sequence. When
        1937 * this key is encountered, all modifier keys current in the down state are
        1938 * released (with accompanying keyup events). The NULL key can be used to
        1939 * simulate common keyboard shortcuts:
        1940 *
        1941 * element.sendKeys("text was",
        1942 * webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
        1943 * "now text is");
        1944 * // Alternatively:
        1945 * element.sendKeys("text was",
        1946 * webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
        1947 * "now text is");
        1948 *
        1949 * - The end of the keysequence is encountered. When there are no more keys
        1950 * to type, all depressed modifier keys are released (with accompanying keyup
        1951 * events).
        1952 *
        1953 * If this element is a file input ({@code <input type="file">}), the
        1954 * specified key sequence should specify the path to the file to attach to
        1955 * the element. This is analgous to the user clicking "Browse..." and entering
        1956 * the path into the file select dialog.
        1957 *
        1958 * var form = driver.findElement(By.css('form'));
        1959 * var element = form.findElement(By.css('input[type=file]'));
        1960 * element.sendKeys('/path/to/file.txt');
        1961 * form.submit();
        1962 *
        1963 * For uploads to function correctly, the entered path must reference a file
        1964 * on the _browser's_ machine, not the local machine running this script. When
        1965 * running against a remote Selenium server, a {@link webdriver.FileDetector}
        1966 * may be used to transparently copy files to the remote machine before
        1967 * attempting to upload them in the browser.
        1968 *
        1969 * __Note:__ On browsers where native keyboard events are not supported
        1970 * (e.g. Firefox on OS X), key events will be synthesized. Special
        1971 * punctionation keys will be synthesized according to a standard QWERTY en-us
        1972 * keyboard layout.
        1973 *
        1974 * @param {...(string|!webdriver.promise.Promise<string>)} var_args The sequence
        1975 * of keys to type. All arguments will be joined into a single sequence.
        1976 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        1977 * when all keys have been typed.
        1978 */
        1979webdriver.WebElement.prototype.sendKeys = function(var_args) {
        1980 // Coerce every argument to a string. This protects us from users that
        1981 // ignore the jsdoc and give us a number (which ends up causing problems on
        1982 // the server, which requires strings).
        1983 var keys = webdriver.promise.all(goog.array.slice(arguments, 0)).
        1984 then(function(keys) {
        1985 return goog.array.map(keys, String);
        1986 });
        1987 if (!this.driver_.fileDetector_) {
        1988 return this.schedule_(
        1989 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
        1990 setParameter('value', keys),
        1991 'WebElement.sendKeys()');
        1992 }
        1993
        1994 // Suppress unhandled rejection errors until the flow executes the command.
        1995 keys.thenCatch(goog.nullFunction);
        1996
        1997 var element = this;
        1998 return this.driver_.flow_.execute(function() {
        1999 return keys.then(function(keys) {
        2000 return element.driver_.fileDetector_
        2001 .handleFile(element.driver_, keys.join(''));
        2002 }).then(function(keys) {
        2003 return element.schedule_(
        2004 new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT).
        2005 setParameter('value', [keys]),
        2006 'WebElement.sendKeys()');
        2007 });
        2008 }, 'WebElement.sendKeys()');
        2009};
        2010
        2011
        2012/**
        2013 * Schedules a command to query for the tag/node name of this element.
        2014 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2015 * resolved with the element's tag name.
        2016 */
        2017webdriver.WebElement.prototype.getTagName = function() {
        2018 return this.schedule_(
        2019 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TAG_NAME),
        2020 'WebElement.getTagName()');
        2021};
        2022
        2023
        2024/**
        2025 * Schedules a command to query for the computed style of the element
        2026 * represented by this instance. If the element inherits the named style from
        2027 * its parent, the parent will be queried for its value. Where possible, color
        2028 * values will be converted to their hex representation (e.g. #00ff00 instead of
        2029 * rgb(0, 255, 0)).
        2030 *
        2031 * _Warning:_ the value returned will be as the browser interprets it, so
        2032 * it may be tricky to form a proper assertion.
        2033 *
        2034 * @param {string} cssStyleProperty The name of the CSS style property to look
        2035 * up.
        2036 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2037 * resolved with the requested CSS value.
        2038 */
        2039webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) {
        2040 var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY;
        2041 return this.schedule_(
        2042 new webdriver.Command(name).
        2043 setParameter('propertyName', cssStyleProperty),
        2044 'WebElement.getCssValue(' + cssStyleProperty + ')');
        2045};
        2046
        2047
        2048/**
        2049 * Schedules a command to query for the value of the given attribute of the
        2050 * element. Will return the current value, even if it has been modified after
        2051 * the page has been loaded. More exactly, this method will return the value of
        2052 * the given attribute, unless that attribute is not present, in which case the
        2053 * value of the property with the same name is returned. If neither value is
        2054 * set, null is returned (for example, the "value" property of a textarea
        2055 * element). The "style" attribute is converted as best can be to a
        2056 * text representation with a trailing semi-colon. The following are deemed to
        2057 * be "boolean" attributes and will return either "true" or null:
        2058 *
        2059 * async, autofocus, autoplay, checked, compact, complete, controls, declare,
        2060 * defaultchecked, defaultselected, defer, disabled, draggable, ended,
        2061 * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope,
        2062 * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open,
        2063 * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
        2064 * selected, spellcheck, truespeed, willvalidate
        2065 *
        2066 * Finally, the following commonly mis-capitalized attribute/property names
        2067 * are evaluated as expected:
        2068 *
        2069 * - "class"
        2070 * - "readonly"
        2071 *
        2072 * @param {string} attributeName The name of the attribute to query.
        2073 * @return {!webdriver.promise.Promise.<?string>} A promise that will be
        2074 * resolved with the attribute's value. The returned value will always be
        2075 * either a string or null.
        2076 */
        2077webdriver.WebElement.prototype.getAttribute = function(attributeName) {
        2078 return this.schedule_(
        2079 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_ATTRIBUTE).
        2080 setParameter('name', attributeName),
        2081 'WebElement.getAttribute(' + attributeName + ')');
        2082};
        2083
        2084
        2085/**
        2086 * Get the visible (i.e. not hidden by CSS) innerText of this element, including
        2087 * sub-elements, without any leading or trailing whitespace.
        2088 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2089 * resolved with the element's visible text.
        2090 */
        2091webdriver.WebElement.prototype.getText = function() {
        2092 return this.schedule_(
        2093 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_TEXT),
        2094 'WebElement.getText()');
        2095};
        2096
        2097
        2098/**
        2099 * Schedules a command to compute the size of this element's bounding box, in
        2100 * pixels.
        2101 * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A
        2102 * promise that will be resolved with the element's size as a
        2103 * {@code {width:number, height:number}} object.
        2104 */
        2105webdriver.WebElement.prototype.getSize = function() {
        2106 return this.schedule_(
        2107 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_SIZE),
        2108 'WebElement.getSize()');
        2109};
        2110
        2111
        2112/**
        2113 * Schedules a command to compute the location of this element in page space.
        2114 * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that
        2115 * will be resolved to the element's location as a
        2116 * {@code {x:number, y:number}} object.
        2117 */
        2118webdriver.WebElement.prototype.getLocation = function() {
        2119 return this.schedule_(
        2120 new webdriver.Command(webdriver.CommandName.GET_ELEMENT_LOCATION),
        2121 'WebElement.getLocation()');
        2122};
        2123
        2124
        2125/**
        2126 * Schedules a command to query whether the DOM element represented by this
        2127 * instance is enabled, as dicted by the {@code disabled} attribute.
        2128 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        2129 * resolved with whether this element is currently enabled.
        2130 */
        2131webdriver.WebElement.prototype.isEnabled = function() {
        2132 return this.schedule_(
        2133 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_ENABLED),
        2134 'WebElement.isEnabled()');
        2135};
        2136
        2137
        2138/**
        2139 * Schedules a command to query whether this element is selected.
        2140 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        2141 * resolved with whether this element is currently selected.
        2142 */
        2143webdriver.WebElement.prototype.isSelected = function() {
        2144 return this.schedule_(
        2145 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_SELECTED),
        2146 'WebElement.isSelected()');
        2147};
        2148
        2149
        2150/**
        2151 * Schedules a command to submit the form containing this element (or this
        2152 * element if it is a FORM element). This command is a no-op if the element is
        2153 * not contained in a form.
        2154 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        2155 * when the form has been submitted.
        2156 */
        2157webdriver.WebElement.prototype.submit = function() {
        2158 return this.schedule_(
        2159 new webdriver.Command(webdriver.CommandName.SUBMIT_ELEMENT),
        2160 'WebElement.submit()');
        2161};
        2162
        2163
        2164/**
        2165 * Schedules a command to clear the {@code value} of this element. This command
        2166 * has no effect if the underlying DOM element is neither a text INPUT element
        2167 * nor a TEXTAREA element.
        2168 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        2169 * when the element has been cleared.
        2170 */
        2171webdriver.WebElement.prototype.clear = function() {
        2172 return this.schedule_(
        2173 new webdriver.Command(webdriver.CommandName.CLEAR_ELEMENT),
        2174 'WebElement.clear()');
        2175};
        2176
        2177
        2178/**
        2179 * Schedules a command to test whether this element is currently displayed.
        2180 * @return {!webdriver.promise.Promise.<boolean>} A promise that will be
        2181 * resolved with whether this element is currently visible on the page.
        2182 */
        2183webdriver.WebElement.prototype.isDisplayed = function() {
        2184 return this.schedule_(
        2185 new webdriver.Command(webdriver.CommandName.IS_ELEMENT_DISPLAYED),
        2186 'WebElement.isDisplayed()');
        2187};
        2188
        2189
        2190/**
        2191 * Schedules a command to retrieve the outer HTML of this element.
        2192 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2193 * resolved with the element's outer HTML.
        2194 */
        2195webdriver.WebElement.prototype.getOuterHtml = function() {
        2196 return this.driver_.executeScript(function() {
        2197 var element = arguments[0];
        2198 if ('outerHTML' in element) {
        2199 return element.outerHTML;
        2200 } else {
        2201 var div = element.ownerDocument.createElement('div');
        2202 div.appendChild(element.cloneNode(true));
        2203 return div.innerHTML;
        2204 }
        2205 }, this);
        2206};
        2207
        2208
        2209/**
        2210 * Schedules a command to retrieve the inner HTML of this element.
        2211 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2212 * resolved with the element's inner HTML.
        2213 */
        2214webdriver.WebElement.prototype.getInnerHtml = function() {
        2215 return this.driver_.executeScript('return arguments[0].innerHTML', this);
        2216};
        2217
        2218
        2219
        2220/**
        2221 * WebElementPromise is a promise that will be fulfilled with a WebElement.
        2222 * This serves as a forward proxy on WebElement, allowing calls to be
        2223 * scheduled without directly on this instance before the underlying
        2224 * WebElement has been fulfilled. In other words, the following two statements
        2225 * are equivalent:
        2226 *
        2227 * driver.findElement({id: 'my-button'}).click();
        2228 * driver.findElement({id: 'my-button'}).then(function(el) {
        2229 * return el.click();
        2230 * });
        2231 *
        2232 * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this
        2233 * element.
        2234 * @param {!webdriver.promise.Promise.<!webdriver.WebElement>} el A promise
        2235 * that will resolve to the promised element.
        2236 * @constructor
        2237 * @extends {webdriver.WebElement}
        2238 * @implements {webdriver.promise.Thenable.<!webdriver.WebElement>}
        2239 * @final
        2240 */
        2241webdriver.WebElementPromise = function(driver, el) {
        2242 webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'});
        2243
        2244 /** @override */
        2245 this.cancel = goog.bind(el.cancel, el);
        2246
        2247 /** @override */
        2248 this.isPending = goog.bind(el.isPending, el);
        2249
        2250 /** @override */
        2251 this.then = goog.bind(el.then, el);
        2252
        2253 /** @override */
        2254 this.thenCatch = goog.bind(el.thenCatch, el);
        2255
        2256 /** @override */
        2257 this.thenFinally = goog.bind(el.thenFinally, el);
        2258
        2259 /**
        2260 * Defers returning the element ID until the wrapped WebElement has been
        2261 * resolved.
        2262 * @override
        2263 */
        2264 this.getId = function() {
        2265 return el.then(function(el) {
        2266 return el.getId();
        2267 });
        2268 };
        2269};
        2270goog.inherits(webdriver.WebElementPromise, webdriver.WebElement);
        2271
        2272
        2273/**
        2274 * Represents a modal dialog such as {@code alert}, {@code confirm}, or
        2275 * {@code prompt}. Provides functions to retrieve the message displayed with
        2276 * the alert, accept or dismiss the alert, and set the response text (in the
        2277 * case of {@code prompt}).
        2278 * @param {!webdriver.WebDriver} driver The driver controlling the browser this
        2279 * alert is attached to.
        2280 * @param {string} text The message text displayed with this alert.
        2281 * @constructor
        2282 */
        2283webdriver.Alert = function(driver, text) {
        2284 /** @private {!webdriver.WebDriver} */
        2285 this.driver_ = driver;
        2286
        2287 /** @private {!webdriver.promise.Promise.<string>} */
        2288 this.text_ = webdriver.promise.when(text);
        2289};
        2290
        2291
        2292/**
        2293 * Retrieves the message text displayed with this alert. For instance, if the
        2294 * alert were opened with alert("hello"), then this would return "hello".
        2295 * @return {!webdriver.promise.Promise.<string>} A promise that will be
        2296 * resolved to the text displayed with this alert.
        2297 */
        2298webdriver.Alert.prototype.getText = function() {
        2299 return this.text_;
        2300};
        2301
        2302
        2303/**
        2304 * Accepts this alert.
        2305 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        2306 * when this command has completed.
        2307 */
        2308webdriver.Alert.prototype.accept = function() {
        2309 return this.driver_.schedule(
        2310 new webdriver.Command(webdriver.CommandName.ACCEPT_ALERT),
        2311 'WebDriver.switchTo().alert().accept()');
        2312};
        2313
        2314
        2315/**
        2316 * Dismisses this alert.
        2317 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        2318 * when this command has completed.
        2319 */
        2320webdriver.Alert.prototype.dismiss = function() {
        2321 return this.driver_.schedule(
        2322 new webdriver.Command(webdriver.CommandName.DISMISS_ALERT),
        2323 'WebDriver.switchTo().alert().dismiss()');
        2324};
        2325
        2326
        2327/**
        2328 * Sets the response text on this alert. This command will return an error if
        2329 * the underlying alert does not support response text (e.g. window.alert and
        2330 * window.confirm).
        2331 * @param {string} text The text to set.
        2332 * @return {!webdriver.promise.Promise.<void>} A promise that will be resolved
        2333 * when this command has completed.
        2334 */
        2335webdriver.Alert.prototype.sendKeys = function(text) {
        2336 return this.driver_.schedule(
        2337 new webdriver.Command(webdriver.CommandName.SET_ALERT_TEXT).
        2338 setParameter('text', text),
        2339 'WebDriver.switchTo().alert().sendKeys(' + text + ')');
        2340};
        2341
        2342
        2343
        2344/**
        2345 * AlertPromise is a promise that will be fulfilled with an Alert. This promise
        2346 * serves as a forward proxy on an Alert, allowing calls to be scheduled
        2347 * directly on this instance before the underlying Alert has been fulfilled. In
        2348 * other words, the following two statements are equivalent:
        2349 *
        2350 * driver.switchTo().alert().dismiss();
        2351 * driver.switchTo().alert().then(function(alert) {
        2352 * return alert.dismiss();
        2353 * });
        2354 *
        2355 * @param {!webdriver.WebDriver} driver The driver controlling the browser this
        2356 * alert is attached to.
        2357 * @param {!webdriver.promise.Thenable.<!webdriver.Alert>} alert A thenable
        2358 * that will be fulfilled with the promised alert.
        2359 * @constructor
        2360 * @extends {webdriver.Alert}
        2361 * @implements {webdriver.promise.Thenable.<!webdriver.Alert>}
        2362 * @final
        2363 */
        2364webdriver.AlertPromise = function(driver, alert) {
        2365 webdriver.Alert.call(this, driver, 'unused');
        2366
        2367 /** @override */
        2368 this.cancel = goog.bind(alert.cancel, alert);
        2369
        2370 /** @override */
        2371 this.isPending = goog.bind(alert.isPending, alert);
        2372
        2373 /** @override */
        2374 this.then = goog.bind(alert.then, alert);
        2375
        2376 /** @override */
        2377 this.thenCatch = goog.bind(alert.thenCatch, alert);
        2378
        2379 /** @override */
        2380 this.thenFinally = goog.bind(alert.thenFinally, alert);
        2381
        2382 /**
        2383 * Defer returning text until the promised alert has been resolved.
        2384 * @override
        2385 */
        2386 this.getText = function() {
        2387 return alert.then(function(alert) {
        2388 return alert.getText();
        2389 });
        2390 };
        2391
        2392 /**
        2393 * Defers action until the alert has been located.
        2394 * @override
        2395 */
        2396 this.accept = function() {
        2397 return alert.then(function(alert) {
        2398 return alert.accept();
        2399 });
        2400 };
        2401
        2402 /**
        2403 * Defers action until the alert has been located.
        2404 * @override
        2405 */
        2406 this.dismiss = function() {
        2407 return alert.then(function(alert) {
        2408 return alert.dismiss();
        2409 });
        2410 };
        2411
        2412 /**
        2413 * Defers action until the alert has been located.
        2414 * @override
        2415 */
        2416 this.sendKeys = function(text) {
        2417 return alert.then(function(alert) {
        2418 return alert.sendKeys(text);
        2419 });
        2420 };
        2421};
        2422goog.inherits(webdriver.AlertPromise, webdriver.Alert);
        2423
        2424
        2425
        2426/**
        2427 * An error returned to indicate that there is an unhandled modal dialog on the
        2428 * current page.
        2429 * @param {string} message The error message.
        2430 * @param {string} text The text displayed with the unhandled alert.
        2431 * @param {!webdriver.Alert} alert The alert handle.
        2432 * @constructor
        2433 * @extends {bot.Error}
        2434 */
        2435webdriver.UnhandledAlertError = function(message, text, alert) {
        2436 webdriver.UnhandledAlertError.base(
        2437 this, 'constructor', bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message);
        2438
        2439 /** @private {string} */
        2440 this.text_ = text;
        2441
        2442 /** @private {!webdriver.Alert} */
        2443 this.alert_ = alert;
        2444};
        2445goog.inherits(webdriver.UnhandledAlertError, bot.Error);
        2446
        2447
        2448/**
        2449 * @return {string} The text displayed with the unhandled alert.
        2450 */
        2451webdriver.UnhandledAlertError.prototype.getAlertText = function() {
        2452 return this.text_;
        2453};
        2454
        2455
        2456
        2457/**
        2458 * Used with {@link webdriver.WebElement#sendKeys WebElement#sendKeys} on file
        2459 * input elements ({@code <input type="file">}) to detect when the entered key
        2460 * sequence defines the path to a file.
        2461 *
        2462 * By default, {@linkplain webdriver.WebElement WebElement's} will enter all
        2463 * key sequences exactly as entered. You may set a
        2464 * {@linkplain webdriver.WebDriver#setFileDetector file detector} on the parent
        2465 * WebDriver instance to define custom behavior for handling file elements. Of
        2466 * particular note is the {@link selenium-webdriver/remote.FileDetector}, which
        2467 * should be used when running against a remote
        2468 * [Selenium Server](http://docs.seleniumhq.org/download/).
        2469 */
        2470webdriver.FileDetector = goog.defineClass(null, {
        2471 /** @constructor */
        2472 constructor: function() {},
        2473
        2474 /**
        2475 * Handles the file specified by the given path, preparing it for use with
        2476 * the current browser. If the path does not refer to a valid file, it will
        2477 * be returned unchanged, otherwisee a path suitable for use with the current
        2478 * browser will be returned.
        2479 *
        2480 * This default implementation is a no-op. Subtypes may override this
        2481 * function for custom tailored file handling.
        2482 *
        2483 * @param {!webdriver.WebDriver} driver The driver for the current browser.
        2484 * @param {string} path The path to process.
        2485 * @return {!webdriver.promise.Promise<string>} A promise for the processed
        2486 * file path.
        2487 * @package
        2488 */
        2489 handleFile: function(driver, path) {
        2490 return webdriver.promise.fulfilled(path);
        2491 }
        2492});
        \ No newline at end of file diff --git a/docs/source/net/index.js.src.html b/docs/source/net/index.js.src.html index dbcd644..0cacfaa 100644 --- a/docs/source/net/index.js.src.html +++ b/docs/source/net/index.js.src.html @@ -1 +1 @@ -index.js

        net/index.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16'use strict';
        17
        18var os = require('os');
        19
        20
        21function getLoInterface() {
        22 var name;
        23 if (process.platform === 'darwin') {
        24 name = 'lo0';
        25 } else if (process.platform === 'linux') {
        26 name = 'lo';
        27 }
        28 return name ? os.networkInterfaces()[name] : null;
        29}
        30
        31
        32/**
        33 * Queries the system network interfaces for an IP address.
        34 * @param {boolean} loopback Whether to find a loopback address.
        35 * @param {string=} opt_family The IP family (IPv4 or IPv6). Defaults to IPv4.
        36 * @return {string} The located IP address or undefined.
        37 */
        38function getAddress(loopback, opt_family) {
        39 var family = opt_family || 'IPv4';
        40 var addresses = [];
        41
        42 var interfaces;
        43 if (loopback) {
        44 interfaces = [getLoInterface()];
        45 }
        46 interfaces = interfaces || os.networkInterfaces();
        47 for (var key in interfaces) {
        48 interfaces[key].forEach(function(ipAddress) {
        49 if (ipAddress.family === family &&
        50 ipAddress.internal === loopback) {
        51 addresses.push(ipAddress.address);
        52 }
        53 });
        54 }
        55 return addresses[0];
        56}
        57
        58
        59// PUBLIC API
        60
        61
        62/**
        63 * Retrieves the external IP address for this host.
        64 * @param {string=} opt_family The IP family to retrieve. Defaults to "IPv4".
        65 * @return {string} The IP address or undefined if not available.
        66 */
        67exports.getAddress = function(opt_family) {
        68 return getAddress(false, opt_family);
        69};
        70
        71
        72/**
        73 * Retrieves a loopback address for this machine.
        74 * @param {string=} opt_family The IP family to retrieve. Defaults to "IPv4".
        75 * @return {string} The IP address or undefined if not available.
        76 */
        77exports.getLoopbackAddress = function(opt_family) {
        78 return getAddress(true, opt_family);
        79};
        \ No newline at end of file +index.js

        net/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var os = require('os');
        21
        22
        23function getLoInterface() {
        24 var name;
        25 if (process.platform === 'darwin') {
        26 name = 'lo0';
        27 } else if (process.platform === 'linux') {
        28 name = 'lo';
        29 }
        30 return name ? os.networkInterfaces()[name] : null;
        31}
        32
        33
        34/**
        35 * Queries the system network interfaces for an IP address.
        36 * @param {boolean} loopback Whether to find a loopback address.
        37 * @param {string=} opt_family The IP family (IPv4 or IPv6). Defaults to IPv4.
        38 * @return {string} The located IP address or undefined.
        39 */
        40function getAddress(loopback, opt_family) {
        41 var family = opt_family || 'IPv4';
        42 var addresses = [];
        43
        44 var interfaces;
        45 if (loopback) {
        46 var lo = getLoInterface();
        47 interfaces = lo ? [lo] : null;
        48 }
        49 interfaces = interfaces || os.networkInterfaces();
        50 for (var key in interfaces) {
        51 interfaces[key].forEach(function(ipAddress) {
        52 if (ipAddress.family === family &&
        53 ipAddress.internal === loopback) {
        54 addresses.push(ipAddress.address);
        55 }
        56 });
        57 }
        58 return addresses[0];
        59}
        60
        61
        62// PUBLIC API
        63
        64
        65/**
        66 * Retrieves the external IP address for this host.
        67 * @param {string=} opt_family The IP family to retrieve. Defaults to "IPv4".
        68 * @return {string} The IP address or undefined if not available.
        69 */
        70exports.getAddress = function(opt_family) {
        71 return getAddress(false, opt_family);
        72};
        73
        74
        75/**
        76 * Retrieves a loopback address for this machine.
        77 * @param {string=} opt_family The IP family to retrieve. Defaults to "IPv4".
        78 * @return {string} The IP address or undefined if not available.
        79 */
        80exports.getLoopbackAddress = function(opt_family) {
        81 return getAddress(true, opt_family);
        82};
        \ No newline at end of file diff --git a/docs/source/net/portprober.js.src.html b/docs/source/net/portprober.js.src.html index 115b0c9..591c440 100644 --- a/docs/source/net/portprober.js.src.html +++ b/docs/source/net/portprober.js.src.html @@ -1 +1 @@ -portprober.js

        net/portprober.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16'use strict';
        17
        18var exec = require('child_process').exec,
        19 fs = require('fs'),
        20 net = require('net');
        21
        22var promise = require('../index').promise;
        23
        24
        25/**
        26 * The IANA suggested ephemeral port range.
        27 * @type {{min: number, max: number}}
        28 * @const
        29 * @see http://en.wikipedia.org/wiki/Ephemeral_ports
        30 */
        31var DEFAULT_IANA_RANGE = {min: 49152, max: 65535};
        32
        33
        34/**
        35 * The epheremal port range for the current system. Lazily computed on first
        36 * access.
        37 * @type {webdriver.promise.Promise.<{min: number, max: number}>}
        38 */
        39var systemRange = null;
        40
        41
        42/**
        43 * Computes the ephemeral port range for the current system. This is based on
        44 * http://stackoverflow.com/a/924337.
        45 * @return {webdriver.promise.Promise.<{min: number, max: number}>} A promise
        46 * that will resolve to the ephemeral port range of the current system.
        47 */
        48function findSystemPortRange() {
        49 if (systemRange) {
        50 return systemRange;
        51 }
        52 var range = process.platform === 'win32' ?
        53 findWindowsPortRange() : findUnixPortRange();
        54 return systemRange = range.thenCatch(function() {
        55 return DEFAULT_IANA_RANGE;
        56 });
        57}
        58
        59
        60/**
        61 * Executes a command and returns its output if it succeeds.
        62 * @param {string} cmd The command to execute.
        63 * @return {!webdriver.promise.Promise<string>} A promise that will resolve
        64 * with the command's stdout data.
        65 */
        66function execute(cmd) {
        67 var result = promise.defer();
        68 exec(cmd, function(err, stdout) {
        69 if (err) {
        70 result.reject(err);
        71 } else {
        72 result.fulfill(stdout);
        73 }
        74 });
        75 return result.promise;
        76}
        77
        78
        79/**
        80 * Computes the ephemeral port range for a Unix-like system.
        81 * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise
        82 * that will resolve with the ephemeral port range on the current system.
        83 */
        84function findUnixPortRange() {
        85 var cmd;
        86 if (process.platform === 'sunos') {
        87 cmd =
        88 '/usr/sbin/ndd /dev/tcp tcp_smallest_anon_port tcp_largest_anon_port';
        89 } else if (fs.existsSync('/proc/sys/net/ipv4/ip_local_port_range')) {
        90 // Linux
        91 cmd = 'cat /proc/sys/net/ipv4/ip_local_port_range';
        92 } else {
        93 cmd = 'sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last' +
        94 ' | sed -e "s/.*:\\s*//"';
        95 }
        96
        97 return execute(cmd).then(function(stdout) {
        98 if (!stdout || !stdout.length) return DEFAULT_IANA_RANGE;
        99 var range = stdout.trim().split(/\s+/).map(Number);
        100 if (range.some(isNaN)) return DEFAULT_IANA_RANGE;
        101 return {min: range[0], max: range[1]};
        102 });
        103}
        104
        105
        106/**
        107 * Computes the ephemeral port range for a Windows system.
        108 * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise
        109 * that will resolve with the ephemeral port range on the current system.
        110 */
        111function findWindowsPortRange() {
        112 var deferredRange = promise.defer();
        113 // First, check if we're running on XP. If this initial command fails,
        114 // we just fallback on the default IANA range.
        115 return execute('cmd.exe /c ver').then(function(stdout) {
        116 if (/Windows XP/.test(stdout)) {
        117 // TODO: Try to read these values from the registry.
        118 return {min: 1025, max: 5000};
        119 } else {
        120 return execute('netsh int ipv4 show dynamicport tcp').
        121 then(function(stdout) {
        122 /* > netsh int ipv4 show dynamicport tcp
        123 Protocol tcp Dynamic Port Range
        124 ---------------------------------
        125 Start Port : 49152
        126 Number of Ports : 16384
        127 */
        128 var range = stdout.split(/\n/).filter(function(line) {
        129 return /.*:\s*\d+/.test(line);
        130 }).map(function(line) {
        131 return Number(line.split(/:\s*/)[1]);
        132 });
        133
        134 return {
        135 min: range[0],
        136 max: range[0] + range[1]
        137 };
        138 });
        139 }
        140 });
        141}
        142
        143
        144/**
        145 * Tests if a port is free.
        146 * @param {number} port The port to test.
        147 * @param {string=} opt_host The bound host to test the {@code port} against.
        148 * Defaults to {@code INADDR_ANY}.
        149 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
        150 * with whether the port is free.
        151 */
        152function isFree(port, opt_host) {
        153 var result = promise.defer(function() {
        154 server.cancel();
        155 });
        156
        157 var server = net.createServer().on('error', function(e) {
        158 if (e.code === 'EADDRINUSE') {
        159 result.fulfill(false);
        160 } else {
        161 result.reject(e);
        162 }
        163 });
        164
        165 server.listen(port, opt_host, function() {
        166 server.close(function() {
        167 result.fulfill(true);
        168 });
        169 });
        170
        171 return result.promise;
        172}
        173
        174
        175/**
        176 * @param {string=} opt_host The bound host to test the {@code port} against.
        177 * Defaults to {@code INADDR_ANY}.
        178 * @return {!webdriver.promise.Promise.<number>} A promise that will resolve
        179 * to a free port. If a port cannot be found, the promise will be
        180 * rejected.
        181 */
        182function findFreePort(opt_host) {
        183 return findSystemPortRange().then(function(range) {
        184 var attempts = 0;
        185 var deferredPort = promise.defer();
        186 findPort();
        187 return deferredPort.promise;
        188
        189 function findPort() {
        190 attempts += 1;
        191 if (attempts > 10) {
        192 deferredPort.reject(Error('Unable to find a free port'));
        193 }
        194
        195 var port = Math.floor(
        196 Math.random() * (range.max - range.min) + range.min);
        197 isFree(port, opt_host).then(function(isFree) {
        198 if (isFree) {
        199 deferredPort.fulfill(port);
        200 } else {
        201 findPort();
        202 }
        203 });
        204 }
        205 });
        206}
        207
        208
        209// PUBLIC API
        210
        211
        212exports.findFreePort = findFreePort;
        213exports.isFree = isFree;
        \ No newline at end of file +portprober.js

        net/portprober.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var exec = require('child_process').exec,
        21 fs = require('fs'),
        22 net = require('net');
        23
        24var promise = require('../index').promise;
        25
        26
        27/**
        28 * The IANA suggested ephemeral port range.
        29 * @type {{min: number, max: number}}
        30 * @const
        31 * @see http://en.wikipedia.org/wiki/Ephemeral_ports
        32 */
        33var DEFAULT_IANA_RANGE = {min: 49152, max: 65535};
        34
        35
        36/**
        37 * The epheremal port range for the current system. Lazily computed on first
        38 * access.
        39 * @type {webdriver.promise.Promise.<{min: number, max: number}>}
        40 */
        41var systemRange = null;
        42
        43
        44/**
        45 * Computes the ephemeral port range for the current system. This is based on
        46 * http://stackoverflow.com/a/924337.
        47 * @return {webdriver.promise.Promise.<{min: number, max: number}>} A promise
        48 * that will resolve to the ephemeral port range of the current system.
        49 */
        50function findSystemPortRange() {
        51 if (systemRange) {
        52 return systemRange;
        53 }
        54 var range = process.platform === 'win32' ?
        55 findWindowsPortRange() : findUnixPortRange();
        56 return systemRange = range.thenCatch(function() {
        57 return DEFAULT_IANA_RANGE;
        58 });
        59}
        60
        61
        62/**
        63 * Executes a command and returns its output if it succeeds.
        64 * @param {string} cmd The command to execute.
        65 * @return {!webdriver.promise.Promise.<string>} A promise that will resolve
        66 * with the command's stdout data.
        67 */
        68function execute(cmd) {
        69 var result = promise.defer();
        70 exec(cmd, function(err, stdout) {
        71 if (err) {
        72 result.reject(err);
        73 } else {
        74 result.fulfill(stdout);
        75 }
        76 });
        77 return result.promise;
        78}
        79
        80
        81/**
        82 * Computes the ephemeral port range for a Unix-like system.
        83 * @return {!webdriver.promise.Promise.<{min: number, max: number}>} A promise
        84 * that will resolve with the ephemeral port range on the current system.
        85 */
        86function findUnixPortRange() {
        87 var cmd;
        88 if (process.platform === 'sunos') {
        89 cmd =
        90 '/usr/sbin/ndd /dev/tcp tcp_smallest_anon_port tcp_largest_anon_port';
        91 } else if (fs.existsSync('/proc/sys/net/ipv4/ip_local_port_range')) {
        92 // Linux
        93 cmd = 'cat /proc/sys/net/ipv4/ip_local_port_range';
        94 } else {
        95 cmd = 'sysctl net.inet.ip.portrange.first net.inet.ip.portrange.last' +
        96 ' | sed -e "s/.*:\\s*//"';
        97 }
        98
        99 return execute(cmd).then(function(stdout) {
        100 if (!stdout || !stdout.length) return DEFAULT_IANA_RANGE;
        101 var range = stdout.trim().split(/\s+/).map(Number);
        102 if (range.some(isNaN)) return DEFAULT_IANA_RANGE;
        103 return {min: range[0], max: range[1]};
        104 });
        105}
        106
        107
        108/**
        109 * Computes the ephemeral port range for a Windows system.
        110 * @return {!webdriver.promise.Promise.<{min: number, max: number}>} A promise
        111 * that will resolve with the ephemeral port range on the current system.
        112 */
        113function findWindowsPortRange() {
        114 var deferredRange = promise.defer();
        115 // First, check if we're running on XP. If this initial command fails,
        116 // we just fallback on the default IANA range.
        117 return execute('cmd.exe /c ver').then(function(stdout) {
        118 if (/Windows XP/.test(stdout)) {
        119 // TODO: Try to read these values from the registry.
        120 return {min: 1025, max: 5000};
        121 } else {
        122 return execute('netsh int ipv4 show dynamicport tcp').
        123 then(function(stdout) {
        124 /* > netsh int ipv4 show dynamicport tcp
        125 Protocol tcp Dynamic Port Range
        126 ---------------------------------
        127 Start Port : 49152
        128 Number of Ports : 16384
        129 */
        130 var range = stdout.split(/\n/).filter(function(line) {
        131 return /.*:\s*\d+/.test(line);
        132 }).map(function(line) {
        133 return Number(line.split(/:\s*/)[1]);
        134 });
        135
        136 return {
        137 min: range[0],
        138 max: range[0] + range[1]
        139 };
        140 });
        141 }
        142 });
        143}
        144
        145
        146/**
        147 * Tests if a port is free.
        148 * @param {number} port The port to test.
        149 * @param {string=} opt_host The bound host to test the {@code port} against.
        150 * Defaults to {@code INADDR_ANY}.
        151 * @return {!webdriver.promise.Promise.<boolean>} A promise that will resolve
        152 * with whether the port is free.
        153 */
        154function isFree(port, opt_host) {
        155 var result = promise.defer(function() {
        156 server.cancel();
        157 });
        158
        159 var server = net.createServer().on('error', function(e) {
        160 if (e.code === 'EADDRINUSE') {
        161 result.fulfill(false);
        162 } else {
        163 result.reject(e);
        164 }
        165 });
        166
        167 server.listen(port, opt_host, function() {
        168 server.close(function() {
        169 result.fulfill(true);
        170 });
        171 });
        172
        173 return result.promise;
        174}
        175
        176
        177/**
        178 * @param {string=} opt_host The bound host to test the {@code port} against.
        179 * Defaults to {@code INADDR_ANY}.
        180 * @return {!webdriver.promise.Promise.<number>} A promise that will resolve
        181 * to a free port. If a port cannot be found, the promise will be
        182 * rejected.
        183 */
        184function findFreePort(opt_host) {
        185 return findSystemPortRange().then(function(range) {
        186 var attempts = 0;
        187 var deferredPort = promise.defer();
        188 findPort();
        189 return deferredPort.promise;
        190
        191 function findPort() {
        192 attempts += 1;
        193 if (attempts > 10) {
        194 deferredPort.reject(Error('Unable to find a free port'));
        195 }
        196
        197 var port = Math.floor(
        198 Math.random() * (range.max - range.min) + range.min);
        199 isFree(port, opt_host).then(function(isFree) {
        200 if (isFree) {
        201 deferredPort.fulfill(port);
        202 } else {
        203 findPort();
        204 }
        205 });
        206 }
        207 });
        208}
        209
        210
        211// PUBLIC API
        212
        213
        214exports.findFreePort = findFreePort;
        215exports.isFree = isFree;
        \ No newline at end of file diff --git a/docs/source/opera.js.src.html b/docs/source/opera.js.src.html new file mode 100644 index 0000000..9d18944 --- /dev/null +++ b/docs/source/opera.js.src.html @@ -0,0 +1 @@ +opera.js

        opera.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a {@linkplain Driver WebDriver} client for the
        20 * Opera web browser (v26+). Before using this module, you must download the
        21 * latest OperaDriver
        22 * [release](https://github.com/operasoftware/operachromiumdriver/releases) and
        23 * ensure it can be found on your system
        24 * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29).
        25 *
        26 * There are three primary classes exported by this module:
        27 *
        28 * 1. {@linkplain ServiceBuilder}: configures the
        29 * {@link selenium-webdriver/remote.DriverService remote.DriverService}
        30 * that manages the
        31 * [OperaDriver](https://github.com/operasoftware/operachromiumdriver)
        32 * child process.
        33 *
        34 * 2. {@linkplain Options}: defines configuration options for each new Opera
        35 * session, such as which {@linkplain Options#setProxy proxy} to use,
        36 * what {@linkplain Options#addExtensions extensions} to install, or
        37 * what {@linkplain Options#addArguments command-line switches} to use when
        38 * starting the browser.
        39 *
        40 * 3. {@linkplain Driver}: the WebDriver client; each new instance will control
        41 * a unique browser session with a clean user profile (unless otherwise
        42 * configured through the {@link Options} class).
        43 *
        44 * By default, every Opera session will use a single driver service, which is
        45 * started the first time a {@link Driver} instance is created and terminated
        46 * when this process exits. The default service will inherit its environment
        47 * from the current process and direct all output to /dev/null. You may obtain
        48 * a handle to this default service using
        49 * {@link #getDefaultService getDefaultService()} and change its configuration
        50 * with {@link #setDefaultService setDefaultService()}.
        51 *
        52 * You may also create a {@link Driver} with its own driver service. This is
        53 * useful if you need to capture the server's log output for a specific session:
        54 *
        55 * var opera = require('selenium-webdriver/opera');
        56 *
        57 * var service = new opera.ServiceBuilder()
        58 * .loggingTo('/my/log/file.txt')
        59 * .enableVerboseLogging()
        60 * .build();
        61 *
        62 * var options = new opera.Options();
        63 * // configure browser options ...
        64 *
        65 * var driver = new opera.Driver(options, service);
        66 *
        67 * Users should only instantiate the {@link Driver} class directly when they
        68 * need a custom driver service configuration (as shown above). For normal
        69 * operation, users should start Opera using the
        70 * {@link selenium-webdriver.Builder}.
        71 */
        72
        73'use strict';
        74
        75var fs = require('fs'),
        76 util = require('util');
        77
        78var webdriver = require('./index'),
        79 executors = require('./executors'),
        80 io = require('./io'),
        81 portprober = require('./net/portprober'),
        82 remote = require('./remote');
        83
        84
        85/**
        86 * Name of the OperaDriver executable.
        87 * @type {string}
        88 * @const
        89 */
        90var OPERADRIVER_EXE =
        91 process.platform === 'win32' ? 'operadriver.exe' : 'operadriver';
        92
        93
        94/**
        95 * Creates {@link remote.DriverService} instances that manages an
        96 * [OperaDriver](https://github.com/operasoftware/operachromiumdriver)
        97 * server in a child process.
        98 *
        99 * @param {string=} opt_exe Path to the server executable to use. If omitted,
        100 * the builder will attempt to locate the operadriver on the current
        101 * PATH.
        102 * @throws {Error} If provided executable does not exist, or the operadriver
        103 * cannot be found on the PATH.
        104 * @constructor
        105 */
        106var ServiceBuilder = function(opt_exe) {
        107 /** @private {string} */
        108 this.exe_ = opt_exe || io.findInPath(OPERADRIVER_EXE, true);
        109 if (!this.exe_) {
        110 throw Error(
        111 'The OperaDriver could not be found on the current PATH. Please ' +
        112 'download the latest version of the OperaDriver from ' +
        113 'https://github.com/operasoftware/operachromiumdriver/releases and ' +
        114 'ensure it can be found on your PATH.');
        115 }
        116
        117 if (!fs.existsSync(this.exe_)) {
        118 throw Error('File does not exist: ' + this.exe_);
        119 }
        120
        121 /** @private {!Array.<string>} */
        122 this.args_ = [];
        123 this.stdio_ = 'ignore';
        124};
        125
        126
        127/** @private {number} */
        128ServiceBuilder.prototype.port_ = 0;
        129
        130
        131/** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
        132ServiceBuilder.prototype.stdio_ = 'ignore';
        133
        134
        135/** @private {Object.<string, string>} */
        136ServiceBuilder.prototype.env_ = null;
        137
        138
        139/**
        140 * Sets the port to start the OperaDriver on.
        141 * @param {number} port The port to use, or 0 for any free port.
        142 * @return {!ServiceBuilder} A self reference.
        143 * @throws {Error} If the port is invalid.
        144 */
        145ServiceBuilder.prototype.usingPort = function(port) {
        146 if (port < 0) {
        147 throw Error('port must be >= 0: ' + port);
        148 }
        149 this.port_ = port;
        150 return this;
        151};
        152
        153
        154/**
        155 * Sets the path of the log file the driver should log to. If a log file is
        156 * not specified, the driver will log to stderr.
        157 * @param {string} path Path of the log file to use.
        158 * @return {!ServiceBuilder} A self reference.
        159 */
        160ServiceBuilder.prototype.loggingTo = function(path) {
        161 this.args_.push('--log-path=' + path);
        162 return this;
        163};
        164
        165
        166/**
        167 * Enables verbose logging.
        168 * @return {!ServiceBuilder} A self reference.
        169 */
        170ServiceBuilder.prototype.enableVerboseLogging = function() {
        171 this.args_.push('--verbose');
        172 return this;
        173};
        174
        175
        176/**
        177 * Silence sthe drivers output.
        178 * @return {!ServiceBuilder} A self reference.
        179 */
        180ServiceBuilder.prototype.silent = function() {
        181 this.args_.push('--silent');
        182 return this;
        183};
        184
        185
        186/**
        187 * Defines the stdio configuration for the driver service. See
        188 * {@code child_process.spawn} for more information.
        189 * @param {(string|!Array.<string|number|!Stream|null|undefined>)} config The
        190 * configuration to use.
        191 * @return {!ServiceBuilder} A self reference.
        192 */
        193ServiceBuilder.prototype.setStdio = function(config) {
        194 this.stdio_ = config;
        195 return this;
        196};
        197
        198
        199/**
        200 * Defines the environment to start the server under. This settings will be
        201 * inherited by every browser session started by the server.
        202 * @param {!Object.<string, string>} env The environment to use.
        203 * @return {!ServiceBuilder} A self reference.
        204 */
        205ServiceBuilder.prototype.withEnvironment = function(env) {
        206 this.env_ = env;
        207 return this;
        208};
        209
        210
        211/**
        212 * Creates a new DriverService using this instance's current configuration.
        213 * @return {remote.DriverService} A new driver service using this instance's
        214 * current configuration.
        215 * @throws {Error} If the driver exectuable was not specified and a default
        216 * could not be found on the current PATH.
        217 */
        218ServiceBuilder.prototype.build = function() {
        219 var port = this.port_ || portprober.findFreePort();
        220 var args = this.args_.concat(); // Defensive copy.
        221
        222 return new remote.DriverService(this.exe_, {
        223 loopback: true,
        224 port: port,
        225 args: webdriver.promise.when(port, function(port) {
        226 return args.concat('--port=' + port);
        227 }),
        228 env: this.env_,
        229 stdio: this.stdio_
        230 });
        231};
        232
        233
        234/** @type {remote.DriverService} */
        235var defaultService = null;
        236
        237
        238/**
        239 * Sets the default service to use for new OperaDriver instances.
        240 * @param {!remote.DriverService} service The service to use.
        241 * @throws {Error} If the default service is currently running.
        242 */
        243function setDefaultService(service) {
        244 if (defaultService && defaultService.isRunning()) {
        245 throw Error(
        246 'The previously configured OperaDriver service is still running. ' +
        247 'You must shut it down before you may adjust its configuration.');
        248 }
        249 defaultService = service;
        250}
        251
        252
        253/**
        254 * Returns the default OperaDriver service. If such a service has not been
        255 * configured, one will be constructed using the default configuration for
        256 * a OperaDriver executable found on the system PATH.
        257 * @return {!remote.DriverService} The default OperaDriver service.
        258 */
        259function getDefaultService() {
        260 if (!defaultService) {
        261 defaultService = new ServiceBuilder().build();
        262 }
        263 return defaultService;
        264}
        265
        266
        267/**
        268 * @type {string}
        269 * @const
        270 */
        271var OPTIONS_CAPABILITY_KEY = 'chromeOptions';
        272
        273
        274/**
        275 * Class for managing {@link Driver OperaDriver} specific options.
        276 * @constructor
        277 * @extends {webdriver.Serializable}
        278 */
        279var Options = function() {
        280 webdriver.Serializable.call(this);
        281
        282 /** @private {!Array.<string>} */
        283 this.args_ = [];
        284
        285 /** @private {!Array.<(string|!Buffer)>} */
        286 this.extensions_ = [];
        287};
        288util.inherits(Options, webdriver.Serializable);
        289
        290
        291/**
        292 * Extracts the OperaDriver specific options from the given capabilities
        293 * object.
        294 * @param {!webdriver.Capabilities} capabilities The capabilities object.
        295 * @return {!Options} The OperaDriver options.
        296 */
        297Options.fromCapabilities = function(capabilities) {
        298 var options;
        299 var o = capabilities.get(OPTIONS_CAPABILITY_KEY);
        300 if (o instanceof Options) {
        301 options = o;
        302 } else if (o) {
        303 options.
        304 addArguments(o.args || []).
        305 addExtensions(o.extensions || []).
        306 setOperaBinaryPath(o.binary);
        307 } else {
        308 options = new Options;
        309 }
        310
        311 if (capabilities.has(webdriver.Capability.PROXY)) {
        312 options.setProxy(capabilities.get(webdriver.Capability.PROXY));
        313 }
        314
        315 if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {
        316 options.setLoggingPrefs(
        317 capabilities.get(webdriver.Capability.LOGGING_PREFS));
        318 }
        319
        320 return options;
        321};
        322
        323
        324/**
        325 * Add additional command line arguments to use when launching the Opera
        326 * browser. Each argument may be specified with or without the "--" prefix
        327 * (e.g. "--foo" and "foo"). Arguments with an associated value should be
        328 * delimited by an "=": "foo=bar".
        329 * @param {...(string|!Array.<string>)} var_args The arguments to add.
        330 * @return {!Options} A self reference.
        331 */
        332Options.prototype.addArguments = function(var_args) {
        333 this.args_ = this.args_.concat.apply(this.args_, arguments);
        334 return this;
        335};
        336
        337
        338/**
        339 * Add additional extensions to install when launching Opera. Each extension
        340 * should be specified as the path to the packed CRX file, or a Buffer for an
        341 * extension.
        342 * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The
        343 * extensions to add.
        344 * @return {!Options} A self reference.
        345 */
        346Options.prototype.addExtensions = function(var_args) {
        347 this.extensions_ = this.extensions_.concat.apply(
        348 this.extensions_, arguments);
        349 return this;
        350};
        351
        352
        353/**
        354 * Sets the path to the Opera binary to use. On Mac OS X, this path should
        355 * reference the actual Opera executable, not just the application binary. The
        356 * binary path be absolute or relative to the operadriver server executable, but
        357 * it must exist on the machine that will launch Opera.
        358 *
        359 * @param {string} path The path to the Opera binary to use.
        360 * @return {!Options} A self reference.
        361 */
        362Options.prototype.setOperaBinaryPath = function(path) {
        363 this.binary_ = path;
        364 return this;
        365};
        366
        367
        368/**
        369 * Sets the logging preferences for the new session.
        370 * @param {!webdriver.logging.Preferences} prefs The logging preferences.
        371 * @return {!Options} A self reference.
        372 */
        373Options.prototype.setLoggingPrefs = function(prefs) {
        374 this.logPrefs_ = prefs;
        375 return this;
        376};
        377
        378
        379/**
        380 * Sets the proxy settings for the new session.
        381 * @param {webdriver.ProxyConfig} proxy The proxy configuration to use.
        382 * @return {!Options} A self reference.
        383 */
        384Options.prototype.setProxy = function(proxy) {
        385 this.proxy_ = proxy;
        386 return this;
        387};
        388
        389
        390/**
        391 * Converts this options instance to a {@link webdriver.Capabilities} object.
        392 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge
        393 * these options into, if any.
        394 * @return {!webdriver.Capabilities} The capabilities.
        395 */
        396Options.prototype.toCapabilities = function(opt_capabilities) {
        397 var capabilities = opt_capabilities || webdriver.Capabilities.opera();
        398 capabilities.
        399 set(webdriver.Capability.PROXY, this.proxy_).
        400 set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_).
        401 set(OPTIONS_CAPABILITY_KEY, this);
        402 return capabilities;
        403};
        404
        405
        406/**
        407 * Converts this instance to its JSON wire protocol representation. Note this
        408 * function is an implementation not intended for general use.
        409 * @return {{args: !Array.<string>,
        410 * binary: (string|undefined),
        411 * detach: boolean,
        412 * extensions: !Array.<(string|!webdriver.promise.Promise.<string>))>,
        413 * localState: (Object|undefined),
        414 * logPath: (string|undefined),
        415 * prefs: (Object|undefined)}} The JSON wire protocol representation
        416 * of this instance.
        417 * @override
        418 */
        419Options.prototype.serialize = function() {
        420 var json = {
        421 args: this.args_,
        422 extensions: this.extensions_.map(function(extension) {
        423 if (Buffer.isBuffer(extension)) {
        424 return extension.toString('base64');
        425 }
        426 return webdriver.promise.checkedNodeCall(
        427 fs.readFile, extension, 'base64');
        428 })
        429 };
        430 if (this.binary_) {
        431 json.binary = this.binary_;
        432 }
        433 if (this.logFile_) {
        434 json.logPath = this.logFile_;
        435 }
        436 if (this.prefs_) {
        437 json.prefs = this.prefs_;
        438 }
        439
        440 return json;
        441};
        442
        443
        444/**
        445 * Creates a new WebDriver client for Opera.
        446 *
        447 * @param {(webdriver.Capabilities|Options)=} opt_config The configuration
        448 * options.
        449 * @param {remote.DriverService=} opt_service The session to use; will use
        450 * the {@link getDefaultService default service} by default.
        451 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
        452 * {@code null} to use the currently active flow.
        453 * @constructor
        454 * @extends {webdriver.WebDriver}
        455 */
        456var Driver = function(opt_config, opt_service, opt_flow) {
        457 var service = opt_service || getDefaultService();
        458 var executor = executors.createExecutor(service.start());
        459
        460 var capabilities =
        461 opt_config instanceof Options ? opt_config.toCapabilities() :
        462 (opt_config || webdriver.Capabilities.opera());
        463
        464 // On Linux, the OperaDriver does not look for Opera on the PATH, so we
        465 // must explicitly find it. See: operachromiumdriver #9.
        466 if (process.platform === 'linux') {
        467 var options = Options.fromCapabilities(capabilities);
        468 if (!options.binary_) {
        469 options.setOperaBinaryPath(io.findInPath('opera', true));
        470 }
        471 capabilities = options.toCapabilities(capabilities);
        472 }
        473
        474 var driver = webdriver.WebDriver.createSession(
        475 executor, capabilities, opt_flow);
        476
        477 webdriver.WebDriver.call(
        478 this, driver.getSession(), executor, driver.controlFlow());
        479};
        480util.inherits(Driver, webdriver.WebDriver);
        481
        482
        483/**
        484 * This function is a no-op as file detectors are not supported by this
        485 * implementation.
        486 * @override
        487 */
        488Driver.prototype.setFileDetector = function() {
        489};
        490
        491
        492// PUBLIC API
        493
        494
        495exports.Driver = Driver;
        496exports.Options = Options;
        497exports.ServiceBuilder = ServiceBuilder;
        498exports.getDefaultService = getDefaultService;
        499exports.setDefaultService = setDefaultService;
        \ No newline at end of file diff --git a/docs/source/phantomjs.js.src.html b/docs/source/phantomjs.js.src.html index 8988c7d..cc68bef 100644 --- a/docs/source/phantomjs.js.src.html +++ b/docs/source/phantomjs.js.src.html @@ -1 +1 @@ -phantomjs.js

        phantomjs.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16'use strict';
        17
        18var fs = require('fs'),
        19 util = require('util');
        20
        21var webdriver = require('./index'),
        22 LogLevel = webdriver.logging.LevelName,
        23 executors = require('./executors'),
        24 io = require('./io'),
        25 portprober = require('./net/portprober'),
        26 remote = require('./remote');
        27
        28
        29/**
        30 * Name of the PhantomJS executable.
        31 * @type {string}
        32 * @const
        33 */
        34var PHANTOMJS_EXE =
        35 process.platform === 'win32' ? 'phantomjs.exe' : 'phantomjs';
        36
        37
        38/**
        39 * Capability that designates the location of the PhantomJS executable to use.
        40 * @type {string}
        41 * @const
        42 */
        43var BINARY_PATH_CAPABILITY = 'phantomjs.binary.path';
        44
        45
        46/**
        47 * Capability that designates the CLI arguments to pass to PhantomJS.
        48 * @type {string}
        49 * @const
        50 */
        51var CLI_ARGS_CAPABILITY = 'phantomjs.cli.args';
        52
        53
        54/**
        55 * Default log file to use if one is not specified through CLI args.
        56 * @type {string}
        57 * @const
        58 */
        59var DEFAULT_LOG_FILE = 'phantomjsdriver.log';
        60
        61
        62/**
        63 * Finds the PhantomJS executable.
        64 * @param {string=} opt_exe Path to the executable to use.
        65 * @return {string} The located executable.
        66 * @throws {Error} If the executable cannot be found on the PATH, or if the
        67 * provided executable path does not exist.
        68 */
        69function findExecutable(opt_exe) {
        70 var exe = opt_exe || io.findInPath(PHANTOMJS_EXE, true);
        71 if (!exe) {
        72 throw Error(
        73 'The PhantomJS executable could not be found on the current PATH. ' +
        74 'Please download the latest version from ' +
        75 'http://phantomjs.org/download.html and ensure it can be found on ' +
        76 'your PATH. For more information, see ' +
        77 'https://github.com/ariya/phantomjs/wiki');
        78 }
        79 if (!fs.existsSync(exe)) {
        80 throw Error('File does not exist: ' + exe);
        81 }
        82 return exe;
        83}
        84
        85
        86/**
        87 * Maps WebDriver logging level name to those recognised by PhantomJS.
        88 * @type {!Object.<webdriver.logging.LevelName, string>}
        89 * @const
        90 */
        91var WEBDRIVER_TO_PHANTOMJS_LEVEL = (function() {
        92 var map = {};
        93 map[LogLevel.ALL] = map[LogLevel.DEBUG] = 'DEBUG';
        94 map[LogLevel.INFO] = 'INFO';
        95 map[LogLevel.WARNING] = 'WARN';
        96 map[LogLevel.SEVERE] = map[LogLevel.OFF] = 'ERROR';
        97 return map;
        98})();
        99
        100
        101/**
        102 * Creates a new PhantomJS WebDriver client.
        103 * @param {webdriver.Capabilities=} opt_capabilities The desired capabilities.
        104 * @return {!webdriver.WebDriver} A new WebDriver instance.
        105 */
        106function createDriver(opt_capabilities) {
        107 var capabilities = opt_capabilities || webdriver.Capabilities.phantomjs();
        108 var exe = findExecutable(capabilities.get(BINARY_PATH_CAPABILITY));
        109 var args = ['--webdriver-logfile=' + DEFAULT_LOG_FILE];
        110
        111 var logPrefs = capabilities.get(webdriver.Capability.LOGGING_PREFS);
        112 if (logPrefs && logPrefs[webdriver.logging.Type.DRIVER]) {
        113 var level = WEBDRIVER_TO_PHANTOMJS_LEVEL[
        114 logPrefs[webdriver.logging.Type.DRIVER]];
        115 if (level) {
        116 args.push('--webdriver-loglevel=' + level);
        117 }
        118 }
        119
        120 var proxy = capabilities.get(webdriver.Capability.PROXY);
        121 if (proxy) {
        122 switch (proxy.proxyType) {
        123 case 'manual':
        124 if (proxy.httpProxy) {
        125 args.push(
        126 '--proxy-type=http',
        127 '--proxy=http://' + proxy.httpProxy);
        128 }
        129 break;
        130 case 'pac':
        131 throw Error('PhantomJS does not support Proxy PAC files');
        132 case 'system':
        133 args.push('--proxy-type=system');
        134 break;
        135 case 'direct':
        136 args.push('--proxy-type=none');
        137 break;
        138 }
        139 }
        140 args = args.concat(capabilities.get(CLI_ARGS_CAPABILITY) || []);
        141
        142 var port = portprober.findFreePort();
        143 var service = new remote.DriverService(exe, {
        144 port: port,
        145 args: webdriver.promise.when(port, function(port) {
        146 args.push('--webdriver=' + port);
        147 return args;
        148 })
        149 });
        150
        151 var executor = executors.createExecutor(service.start());
        152 var driver = webdriver.WebDriver.createSession(executor, capabilities);
        153 var boundQuit = driver.quit.bind(driver);
        154 driver.quit = function() {
        155 return boundQuit().thenFinally(service.kill.bind(service));
        156 };
        157 return driver;
        158}
        159
        160
        161// PUBLIC API
        162
        163
        164exports.createDriver = createDriver;
        \ No newline at end of file +phantomjs.js

        phantomjs.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var fs = require('fs'),
        21 util = require('util');
        22
        23var webdriver = require('./index'),
        24 executors = require('./executors'),
        25 http = require('./http'),
        26 io = require('./io'),
        27 portprober = require('./net/portprober'),
        28 remote = require('./remote');
        29
        30
        31/**
        32 * Name of the PhantomJS executable.
        33 * @type {string}
        34 * @const
        35 */
        36var PHANTOMJS_EXE =
        37 process.platform === 'win32' ? 'phantomjs.exe' : 'phantomjs';
        38
        39
        40/**
        41 * Capability that designates the location of the PhantomJS executable to use.
        42 * @type {string}
        43 * @const
        44 */
        45var BINARY_PATH_CAPABILITY = 'phantomjs.binary.path';
        46
        47
        48/**
        49 * Capability that designates the CLI arguments to pass to PhantomJS.
        50 * @type {string}
        51 * @const
        52 */
        53var CLI_ARGS_CAPABILITY = 'phantomjs.cli.args';
        54
        55
        56/**
        57 * Default log file to use if one is not specified through CLI args.
        58 * @type {string}
        59 * @const
        60 */
        61var DEFAULT_LOG_FILE = 'phantomjsdriver.log';
        62
        63
        64/**
        65 * Custom command names supported by PhantomJS.
        66 * @enum {string}
        67 */
        68var Command = {
        69 EXECUTE_PHANTOM_SCRIPT: 'executePhantomScript'
        70};
        71
        72
        73/**
        74 * Finds the PhantomJS executable.
        75 * @param {string=} opt_exe Path to the executable to use.
        76 * @return {string} The located executable.
        77 * @throws {Error} If the executable cannot be found on the PATH, or if the
        78 * provided executable path does not exist.
        79 */
        80function findExecutable(opt_exe) {
        81 var exe = opt_exe || io.findInPath(PHANTOMJS_EXE, true);
        82 if (!exe) {
        83 throw Error(
        84 'The PhantomJS executable could not be found on the current PATH. ' +
        85 'Please download the latest version from ' +
        86 'http://phantomjs.org/download.html and ensure it can be found on ' +
        87 'your PATH. For more information, see ' +
        88 'https://github.com/ariya/phantomjs/wiki');
        89 }
        90 if (!fs.existsSync(exe)) {
        91 throw Error('File does not exist: ' + exe);
        92 }
        93 return exe;
        94}
        95
        96
        97/**
        98 * Maps WebDriver logging level name to those recognised by PhantomJS.
        99 * @type {!Object.<string, string>}
        100 * @const
        101 */
        102var WEBDRIVER_TO_PHANTOMJS_LEVEL = (function() {
        103 var map = {};
        104 map[webdriver.logging.Level.ALL.name] = 'DEBUG';
        105 map[webdriver.logging.Level.DEBUG.name] = 'DEBUG';
        106 map[webdriver.logging.Level.INFO.name] = 'INFO';
        107 map[webdriver.logging.Level.WARNING.name] = 'WARN';
        108 map[webdriver.logging.Level.SEVERE.name] = 'ERROR';
        109 return map;
        110})();
        111
        112
        113/**
        114 * Creates a command executor with support for PhantomJS' custom commands.
        115 * @param {!webdriver.promise.Promise<string>} url The server's URL.
        116 * @return {!webdriver.CommandExecutor} The new command executor.
        117 */
        118function createExecutor(url) {
        119 return new executors.DeferredExecutor(url.then(function(url) {
        120 var client = new http.HttpClient(url);
        121 var executor = new http.Executor(client);
        122
        123 executor.defineCommand(
        124 Command.EXECUTE_PHANTOM_SCRIPT,
        125 'POST', '/session/:sessionId/phantom/execute');
        126
        127 return executor;
        128 }));
        129}
        130
        131/**
        132 * Creates a new WebDriver client for PhantomJS.
        133 *
        134 * @param {webdriver.Capabilities=} opt_capabilities The desired capabilities.
        135 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or
        136 * {@code null} to use the currently active flow.
        137 * @constructor
        138 * @extends {webdriver.WebDriver}
        139 */
        140var Driver = function(opt_capabilities, opt_flow) {
        141 var capabilities = opt_capabilities || webdriver.Capabilities.phantomjs();
        142 var exe = findExecutable(capabilities.get(BINARY_PATH_CAPABILITY));
        143 var args = ['--webdriver-logfile=' + DEFAULT_LOG_FILE];
        144
        145 var logPrefs = capabilities.get(webdriver.Capability.LOGGING_PREFS);
        146 if (logPrefs instanceof webdriver.logging.Preferences) {
        147 logPrefs = logPrefs.toJSON();
        148 }
        149
        150 if (logPrefs && logPrefs[webdriver.logging.Type.DRIVER]) {
        151 var level = WEBDRIVER_TO_PHANTOMJS_LEVEL[
        152 logPrefs[webdriver.logging.Type.DRIVER]];
        153 if (level) {
        154 args.push('--webdriver-loglevel=' + level);
        155 }
        156 }
        157
        158 var proxy = capabilities.get(webdriver.Capability.PROXY);
        159 if (proxy) {
        160 switch (proxy.proxyType) {
        161 case 'manual':
        162 if (proxy.httpProxy) {
        163 args.push(
        164 '--proxy-type=http',
        165 '--proxy=http://' + proxy.httpProxy);
        166 }
        167 break;
        168 case 'pac':
        169 throw Error('PhantomJS does not support Proxy PAC files');
        170 case 'system':
        171 args.push('--proxy-type=system');
        172 break;
        173 case 'direct':
        174 args.push('--proxy-type=none');
        175 break;
        176 }
        177 }
        178 args = args.concat(capabilities.get(CLI_ARGS_CAPABILITY) || []);
        179
        180 var port = portprober.findFreePort();
        181 var service = new remote.DriverService(exe, {
        182 port: port,
        183 args: webdriver.promise.when(port, function(port) {
        184 args.push('--webdriver=' + port);
        185 return args;
        186 })
        187 });
        188
        189 var executor = createExecutor(service.start());
        190 var driver = webdriver.WebDriver.createSession(
        191 executor, capabilities, opt_flow);
        192
        193 webdriver.WebDriver.call(
        194 this, driver.getSession(), executor, driver.controlFlow());
        195
        196 var boundQuit = this.quit.bind(this);
        197
        198 /** @override */
        199 this.quit = function() {
        200 return boundQuit().thenFinally(service.kill.bind(service));
        201 };
        202};
        203util.inherits(Driver, webdriver.WebDriver);
        204
        205
        206/**
        207 * This function is a no-op as file detectors are not supported by this
        208 * implementation.
        209 * @override
        210 */
        211Driver.prototype.setFileDetector = function() {
        212};
        213
        214
        215/**
        216 * Executes a PhantomJS fragment. This method is similar to
        217 * {@link #executeScript}, except it exposes the
        218 * <a href="http://phantomjs.org/api/">PhantomJS API</a> to the injected
        219 * script.
        220 *
        221 * <p>The injected script will execute in the context of PhantomJS's
        222 * {@code page} variable. If a page has not been loaded before calling this
        223 * method, one will be created.</p>
        224 *
        225 * <p>Be sure to wrap callback definitions in a try/catch block, as failures
        226 * may cause future WebDriver calls to fail.</p>
        227 *
        228 * <p>Certain callbacks are used by GhostDriver (the PhantomJS WebDriver
        229 * implementation) and overriding these may cause the script to fail. It is
        230 * recommended that you check for existing callbacks before defining your own.
        231 * </p>
        232 *
        233 * As with {@link #executeScript}, the injected script may be defined as
        234 * a string for an anonymous function body (e.g. "return 123;"), or as a
        235 * function. If a function is provided, it will be decompiled to its original
        236 * source. Note that injecting functions is provided as a convenience to
        237 * simplify defining complex scripts. Care must be taken that the function
        238 * only references variables that will be defined in the page's scope and
        239 * that the function does not override {@code Function.prototype.toString}
        240 * (overriding toString() will interfere with how the function is
        241 * decompiled.
        242 *
        243 * @param {(string|!Function)} script The script to execute.
        244 * @param {...*} var_args The arguments to pass to the script.
        245 * @return {!webdriver.promise.Promise<T>} A promise that resolve to the
        246 * script's return value.
        247 * @template T
        248 */
        249Driver.prototype.executePhantomJS = function(script, args) {
        250 if (typeof script === 'function') {
        251 script = 'return (' + script + ').apply(this, arguments);';
        252 }
        253 var args = arguments.length > 1
        254 ? Array.prototype.slice.call(arguments, 1) : [];
        255 return this.schedule(
        256 new webdriver.Command(Command.EXECUTE_PHANTOM_SCRIPT)
        257 .setParameter('script', script)
        258 .setParameter('args', args),
        259 'Driver.executePhantomJS()');
        260};
        261
        262
        263// PUBLIC API
        264
        265exports.Driver = Driver;
        \ No newline at end of file diff --git a/docs/source/proxy.js.src.html b/docs/source/proxy.js.src.html index 70d3c1d..3424f55 100644 --- a/docs/source/proxy.js.src.html +++ b/docs/source/proxy.js.src.html @@ -1 +1 @@ -proxy.js

        proxy.js

        1// Copyright 2013 Selenium committers
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines functions for configuring a webdriver proxy:
        17 * <pre><code>
        18 * var webdriver = require('selenium-webdriver'),
        19 * proxy = require('selenium-webdriver/proxy');
        20 *
        21 * var driver = new webdriver.Builder()
        22 * .withCapabilities(webdriver.Capabilities.chrome())
        23 * .setProxy(proxy.manual({http: 'host:1234'}))
        24 * .build();
        25 * </code></pre>
        26 */
        27
        28'use strict';
        29
        30var util = require('util');
        31
        32
        33/**
        34 * Proxy configuration object, as defined by the WebDriver wire protocol.
        35 * @typedef {(
        36 * {proxyType: string}|
        37 * {proxyType: string,
        38 * proxyAutoconfigUrl: string}|
        39 * {proxyType: string,
        40 * ftpProxy: string,
        41 * httpProxy: string,
        42 * sslProxy: string,
        43 * noProxy: string})}
        44 */
        45var ProxyConfig;
        46
        47
        48
        49// PUBLIC API
        50
        51
        52/**
        53 * Configures WebDriver to bypass all browser proxies.
        54 * @return {!ProxyConfig} A new proxy configuration object.
        55 */
        56exports.direct = function() {
        57 return {proxyType: 'direct'};
        58};
        59
        60
        61/**
        62 * Manually configures the browser proxy. The following options are
        63 * supported:
        64 * <ul>
        65 * <li>{@code ftp}: Proxy host to use for FTP requests
        66 * <li>{@code http}: Proxy host to use for HTTP requests
        67 * <li>{@code https}: Proxy host to use for HTTPS requests
        68 * <li>{@code bypass}: A list of hosts requests should directly connect to,
        69 * bypassing any other proxies for that request. May be specified as a
        70 * comma separated string, or a list of strings.
        71 * </ul>
        72 *
        73 * Behavior is undefined for FTP, HTTP, and HTTPS requests if the
        74 * corresponding key is omitted from the configuration options.
        75 *
        76 * @param {{ftp: (string|undefined),
        77 * http: (string|undefined),
        78 * https: (string|undefined),
        79 * bypass: (string|!Array.<string>|undefined)}} options Proxy
        80 * configuration options.
        81 * @return {!ProxyConfig} A new proxy configuration object.
        82 */
        83exports.manual = function(options) {
        84 return {
        85 proxyType: 'manual',
        86 ftpProxy: options.ftp,
        87 httpProxy: options.http,
        88 sslProxy: options.https,
        89 noProxy: util.isArray(options.bypass) ?
        90 options.bypass.join(',') : options.bypass
        91 };
        92};
        93
        94
        95/**
        96 * Configures WebDriver to configure the browser proxy using the PAC file at
        97 * the given URL.
        98 * @param {string} url URL for the PAC proxy to use.
        99 * @return {!ProxyConfig} A new proxy configuration object.
        100 */
        101exports.pac = function(url) {
        102 return {
        103 proxyType: 'pac',
        104 proxyAutoconfigUrl: url
        105 };
        106};
        107
        108
        109/**
        110 * Configures WebDriver to use the current system's proxy.
        111 * @return {!ProxyConfig} A new proxy configuration object.
        112 */
        113exports.system = function() {
        114 return {proxyType: 'system'};
        115};
        \ No newline at end of file +proxy.js

        proxy.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines functions for configuring a webdriver proxy:
        20 *
        21 * var webdriver = require('selenium-webdriver'),
        22 * proxy = require('selenium-webdriver/proxy');
        23 *
        24 * var driver = new webdriver.Builder()
        25 * .withCapabilities(webdriver.Capabilities.chrome())
        26 * .setProxy(proxy.manual({http: 'host:1234'}))
        27 * .build();
        28 */
        29
        30'use strict';
        31
        32var util = require('util');
        33
        34
        35
        36// PUBLIC API
        37
        38
        39/**
        40 * Configures WebDriver to bypass all browser proxies.
        41 * @return {!webdriver.ProxyConfig} A new proxy configuration object.
        42 */
        43exports.direct = function() {
        44 return {proxyType: 'direct'};
        45};
        46
        47
        48/**
        49 * Manually configures the browser proxy. The following options are
        50 * supported:
        51 *
        52 * - `ftp`: Proxy host to use for FTP requests
        53 * - `http`: Proxy host to use for HTTP requests
        54 * - `https`: Proxy host to use for HTTPS requests
        55 * - `bypass`: A list of hosts requests should directly connect to,
        56 * bypassing any other proxies for that request. May be specified as a
        57 * comma separated string, or a list of strings.
        58 *
        59 * Behavior is undefined for FTP, HTTP, and HTTPS requests if the
        60 * corresponding key is omitted from the configuration options.
        61 *
        62 * @param {{ftp: (string|undefined),
        63 * http: (string|undefined),
        64 * https: (string|undefined),
        65 * bypass: (string|!Array.<string>|undefined)}} options Proxy
        66 * configuration options.
        67 * @return {!webdriver.ProxyConfig} A new proxy configuration object.
        68 */
        69exports.manual = function(options) {
        70 return {
        71 proxyType: 'manual',
        72 ftpProxy: options.ftp,
        73 httpProxy: options.http,
        74 sslProxy: options.https,
        75 noProxy: util.isArray(options.bypass) ?
        76 options.bypass.join(',') : options.bypass
        77 };
        78};
        79
        80
        81/**
        82 * Configures WebDriver to configure the browser proxy using the PAC file at
        83 * the given URL.
        84 * @param {string} url URL for the PAC proxy to use.
        85 * @return {!webdriver.ProxyConfig} A new proxy configuration object.
        86 */
        87exports.pac = function(url) {
        88 return {
        89 proxyType: 'pac',
        90 proxyAutoconfigUrl: url
        91 };
        92};
        93
        94
        95/**
        96 * Configures WebDriver to use the current system's proxy.
        97 * @return {!webdriver.ProxyConfig} A new proxy configuration object.
        98 */
        99exports.system = function() {
        100 return {proxyType: 'system'};
        101};
        \ No newline at end of file diff --git a/docs/source/remote/index.js.src.html b/docs/source/remote/index.js.src.html index a6a3b48..dabee06 100644 --- a/docs/source/remote/index.js.src.html +++ b/docs/source/remote/index.js.src.html @@ -1 +1 @@ -index.js

        remote/index.js

        1// Copyright 2013 Software Freedom Conservancy
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15'use strict';
        16
        17var spawn = require('child_process').spawn,
        18 os = require('os'),
        19 path = require('path'),
        20 url = require('url'),
        21 util = require('util');
        22
        23var promise = require('../').promise,
        24 httpUtil = require('../http/util'),
        25 net = require('../net'),
        26 portprober = require('../net/portprober');
        27
        28
        29
        30/**
        31 * Configuration options for a DriverService instance.
        32 * <ul>
        33 * <li>{@code port} - The port to start the server on (must be > 0). If the
        34 * port is provided as a promise, the service will wait for the promise to
        35 * resolve before starting.
        36 * <li>{@code args} - The arguments to pass to the service. If a promise is
        37 * provided, the service will wait for it to resolve before starting.
        38 * <li>{@code path} - The base path on the server for the WebDriver wire
        39 * protocol (e.g. '/wd/hub'). Defaults to '/'.
        40 * <li>{@code env} - The environment variables that should be visible to the
        41 * server process. Defaults to inheriting the current process's
        42 * environment.
        43 * <li>{@code stdio} - IO configuration for the spawned server process. For
        44 * more information, refer to the documentation of
        45 * {@code child_process.spawn}.
        46 * </ul>
        47 *
        48 * @typedef {{
        49 * port: (number|!webdriver.promise.Promise.<number>),
        50 * args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
        51 * path: (string|undefined),
        52 * env: (!Object.<string, string>|undefined),
        53 * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
        54 * }}
        55 */
        56var ServiceOptions;
        57
        58
        59/**
        60 * Manages the life and death of a native executable WebDriver server.
        61 *
        62 * <p>It is expected that the driver server implements the
        63 * <a href="http://code.google.com/p/selenium/wiki/JsonWireProtocol">WebDriver
        64 * Wire Protocol</a>. Furthermore, the managed server should support multiple
        65 * concurrent sessions, so that this class may be reused for multiple clients.
        66 *
        67 * @param {string} executable Path to the executable to run.
        68 * @param {!ServiceOptions} options Configuration options for the service.
        69 * @constructor
        70 */
        71function DriverService(executable, options) {
        72
        73 /** @private {string} */
        74 this.executable_ = executable;
        75
        76 /** @private {(number|!webdriver.promise.Promise.<number>)} */
        77 this.port_ = options.port;
        78
        79 /**
        80 * @private {!(Array.<string>|webdriver.promise.Promise.<!Array.<string>>)}
        81 */
        82 this.args_ = options.args;
        83
        84 /** @private {string} */
        85 this.path_ = options.path || '/';
        86
        87 /** @private {!Object.<string, string>} */
        88 this.env_ = options.env || process.env;
        89
        90 /** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
        91 this.stdio_ = options.stdio || 'ignore';
        92}
        93
        94
        95/**
        96 * The default amount of time, in milliseconds, to wait for the server to
        97 * start.
        98 * @type {number}
        99 */
        100DriverService.DEFAULT_START_TIMEOUT_MS = 30 * 1000;
        101
        102
        103/** @private {child_process.ChildProcess} */
        104DriverService.prototype.process_ = null;
        105
        106
        107/**
        108 * Promise that resolves to the server's address or null if the server has not
        109 * been started.
        110 * @private {webdriver.promise.Promise.<string>}
        111 */
        112DriverService.prototype.address_ = null;
        113
        114
        115/**
        116 * Promise that tracks the status of shutting down the server, or null if the
        117 * server is not currently shutting down.
        118 * @private {webdriver.promise.Promise}
        119 */
        120DriverService.prototype.shutdownHook_ = null;
        121
        122
        123/**
        124 * @return {!webdriver.promise.Promise.<string>} A promise that resolves to
        125 * the server's address.
        126 * @throws {Error} If the server has not been started.
        127 */
        128DriverService.prototype.address = function() {
        129 if (this.address_) {
        130 return this.address_;
        131 }
        132 throw Error('Server has not been started.');
        133};
        134
        135
        136/**
        137 * @return {boolean} Whether the underlying service process is running.
        138 */
        139DriverService.prototype.isRunning = function() {
        140 return !!this.address_;
        141};
        142
        143
        144/**
        145 * Starts the server if it is not already running.
        146 * @param {number=} opt_timeoutMs How long to wait, in milliseconds, for the
        147 * server to start accepting requests. Defaults to 30 seconds.
        148 * @return {!webdriver.promise.Promise.<string>} A promise that will resolve
        149 * to the server's base URL when it has started accepting requests. If the
        150 * timeout expires before the server has started, the promise will be
        151 * rejected.
        152 */
        153DriverService.prototype.start = function(opt_timeoutMs) {
        154 if (this.address_) {
        155 return this.address_;
        156 }
        157
        158 var timeout = opt_timeoutMs || DriverService.DEFAULT_START_TIMEOUT_MS;
        159
        160 var self = this;
        161 this.address_ = promise.defer();
        162 this.address_.fulfill(promise.when(this.port_, function(port) {
        163 if (port <= 0) {
        164 throw Error('Port must be > 0: ' + port);
        165 }
        166 return promise.when(self.args_, function(args) {
        167 self.process_ = spawn(self.executable_, args, {
        168 env: self.env_,
        169 stdio: self.stdio_
        170 }).once('exit', onServerExit);
        171
        172 // This process should not wait on the spawned child, however, we do
        173 // want to ensure the child is killed when this process exits.
        174 self.process_.unref();
        175 process.once('exit', killServer);
        176
        177 var serverUrl = url.format({
        178 protocol: 'http',
        179 hostname: net.getAddress() || net.getLoopbackAddress(),
        180 port: port,
        181 pathname: self.path_
        182 });
        183
        184 return httpUtil.waitForServer(serverUrl, timeout).then(function() {
        185 return serverUrl;
        186 });
        187 });
        188 }));
        189
        190 return this.address_;
        191
        192 function onServerExit(code, signal) {
        193 self.address_.reject(code == null ?
        194 Error('Server was killed with ' + signal) :
        195 Error('Server exited with ' + code));
        196
        197 if (self.shutdownHook_) {
        198 self.shutdownHook_.fulfill();
        199 }
        200
        201 self.shutdownHook_ = null;
        202 self.address_ = null;
        203 self.process_ = null;
        204 process.removeListener('exit', killServer);
        205 }
        206
        207 function killServer() {
        208 process.removeListener('exit', killServer);
        209 self.process_ && self.process_.kill('SIGTERM');
        210 }
        211};
        212
        213
        214/**
        215 * Stops the service if it is not currently running. This function will kill
        216 * the server immediately. To synchronize with the active control flow, use
        217 * {@link #stop()}.
        218 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        219 * the server has been stopped.
        220 */
        221DriverService.prototype.kill = function() {
        222 if (!this.address_) {
        223 return promise.fulfilled(); // Not currently running.
        224 }
        225
        226 if (!this.shutdownHook_) {
        227 // No process: still starting; wait on address.
        228 // Otherwise, kill the process now. Exit handler will resolve the
        229 // shutdown hook.
        230 if (this.process_) {
        231 this.shutdownHook_ = promise.defer();
        232 this.process_.kill('SIGTERM');
        233 } else {
        234 var self = this;
        235 this.shutdownHook_ = this.address_.thenFinally(function() {
        236 self.process_ && self.process_.kill('SIGTERM');
        237 });
        238 }
        239 }
        240
        241 return this.shutdownHook_;
        242};
        243
        244
        245/**
        246 * Schedules a task in the current control flow to stop the server if it is
        247 * currently running.
        248 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        249 * the server has been stopped.
        250 */
        251DriverService.prototype.stop = function() {
        252 return promise.controlFlow().execute(this.kill.bind(this));
        253};
        254
        255
        256
        257/**
        258 * Manages the life and death of the Selenium standalone server. The server
        259 * may be obtained from http://selenium-release.storage.googleapis.com/index.html.
        260 * @param {string} jar Path to the Selenium server jar.
        261 * @param {!SeleniumServer.Options} options Configuration options for the
        262 * server.
        263 * @throws {Error} If an invalid port is specified.
        264 * @constructor
        265 * @extends {DriverService}
        266 */
        267function SeleniumServer(jar, options) {
        268 if (options.port < 0)
        269 throw Error('Port must be >= 0: ' + options.port);
        270
        271 var port = options.port || portprober.findFreePort();
        272 var args = promise.when(options.jvmArgs || [], function(jvmArgs) {
        273 return promise.when(options.args || [], function(args) {
        274 return promise.when(port, function(port) {
        275 return jvmArgs.concat(['-jar', jar, '-port', port]).concat(args);
        276 });
        277 });
        278 });
        279
        280 DriverService.call(this, 'java', {
        281 port: port,
        282 args: args,
        283 path: '/wd/hub',
        284 env: options.env,
        285 stdio: options.stdio
        286 });
        287}
        288util.inherits(SeleniumServer, DriverService);
        289
        290
        291/**
        292 * Options for the Selenium server:
        293 * <ul>
        294 * <li>{@code port} - The port to start the server on (must be > 0). If the
        295 * port is provided as a promise, the service will wait for the promise to
        296 * resolve before starting.
        297 * <li>{@code args} - The arguments to pass to the service. If a promise is
        298 * provided, the service will wait for it to resolve before starting.
        299 * <li>{@code jvmArgs} - The arguments to pass to the JVM. If a promise is
        300 * provided, the service will wait for it to resolve before starting.
        301 * <li>{@code env} - The environment variables that should be visible to the
        302 * server process. Defaults to inheriting the current process's
        303 * environment.
        304 * <li>{@code stdio} - IO configuration for the spawned server process. For
        305 * more information, refer to the documentation of
        306 * {@code child_process.spawn}.
        307 * </ul>
        308 *
        309 * @typedef {{
        310 * port: (number|!webdriver.promise.Promise.<number>),
        311 * args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
        312 * jvmArgs: (!Array.<string>|
        313 * !webdriver.promise.Promise.<!Array.<string>>|
        314 * undefined),
        315 * env: (!Object.<string, string>|undefined),
        316 * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
        317 * }}
        318 */
        319SeleniumServer.Options;
        320
        321
        322// PUBLIC API
        323
        324exports.DriverService = DriverService;
        325exports.SeleniumServer = SeleniumServer;
        \ No newline at end of file +index.js

        remote/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18'use strict';
        19
        20var AdmZip = require('adm-zip'),
        21 fs = require('fs'),
        22 path = require('path'),
        23 url = require('url'),
        24 util = require('util');
        25
        26var _base = require('../_base'),
        27 webdriver = require('../'),
        28 promise = require('../').promise,
        29 httpUtil = require('../http/util'),
        30 exec = require('../io/exec'),
        31 net = require('../net'),
        32 portprober = require('../net/portprober');
        33
        34
        35
        36/**
        37 * Configuration options for a DriverService instance.
        38 *
        39 * - `loopback` - Whether the service should only be accessed on this host's
        40 * loopback address.
        41 * - `port` - The port to start the server on (must be > 0). If the port is
        42 * provided as a promise, the service will wait for the promise to resolve
        43 * before starting.
        44 * - `args` - The arguments to pass to the service. If a promise is provided,
        45 * the service will wait for it to resolve before starting.
        46 * - `path` - The base path on the server for the WebDriver wire protocol
        47 * (e.g. '/wd/hub'). Defaults to '/'.
        48 * - `env` - The environment variables that should be visible to the server
        49 * process. Defaults to inheriting the current process's environment.
        50 * - `stdio` - IO configuration for the spawned server process. For more
        51 * information, refer to the documentation of `child_process.spawn`.
        52 *
        53 * @typedef {{
        54 * port: (number|!webdriver.promise.Promise.<number>),
        55 * args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
        56 * path: (string|undefined),
        57 * env: (!Object.<string, string>|undefined),
        58 * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
        59 * }}
        60 */
        61var ServiceOptions;
        62
        63
        64/**
        65 * Manages the life and death of a native executable WebDriver server.
        66 *
        67 * It is expected that the driver server implements the
        68 * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol.
        69 * Furthermore, the managed server should support multiple concurrent sessions,
        70 * so that this class may be reused for multiple clients.
        71 *
        72 * @param {string} executable Path to the executable to run.
        73 * @param {!ServiceOptions} options Configuration options for the service.
        74 * @constructor
        75 */
        76function DriverService(executable, options) {
        77
        78 /** @private {string} */
        79 this.executable_ = executable;
        80
        81 /** @private {boolean} */
        82 this.loopbackOnly_ = !!options.loopback;
        83
        84 /** @private {(number|!webdriver.promise.Promise.<number>)} */
        85 this.port_ = options.port;
        86
        87 /**
        88 * @private {!(Array.<string>|webdriver.promise.Promise.<!Array.<string>>)}
        89 */
        90 this.args_ = options.args;
        91
        92 /** @private {string} */
        93 this.path_ = options.path || '/';
        94
        95 /** @private {!Object.<string, string>} */
        96 this.env_ = options.env || process.env;
        97
        98 /** @private {(string|!Array.<string|number|!Stream|null|undefined>)} */
        99 this.stdio_ = options.stdio || 'ignore';
        100
        101 /**
        102 * A promise for the managed subprocess, or null if the server has not been
        103 * started yet. This promise will never be rejected.
        104 * @private {promise.Promise.<!exec.Command>}
        105 */
        106 this.command_ = null;
        107
        108 /**
        109 * Promise that resolves to the server's address or null if the server has
        110 * not been started. This promise will be rejected if the server terminates
        111 * before it starts accepting WebDriver requests.
        112 * @private {promise.Promise.<string>}
        113 */
        114 this.address_ = null;
        115}
        116
        117
        118/**
        119 * The default amount of time, in milliseconds, to wait for the server to
        120 * start.
        121 * @type {number}
        122 */
        123DriverService.DEFAULT_START_TIMEOUT_MS = 30 * 1000;
        124
        125
        126/**
        127 * @return {!webdriver.promise.Promise.<string>} A promise that resolves to
        128 * the server's address.
        129 * @throws {Error} If the server has not been started.
        130 */
        131DriverService.prototype.address = function() {
        132 if (this.address_) {
        133 return this.address_;
        134 }
        135 throw Error('Server has not been started.');
        136};
        137
        138
        139/**
        140 * Returns whether the underlying process is still running. This does not take
        141 * into account whether the process is in the process of shutting down.
        142 * @return {boolean} Whether the underlying service process is running.
        143 */
        144DriverService.prototype.isRunning = function() {
        145 return !!this.address_;
        146};
        147
        148
        149/**
        150 * Starts the server if it is not already running.
        151 * @param {number=} opt_timeoutMs How long to wait, in milliseconds, for the
        152 * server to start accepting requests. Defaults to 30 seconds.
        153 * @return {!promise.Promise.<string>} A promise that will resolve
        154 * to the server's base URL when it has started accepting requests. If the
        155 * timeout expires before the server has started, the promise will be
        156 * rejected.
        157 */
        158DriverService.prototype.start = function(opt_timeoutMs) {
        159 if (this.address_) {
        160 return this.address_;
        161 }
        162
        163 var timeout = opt_timeoutMs || DriverService.DEFAULT_START_TIMEOUT_MS;
        164
        165 var self = this;
        166 this.command_ = promise.defer();
        167 this.address_ = promise.defer();
        168 this.address_.fulfill(promise.when(this.port_, function(port) {
        169 if (port <= 0) {
        170 throw Error('Port must be > 0: ' + port);
        171 }
        172 return promise.when(self.args_, function(args) {
        173 var command = exec(self.executable_, {
        174 args: args,
        175 env: self.env_,
        176 stdio: self.stdio_
        177 });
        178
        179 self.command_.fulfill(command);
        180
        181 var earlyTermination = command.result().then(function(result) {
        182 var error = result.code == null ?
        183 Error('Server was killed with ' + result.signal) :
        184 Error('Server terminated early with status ' + result.code);
        185 self.address_.reject(error);
        186 self.address_ = null;
        187 self.command_ = null;
        188 throw error;
        189 });
        190
        191 var serverUrl = url.format({
        192 protocol: 'http',
        193 hostname: !self.loopbackOnly_ && net.getAddress() ||
        194 net.getLoopbackAddress(),
        195 port: port,
        196 pathname: self.path_
        197 });
        198
        199 return new promise.Promise(function(fulfill, reject) {
        200 var ready = httpUtil.waitForServer(serverUrl, timeout)
        201 .then(fulfill, reject);
        202 earlyTermination.thenCatch(function(e) {
        203 ready.cancel(e);
        204 reject(Error(e.message));
        205 });
        206 }).then(function() {
        207 return serverUrl;
        208 });
        209 });
        210 }));
        211
        212 return this.address_;
        213};
        214
        215
        216/**
        217 * Stops the service if it is not currently running. This function will kill
        218 * the server immediately. To synchronize with the active control flow, use
        219 * {@link #stop()}.
        220 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        221 * the server has been stopped.
        222 */
        223DriverService.prototype.kill = function() {
        224 if (!this.address_ || !this.command_) {
        225 return promise.fulfilled(); // Not currently running.
        226 }
        227 return this.command_.then(function(command) {
        228 command.kill('SIGTERM');
        229 });
        230};
        231
        232
        233/**
        234 * Schedules a task in the current control flow to stop the server if it is
        235 * currently running.
        236 * @return {!webdriver.promise.Promise} A promise that will be resolved when
        237 * the server has been stopped.
        238 */
        239DriverService.prototype.stop = function() {
        240 return promise.controlFlow().execute(this.kill.bind(this));
        241};
        242
        243
        244
        245/**
        246 * Manages the life and death of the
        247 * <a href="http://selenium-release.storage.googleapis.com/index.html">
        248 * standalone Selenium server</a>.
        249 *
        250 * @param {string} jar Path to the Selenium server jar.
        251 * @param {SeleniumServer.Options=} opt_options Configuration options for the
        252 * server.
        253 * @throws {Error} If the path to the Selenium jar is not specified or if an
        254 * invalid port is specified.
        255 * @constructor
        256 * @extends {DriverService}
        257 */
        258function SeleniumServer(jar, opt_options) {
        259 if (!jar) {
        260 throw Error('Path to the Selenium jar not specified');
        261 }
        262
        263 var options = opt_options || {};
        264
        265 if (options.port < 0) {
        266 throw Error('Port must be >= 0: ' + options.port);
        267 }
        268
        269 var port = options.port || portprober.findFreePort();
        270 var args = promise.when(options.jvmArgs || [], function(jvmArgs) {
        271 return promise.when(options.args || [], function(args) {
        272 return promise.when(port, function(port) {
        273 return jvmArgs.concat(['-jar', jar, '-port', port]).concat(args);
        274 });
        275 });
        276 });
        277
        278 DriverService.call(this, 'java', {
        279 port: port,
        280 args: args,
        281 path: '/wd/hub',
        282 env: options.env,
        283 stdio: options.stdio
        284 });
        285}
        286util.inherits(SeleniumServer, DriverService);
        287
        288
        289/**
        290 * Options for the Selenium server:
        291 *
        292 * - `port` - The port to start the server on (must be > 0). If the port is
        293 * provided as a promise, the service will wait for the promise to resolve
        294 * before starting.
        295 * - `args` - The arguments to pass to the service. If a promise is provided,
        296 * the service will wait for it to resolve before starting.
        297 * - `jvmArgs` - The arguments to pass to the JVM. If a promise is provided,
        298 * the service will wait for it to resolve before starting.
        299 * - `env` - The environment variables that should be visible to the server
        300 * process. Defaults to inheriting the current process's environment.
        301 * - `stdio` - IO configuration for the spawned server process. For more
        302 * information, refer to the documentation of `child_process.spawn`.
        303 *
        304 * @typedef {{
        305 * port: (number|!webdriver.promise.Promise.<number>),
        306 * args: !(Array.<string>|webdriver.promise.Promise.<!Array.<string>>),
        307 * jvmArgs: (!Array.<string>|
        308 * !webdriver.promise.Promise.<!Array.<string>>|
        309 * undefined),
        310 * env: (!Object.<string, string>|undefined),
        311 * stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined)
        312 * }}
        313 */
        314SeleniumServer.Options;
        315
        316
        317
        318/**
        319 * A {@link webdriver.FileDetector} that may be used when running
        320 * against a remote
        321 * [Selenium server](http://selenium-release.storage.googleapis.com/index.html).
        322 *
        323 * When a file path on the local machine running this script is entered with
        324 * {@link webdriver.WebElement#sendKeys WebElement#sendKeys}, this file detector
        325 * will transfer the specified file to the Selenium server's host; the sendKeys
        326 * command will be updated to use the transfered file's path.
        327 *
        328 * __Note:__ This class depends on a non-standard command supported on the
        329 * Java Selenium server. The file detector will fail if used with a server that
        330 * only supports standard WebDriver commands (such as the ChromeDriver).
        331 *
        332 * @constructor
        333 * @extends {webdriver.FileDetector}
        334 * @final
        335 */
        336var FileDetector = function() {};
        337util.inherits(webdriver.FileDetector, FileDetector);
        338
        339
        340/** @override */
        341FileDetector.prototype.handleFile = function(driver, filePath) {
        342 return promise.checkedNodeCall(fs.stat, filePath).then(function(stats) {
        343 if (stats.isDirectory()) {
        344 throw TypeError('Uploading directories is not supported: ' + filePath);
        345 }
        346
        347 var zip = new AdmZip();
        348 zip.addLocalFile(filePath);
        349
        350 var command = new webdriver.Command(webdriver.CommandName.UPLOAD_FILE)
        351 .setParameter('file', zip.toBuffer().toString('base64'));
        352 return driver.schedule(command,
        353 'remote.FileDetector.handleFile(' + filePath + ')');
        354 }, function(err) {
        355 if (err.code === 'ENOENT') {
        356 return filePath; // Not a file; return original input.
        357 }
        358 throw err;
        359 });
        360};
        361
        362// PUBLIC API
        363
        364exports.DriverService = DriverService;
        365exports.FileDetector = FileDetector;
        366exports.SeleniumServer = SeleniumServer;
        \ No newline at end of file diff --git a/docs/source/safari.js.src.html b/docs/source/safari.js.src.html new file mode 100644 index 0000000..60698f3 --- /dev/null +++ b/docs/source/safari.js.src.html @@ -0,0 +1 @@ +safari.js

        safari.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a WebDriver client for Safari. Before using this
        20 * module, you must install the
        21 * [latest version](http://selenium-release.storage.googleapis.com/index.html)
        22 * of the SafariDriver browser extension; using Safari for normal browsing is
        23 * not recommended once the extension has been installed. You can, and should,
        24 * disable the extension when the browser is not being used with WebDriver.
        25 */
        26
        27'use strict';
        28
        29var events = require('events');
        30var fs = require('fs');
        31var http = require('http');
        32var path = require('path');
        33var url = require('url');
        34var util = require('util');
        35var ws = require('ws');
        36
        37var webdriver = require('./');
        38var promise = webdriver.promise;
        39var _base = require('./_base');
        40var io = require('./io');
        41var exec = require('./io/exec');
        42var portprober = require('./net/portprober');
        43
        44
        45/** @const */
        46var CLIENT_PATH = _base.isDevMode()
        47 ? path.join(__dirname,
        48 '../../../build/javascript/safari-driver/client.js')
        49 : path.join(__dirname, 'lib/safari/client.js');
        50
        51
        52/** @const */
        53var LIBRARY_DIR = process.platform === 'darwin'
        54 ? path.join('/Users', process.env['USER'], 'Library/Safari')
        55 : path.join(process.env['APPDATA'], 'Apple Computer', 'Safari');
        56
        57
        58/** @const */
        59var SESSION_DATA_FILES = (function() {
        60 if (process.platform === 'darwin') {
        61 var libraryDir = path.join('/Users', process.env['USER'], 'Library');
        62 return [
        63 path.join(libraryDir, 'Caches/com.apple.Safari/Cache.db'),
        64 path.join(libraryDir, 'Cookies/Cookies.binarycookies'),
        65 path.join(libraryDir, 'Cookies/Cookies.plist'),
        66 path.join(libraryDir, 'Safari/History.plist'),
        67 path.join(libraryDir, 'Safari/LastSession.plist'),
        68 path.join(libraryDir, 'Safari/LocalStorage'),
        69 path.join(libraryDir, 'Safari/Databases')
        70 ];
        71 } else if (process.platform === 'win32') {
        72 var appDataDir = path.join(process.env['APPDATA'],
        73 'Apple Computer', 'Safari');
        74 var localDataDir = path.join(process.env['LOCALAPPDATA'],
        75 'Apple Computer', 'Safari');
        76 return [
        77 path.join(appDataDir, 'History.plist'),
        78 path.join(appDataDir, 'LastSession.plist'),
        79 path.join(appDataDir, 'Cookies/Cookies.plist'),
        80 path.join(appDataDir, 'Cookies/Cookies.binarycookies'),
        81 path.join(localDataDir, 'Cache.db'),
        82 path.join(localDataDir, 'Databases'),
        83 path.join(localDataDir, 'LocalStorage')
        84 ];
        85 } else {
        86 return [];
        87 }
        88})();
        89
        90
        91/** @typedef {{port: number, address: string, family: string}} */
        92var Host;
        93
        94
        95/**
        96 * A basic HTTP/WebSocket server used to communicate with the SafariDriver
        97 * browser extension.
        98 * @constructor
        99 * @extends {events.EventEmitter}
        100 */
        101var Server = function() {
        102 events.EventEmitter.call(this);
        103
        104 var server = http.createServer(function(req, res) {
        105 if (req.url === '/favicon.ico') {
        106 res.writeHead(204);
        107 res.end();
        108 return;
        109 }
        110
        111 var query = url.parse(req.url).query || '';
        112 if (query.indexOf('url=') == -1) {
        113 var address = server.address()
        114 var host = address.address + ':' + address.port;
        115 res.writeHead(302, {'Location': 'http://' + host + '?url=ws://' + host});
        116 res.end();
        117 }
        118
        119 fs.readFile(CLIENT_PATH, 'utf8', function(err, data) {
        120 if (err) {
        121 res.writeHead(500, {'Content-Type': 'text/plain'});
        122 res.end(err.stack);
        123 return;
        124 }
        125 var content = '<!DOCTYPE html><body><script>' + data + '</script>';
        126 res.writeHead(200, {
        127 'Content-Type': 'text/html; charset=utf-8',
        128 'Content-Length': Buffer.byteLength(content, 'utf8'),
        129 });
        130 res.end(content);
        131 });
        132 });
        133
        134 var wss = new ws.Server({server: server});
        135 wss.on('connection', this.emit.bind(this, 'connection'));
        136
        137 /**
        138 * Starts the server on a random port.
        139 * @return {!webdriver.promise.Promise<Host>} A promise that will resolve
        140 * with the server host when it has fully started.
        141 */
        142 this.start = function() {
        143 if (server.address()) {
        144 return promise.fulfilled(server.address());
        145 }
        146 return portprober.findFreePort('localhost').then(function(port) {
        147 return promise.checkedNodeCall(
        148 server.listen.bind(server, port, 'localhost'));
        149 }).then(function() {
        150 return server.address();
        151 });
        152 };
        153
        154 /**
        155 * Stops the server.
        156 * @return {!webdriver.promise.Promise} A promise that will resolve when the
        157 * server has closed all connections.
        158 */
        159 this.stop = function() {
        160 return new promise.Promise(function(fulfill) {
        161 server.close(fulfill);
        162 });
        163 };
        164
        165 /**
        166 * @return {Host} This server's host info.
        167 * @throws {Error} If the server is not running.
        168 */
        169 this.address = function() {
        170 var addr = server.address();
        171 if (!addr) {
        172 throw Error('There server is not running!');
        173 }
        174 return addr;
        175 };
        176};
        177util.inherits(Server, events.EventEmitter);
        178
        179
        180/**
        181 * @return {!promise.Promise<string>} A promise that will resolve with the path
        182 * to Safari on the current system.
        183 */
        184function findSafariExecutable() {
        185 switch (process.platform) {
        186 case 'darwin':
        187 return promise.fulfilled(
        188 '/Applications/Safari.app/Contents/MacOS/Safari');
        189
        190 case 'win32':
        191 var files = [
        192 process.env['PROGRAMFILES'] || '\\Program Files',
        193 process.env['PROGRAMFILES(X86)'] || '\\Program Files (x86)'
        194 ].map(function(prefix) {
        195 return path.join(prefix, 'Safari\\Safari.exe');
        196 });
        197 return io.exists(files[0]).then(function(exists) {
        198 return exists ? files[0] : io.exists(files[1]).then(function(exists) {
        199 if (exists) {
        200 return files[1];
        201 }
        202 throw Error('Unable to find Safari on the current system');
        203 });
        204 });
        205
        206 default:
        207 return promise.rejected(
        208 Error('Safari is not supported on the current platform: ' +
        209 process.platform));
        210 }
        211}
        212
        213
        214/**
        215 * @param {string} url The URL to connect to.
        216 * @return {!promise.Promise<string>} A promise for the path to a file that
        217 * Safari can open on start-up to trigger a new connection to the WebSocket
        218 * server.
        219 */
        220function createConnectFile(url) {
        221 return io.tmpFile({postfix: '.html'}).then(function(f) {
        222 var writeFile = promise.checkedNodeCall(fs.writeFile,
        223 f,
        224 '<!DOCTYPE html><script>window.location = "' + url + '";</script>',
        225 {encoding: 'utf8'});
        226 return writeFile.then(function() {
        227 return f;
        228 });
        229 });
        230}
        231
        232
        233/**
        234 * Deletes all session data files if so desired.
        235 * @param {!Object} desiredCapabilities .
        236 * @return {!Array<promise.Promise>} A list of promises for the deleted files.
        237 */
        238function cleanSession(desiredCapabilities) {
        239 if (!desiredCapabilities) {
        240 return [];
        241 }
        242 var options = desiredCapabilities[OPTIONS_CAPABILITY_KEY];
        243 if (!options) {
        244 return [];
        245 }
        246 if (!options['cleanSession']) {
        247 return [];
        248 }
        249 return SESSION_DATA_FILES.map(function(file) {
        250 return io.unlink(file);
        251 });
        252}
        253
        254
        255/**
        256 * @constructor
        257 * @implements {webdriver.CommandExecutor}
        258 */
        259var CommandExecutor = function() {
        260 /** @private {Server} */
        261 this.server_ = null;
        262
        263 /** @private {ws.WebSocket} */
        264 this.socket_ = null;
        265
        266 /** @private {promise.Promise.<!exec.Command>} */
        267 this.safari_ = null;
        268};
        269
        270
        271/** @override */
        272CommandExecutor.prototype.execute = function(command, callback) {
        273 var safariCommand = JSON.stringify({
        274 'origin': 'webdriver',
        275 'type': 'command',
        276 'command': {
        277 'id': _base.require('goog.string').getRandomString(),
        278 'name': command.getName(),
        279 'parameters': command.getParameters()
        280 }
        281 });
        282 var self = this;
        283
        284 switch (command.getName()) {
        285 case webdriver.CommandName.NEW_SESSION:
        286 this.startSafari_(command).then(sendCommand, callback);
        287 break;
        288
        289 case webdriver.CommandName.QUIT:
        290 this.destroySession_().then(function() {
        291 callback(null, _base.require('bot.response').createResponse(null));
        292 }, callback);
        293 break;
        294
        295 default:
        296 sendCommand();
        297 break;
        298 }
        299
        300 function sendCommand() {
        301 new promise.Promise(function(fulfill, reject) {
        302 // TODO: support reconnecting with the extension.
        303 if (!self.socket_) {
        304 self.destroySession_().thenFinally(function() {
        305 reject(Error('The connection to the SafariDriver was closed'));
        306 });
        307 return;
        308 }
        309
        310 self.socket_.send(safariCommand, function(err) {
        311 if (err) {
        312 reject(err);
        313 return;
        314 }
        315 });
        316
        317 self.socket_.once('message', function(data) {
        318 try {
        319 data = JSON.parse(data);
        320 } catch (ex) {
        321 reject(Error('Failed to parse driver message: ' + data));
        322 return;
        323 }
        324 fulfill(data['response']);
        325 });
        326
        327 }).then(function(value) {
        328 callback(null, value);
        329 }, callback);
        330 }
        331};
        332
        333
        334/**
        335 * @param {!webdriver.Command} command .
        336 * @private
        337 */
        338CommandExecutor.prototype.startSafari_ = function(command) {
        339 this.server_ = new Server();
        340
        341 this.safari_ = this.server_.start().then(function(address) {
        342 var tasks = cleanSession(command.getParameters()['desiredCapabilities']);
        343 tasks.push(
        344 findSafariExecutable(),
        345 createConnectFile(
        346 'http://' + address.address + ':' + address.port));
        347 return promise.all(tasks).then(function(tasks) {
        348 var exe = tasks[tasks.length - 2];
        349 var html = tasks[tasks.length - 1];
        350 return exec(exe, {args: [html]});
        351 });
        352 });
        353
        354 var connected = promise.defer();
        355 var self = this;
        356 var start = Date.now();
        357 var timer = setTimeout(function() {
        358 connected.reject(Error(
        359 'Failed to connect to the SafariDriver after ' + (Date.now() - start) +
        360 ' ms; Have you installed the latest extension from ' +
        361 'http://selenium-release.storage.googleapis.com/index.html?'));
        362 }, 10 * 1000);
        363 this.server_.once('connection', function(socket) {
        364 clearTimeout(timer);
        365 self.socket_ = socket;
        366 socket.once('close', function() {
        367 self.socket_ = null;
        368 });
        369 connected.fulfill();
        370 });
        371 return connected.promise;
        372};
        373
        374
        375/**
        376 * Destroys the active session by stopping the WebSocket server and killing the
        377 * Safari subprocess.
        378 * @private
        379 */
        380CommandExecutor.prototype.destroySession_ = function() {
        381 var tasks = [];
        382 if (this.server_) {
        383 tasks.push(this.server_.stop());
        384 }
        385 if (this.safari_) {
        386 tasks.push(this.safari_.then(function(safari) {
        387 safari.kill();
        388 return safari.result();
        389 }));
        390 }
        391 var self = this;
        392 return promise.all(tasks).thenFinally(function() {
        393 self.server_ = null;
        394 self.socket_ = null;
        395 self.safari_ = null;
        396 });
        397};
        398
        399
        400/** @const */
        401var OPTIONS_CAPABILITY_KEY = 'safari.options';
        402
        403
        404
        405/**
        406 * Configuration options specific to the {@link Driver SafariDriver}.
        407 * @constructor
        408 * @extends {webdriver.Serializable}
        409 */
        410var Options = function() {
        411 webdriver.Serializable.call(this);
        412
        413 /** @private {Object<string, *>} */
        414 this.options_ = null;
        415
        416 /** @private {webdriver.logging.Preferences} */
        417 this.logPrefs_ = null;
        418};
        419util.inherits(Options, webdriver.Serializable);
        420
        421
        422/**
        423 * Extracts the SafariDriver specific options from the given capabilities
        424 * object.
        425 * @param {!webdriver.Capabilities} capabilities The capabilities object.
        426 * @return {!Options} The ChromeDriver options.
        427 */
        428Options.fromCapabilities = function(capabilities) {
        429 var options = new Options();
        430
        431 var o = capabilities.get(OPTIONS_CAPABILITY_KEY);
        432 if (o instanceof Options) {
        433 options = o;
        434 } else if (o) {
        435 options.setCleanSession(o.cleanSession);
        436 }
        437
        438 if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) {
        439 options.setLoggingPrefs(
        440 capabilities.get(webdriver.Capability.LOGGING_PREFS));
        441 }
        442
        443 return options;
        444};
        445
        446
        447/**
        448 * Sets whether to force Safari to start with a clean session. Enabling this
        449 * option will cause all global browser data to be deleted.
        450 * @param {boolean} clean Whether to make sure the session has no cookies,
        451 * cache entries, local storage, or databases.
        452 * @return {!Options} A self reference.
        453 */
        454Options.prototype.setCleanSession = function(clean) {
        455 if (!this.options_) {
        456 this.options_ = {};
        457 }
        458 this.options_['cleanSession'] = clean;
        459 return this;
        460};
        461
        462
        463/**
        464 * Sets the logging preferences for the new session.
        465 * @param {!webdriver.logging.Preferences} prefs The logging preferences.
        466 * @return {!Options} A self reference.
        467 */
        468Options.prototype.setLoggingPrefs = function(prefs) {
        469 this.logPrefs_ = prefs;
        470 return this;
        471};
        472
        473
        474/**
        475 * Converts this options instance to a {@link webdriver.Capabilities} object.
        476 * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge
        477 * these options into, if any.
        478 * @return {!webdriver.Capabilities} The capabilities.
        479 */
        480Options.prototype.toCapabilities = function(opt_capabilities) {
        481 var capabilities = opt_capabilities || webdriver.Capabilities.safari();
        482 if (this.logPrefs_) {
        483 capabilities.set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_);
        484 }
        485 if (this.options_) {
        486 capabilities.set(OPTIONS_CAPABILITY_KEY, this);
        487 }
        488 return capabilities;
        489};
        490
        491
        492/**
        493 * Converts this instance to its JSON wire protocol representation. Note this
        494 * function is an implementation detail not intended for general use.
        495 * @return {!Object<string, *>} The JSON wire protocol representation of this
        496 * instance.
        497 * @override
        498 */
        499Options.prototype.serialize = function() {
        500 return this.options_ || {};
        501};
        502
        503
        504
        505/**
        506 * A WebDriver client for Safari. This class should never be instantiated
        507 * directly; instead, use the {@link selenium-webdriver.Builder}:
        508 *
        509 * var driver = new Builder()
        510 * .forBrowser('safari')
        511 * .build();
        512 *
        513 * @param {(Options|webdriver.Capabilities)=} opt_config The configuration
        514 * options for the new session.
        515 * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to create
        516 * the driver under.
        517 * @constructor
        518 * @extends {webdriver.WebDriver}
        519 */
        520var Driver = function(opt_config, opt_flow) {
        521 var executor = new CommandExecutor();
        522 var capabilities =
        523 opt_config instanceof Options ? opt_config.toCapabilities() :
        524 (opt_config || webdriver.Capabilities.safari());
        525
        526 var driver = webdriver.WebDriver.createSession(
        527 executor, capabilities, opt_flow);
        528 webdriver.WebDriver.call(
        529 this, driver.getSession(), executor, driver.controlFlow());
        530};
        531util.inherits(Driver, webdriver.WebDriver);
        532
        533
        534// Public API
        535
        536
        537exports.Driver = Driver;
        538exports.Options = Options;
        \ No newline at end of file diff --git a/docs/source/testing/assert.js.src.html b/docs/source/testing/assert.js.src.html index 125141d..48bfc89 100644 --- a/docs/source/testing/assert.js.src.html +++ b/docs/source/testing/assert.js.src.html @@ -1 +1 @@ -assert.js

        testing/assert.js

        1// Copyright 2013 Software Freedom Conservancy
        2//
        3// Licensed under the Apache License, Version 2.0 (the "License");
        4// you may not use this file except in compliance with the License.
        5// You may obtain a copy of the License at
        6//
        7// http://www.apache.org/licenses/LICENSE-2.0
        8//
        9// Unless required by applicable law or agreed to in writing, software
        10// distributed under the License is distributed on an "AS IS" BASIS,
        11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        12// See the License for the specific language governing permissions and
        13// limitations under the License.
        14
        15/**
        16 * @fileoverview Defines a library that simplifies writing assertions against
        17 * promised values.
        18 *
        19 * <blockquote>
        20 * <hr>
        21 * <b>NOTE:</b> This module is considered experimental and is subject to
        22 * change, or removal, at any time!
        23 * <hr>
        24 * </blockquote>
        25 *
        26 * Sample usage:
        27 * <pre><code>
        28 * var driver = new webdriver.Builder().build();
        29 * driver.get('http://www.google.com');
        30 *
        31 * assert(driver.getTitle()).equalTo('Google');
        32 * </code></pre>
        33 */
        34
        35var base = require('../_base'),
        36 assert = base.require('webdriver.testing.assert');
        37
        38
        39// PUBLIC API
        40
        41
        42/** @type {webdriver.testing.assert.} */
        43module.exports = assert;
        \ No newline at end of file +assert.js

        testing/assert.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Defines a library that simplifies writing assertions against
        20 * promised values.
        21 *
        22 * > <hr>
        23 * > __NOTE:__ This module is considered experimental and is subject to
        24 * > change, or removal, at any time!
        25 * > <hr>
        26 *
        27 * Sample usage:
        28 *
        29 * var driver = new webdriver.Builder().build();
        30 * driver.get('http://www.google.com');
        31 *
        32 * assert(driver.getTitle()).equalTo('Google');
        33 */
        34
        35var base = require('../_base'),
        36 assert = base.require('webdriver.testing.assert');
        37
        38
        39// PUBLIC API
        40
        41
        42/**
        43 * Creates a new assertion.
        44 * @param {*} value The value to perform an assertion on.
        45 * @return {!webdriver.testing.Assertion} The new assertion.
        46 */
        47module.exports = function(value) {
        48 return assert(value);
        49};
        50
        51
        52/**
        53 * Registers a new assertion to expose from the
        54 * {@link webdriver.testing.Assertion} prototype.
        55 * @param {string} name The assertion name.
        56 * @param {(function(new: goog.labs.testing.Matcher, *)|
        57 * {matches: function(*): boolean,
        58 * describe: function(): string})} matcherTemplate Either the
        59 * matcher constructor to use, or an object literal defining a matcher.
        60 */
        61module.exports.register = assert.register;
        \ No newline at end of file diff --git a/docs/source/testing/index.js.src.html b/docs/source/testing/index.js.src.html index 5049609..81d7f5a 100644 --- a/docs/source/testing/index.js.src.html +++ b/docs/source/testing/index.js.src.html @@ -1 +1 @@ -index.js

        testing/index.js

        1// Copyright 2013 Selenium committers
        2// Copyright 2013 Software Freedom Conservancy
        3//
        4// Licensed under the Apache License, Version 2.0 (the "License");
        5// you may not use this file except in compliance with the License.
        6// You may obtain a copy of the License at
        7//
        8// http://www.apache.org/licenses/LICENSE-2.0
        9//
        10// Unless required by applicable law or agreed to in writing, software
        11// distributed under the License is distributed on an "AS IS" BASIS,
        12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        13// See the License for the specific language governing permissions and
        14// limitations under the License.
        15
        16/**
        17 * @fileoverview Provides wrappers around the following global functions from
        18 * <a href="http://visionmedia.github.io/mocha/">Mocha's BDD interface</a>:
        19 * <ul>
        20 * <li>after
        21 * <li>afterEach
        22 * <li>before
        23 * <li>beforeEach
        24 * <li>it
        25 * <li>it.only
        26 * <li>it.skip
        27 * <li>xit
        28 * </ul>
        29 *
        30 * <p>The provided wrappers leverage the webdriver.promise.ControlFlow to
        31 * simplify writing asynchronous tests:
        32 * <pre><code>
        33 * var webdriver = require('selenium-webdriver'),
        34 * remote = require('selenium-webdriver/remote'),
        35 * test = require('selenium-webdriver/testing');
        36 *
        37 * test.describe('Google Search', function() {
        38 * var driver, server;
        39 *
        40 * test.before(function() {
        41 * server = new remote.SeleniumServer({
        42 * jar: 'path/to/selenium-server-standalone.jar'
        43 * });
        44 * server.start();
        45 *
        46 * driver = new webdriver.Builder().
        47 * withCapabilities({'browserName': 'firefox'}).
        48 * usingServer(server.address()).
        49 * build();
        50 * });
        51 *
        52 * test.after(function() {
        53 * driver.quit();
        54 * server.stop();
        55 * });
        56 *
        57 * test.it('should append query to title', function() {
        58 * driver.get('http://www.google.com');
        59 * driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
        60 * driver.findElement(webdriver.By.name('btnG')).click();
        61 * driver.wait(function() {
        62 * return driver.getTitle().then(function(title) {
        63 * return 'webdriver - Google Search' === title;
        64 * });
        65 * }, 1000, 'Waiting for title to update');
        66 * });
        67 * });
        68 * </code></pre>
        69 *
        70 * <p>You may conditionally suppress a test function using the exported
        71 * "ignore" function. If the provided predicate returns true, the attached
        72 * test case will be skipped:
        73 * <pre><code>
        74 * test.ignore(maybe()).it('is flaky', function() {
        75 * if (Math.random() < 0.5) throw Error();
        76 * });
        77 *
        78 * function maybe() { return Math.random() < 0.5; }
        79 * </code></pre>
        80 */
        81
        82var flow = require('..').promise.controlFlow();
        83
        84
        85/**
        86 * Wraps a function so that all passed arguments are ignored.
        87 * @param {!Function} fn The function to wrap.
        88 * @return {!Function} The wrapped function.
        89 */
        90function seal(fn) {
        91 return function() {
        92 fn();
        93 };
        94}
        95
        96
        97/**
        98 * Wraps a function on Mocha's BDD interface so it runs inside a
        99 * webdriver.promise.ControlFlow and waits for the flow to complete before
        100 * continuing.
        101 * @param {!Function} globalFn The function to wrap.
        102 * @return {!Function} The new function.
        103 */
        104function wrapped(globalFn) {
        105 return function() {
        106 switch (arguments.length) {
        107 case 1:
        108 globalFn(asyncTestFn(arguments[0]));
        109 break;
        110
        111 case 2:
        112 globalFn(arguments[0], asyncTestFn(arguments[1]));
        113 break;
        114
        115 default:
        116 throw Error('Invalid # arguments: ' + arguments.length);
        117 }
        118 };
        119
        120 function asyncTestFn(fn) {
        121 return function(done) {
        122 this.timeout(0);
        123 var timeout = this.timeout;
        124 this.timeout = undefined; // Do not let tests change the timeout.
        125 try {
        126 flow.execute(fn.bind(this)).then(seal(done), done);
        127 } finally {
        128 this.timeout = timeout;
        129 }
        130 };
        131 }
        132}
        133
        134
        135/**
        136 * Ignores the test chained to this function if the provided predicate returns
        137 * true.
        138 * @param {function(): boolean} predicateFn A predicate to call to determine
        139 * if the test should be suppressed. This function MUST be synchronous.
        140 * @return {!Object} An object with wrapped versions of {@link #it()} and
        141 * {@link #describe()} that ignore tests as indicated by the predicate.
        142 */
        143function ignore(predicateFn) {
        144 var describe = wrap(exports.xdescribe, exports.describe);
        145 describe.only = wrap(exports.xdescribe, exports.describe.only);
        146
        147 var it = wrap(exports.xit, exports.it);
        148 it.only = wrap(exports.xit, exports.it.only);
        149
        150 return {
        151 describe: describe,
        152 it: it
        153 };
        154
        155 function wrap(onSkip, onRun) {
        156 return function(title, fn) {
        157 if (predicateFn()) {
        158 onSkip(title, fn);
        159 } else {
        160 onRun(title, fn);
        161 }
        162 };
        163 }
        164}
        165
        166
        167// PUBLIC API
        168
        169/**
        170 * Registers a new test suite.
        171 * @param {string} name The suite name.
        172 * @param {function()=} fn The suite function, or {@code undefined} to define
        173 * a pending test suite.
        174 */
        175exports.describe = global.describe;
        176
        177/**
        178 * Defines a suppressed test suite.
        179 * @param {string} name The suite name.
        180 * @param {function()=} fn The suite function, or {@code undefined} to define
        181 * a pending test suite.
        182 */
        183exports.xdescribe = global.xdescribe;
        184exports.describe.skip = global.describe.skip;
        185
        186/**
        187 * Register a function to call after the current suite finishes.
        188 * @param {function()} fn .
        189 */
        190exports.after = wrapped(global.after);
        191
        192/**
        193 * Register a function to call after each test in a suite.
        194 * @param {function()} fn .
        195 */
        196exports.afterEach = wrapped(global.afterEach);
        197
        198/**
        199 * Register a function to call before the current suite starts.
        200 * @param {function()} fn .
        201 */
        202exports.before = wrapped(global.before);
        203
        204/**
        205 * Register a function to call before each test in a suite.
        206 * @param {function()} fn .
        207 */
        208exports.beforeEach = wrapped(global.beforeEach);
        209
        210/**
        211 * Add a test to the current suite.
        212 * @param {string} name The test name.
        213 * @param {function()=} fn The test function, or {@code undefined} to define
        214 * a pending test case.
        215 */
        216exports.it = wrapped(global.it);
        217
        218/**
        219 * An alias for {@link #it()} that flags the test as the only one that should
        220 * be run within the current suite.
        221 * @param {string} name The test name.
        222 * @param {function()=} fn The test function, or {@code undefined} to define
        223 * a pending test case.
        224 */
        225exports.iit = exports.it.only = wrapped(global.it.only);
        226
        227/**
        228 * Adds a test to the current suite while suppressing it so it is not run.
        229 * @param {string} name The test name.
        230 * @param {function()=} fn The test function, or {@code undefined} to define
        231 * a pending test case.
        232 */
        233exports.xit = exports.it.skip = wrapped(global.xit);
        234
        235exports.ignore = ignore;
        \ No newline at end of file +index.js

        testing/index.js

        1// Licensed to the Software Freedom Conservancy (SFC) under one
        2// or more contributor license agreements. See the NOTICE file
        3// distributed with this work for additional information
        4// regarding copyright ownership. The SFC licenses this file
        5// to you under the Apache License, Version 2.0 (the
        6// "License"); you may not use this file except in compliance
        7// with the License. You may obtain a copy of the License at
        8//
        9// http://www.apache.org/licenses/LICENSE-2.0
        10//
        11// Unless required by applicable law or agreed to in writing,
        12// software distributed under the License is distributed on an
        13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
        14// KIND, either express or implied. See the License for the
        15// specific language governing permissions and limitations
        16// under the License.
        17
        18/**
        19 * @fileoverview Provides wrappers around the following global functions from
        20 * [Mocha's BDD interface](https://github.com/mochajs/mocha):
        21 *
        22 * - after
        23 * - afterEach
        24 * - before
        25 * - beforeEach
        26 * - it
        27 * - it.only
        28 * - it.skip
        29 * - xit
        30 *
        31 * The provided wrappers leverage the {@link webdriver.promise.ControlFlow}
        32 * to simplify writing asynchronous tests:
        33 *
        34 * var By = require('selenium-webdriver').By,
        35 * until = require('selenium-webdriver').until,
        36 * firefox = require('selenium-webdriver/firefox'),
        37 * test = require('selenium-webdriver/testing');
        38 *
        39 * test.describe('Google Search', function() {
        40 * var driver;
        41 *
        42 * test.before(function() {
        43 * driver = new firefox.Driver();
        44 * });
        45 *
        46 * test.after(function() {
        47 * driver.quit();
        48 * });
        49 *
        50 * test.it('should append query to title', function() {
        51 * driver.get('http://www.google.com/ncr');
        52 * driver.findElement(By.name('q')).sendKeys('webdriver');
        53 * driver.findElement(By.name('btnG')).click();
        54 * driver.wait(until.titleIs('webdriver - Google Search'), 1000);
        55 * });
        56 * });
        57 *
        58 * You may conditionally suppress a test function using the exported
        59 * "ignore" function. If the provided predicate returns true, the attached
        60 * test case will be skipped:
        61 *
        62 * test.ignore(maybe()).it('is flaky', function() {
        63 * if (Math.random() < 0.5) throw Error();
        64 * });
        65 *
        66 * function maybe() { return Math.random() < 0.5; }
        67 */
        68
        69var promise = require('..').promise;
        70var flow = promise.controlFlow();
        71
        72
        73/**
        74 * Wraps a function so that all passed arguments are ignored.
        75 * @param {!Function} fn The function to wrap.
        76 * @return {!Function} The wrapped function.
        77 */
        78function seal(fn) {
        79 return function() {
        80 fn();
        81 };
        82}
        83
        84
        85/**
        86 * Wraps a function on Mocha's BDD interface so it runs inside a
        87 * webdriver.promise.ControlFlow and waits for the flow to complete before
        88 * continuing.
        89 * @param {!Function} globalFn The function to wrap.
        90 * @return {!Function} The new function.
        91 */
        92function wrapped(globalFn) {
        93 return function() {
        94 if (arguments.length === 1) {
        95 return globalFn(makeAsyncTestFn(arguments[0]));
        96 }
        97 else if (arguments.length === 2) {
        98 return globalFn(arguments[0], makeAsyncTestFn(arguments[1]));
        99 }
        100 else {
        101 throw Error('Invalid # arguments: ' + arguments.length);
        102 }
        103 };
        104}
        105
        106/**
        107 * Make a wrapper to invoke caller's test function, fn. Run the test function
        108 * within a ControlFlow.
        109 *
        110 * Should preserve the semantics of Mocha's Runnable.prototype.run (See
        111 * https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192)
        112 *
        113 * @param {Function} fn
        114 * @return {Function}
        115 */
        116function makeAsyncTestFn(fn) {
        117 var async = fn.length > 0; // if test function expects a callback, its "async"
        118
        119 var ret = function(done) {
        120 var runnable = this.runnable();
        121 var mochaCallback = runnable.callback;
        122 runnable.callback = function() {
        123 flow.reset();
        124 return mochaCallback.apply(this, arguments);
        125 };
        126
        127 var testFn = fn.bind(this);
        128 flow.execute(function controlFlowExecute() {
        129 return new promise.Promise(function(fulfill, reject) {
        130 if (async) {
        131 // If testFn is async (it expects a done callback), resolve the promise of this
        132 // test whenever that callback says to. Any promises returned from testFn are
        133 // ignored.
        134 testFn(function testFnDoneCallback(err) {
        135 if (err) {
        136 reject(err);
        137 } else {
        138 fulfill();
        139 }
        140 });
        141 } else {
        142 // Without a callback, testFn can return a promise, or it will
        143 // be assumed to have completed synchronously
        144 fulfill(testFn());
        145 }
        146 }, flow);
        147 }, runnable.fullTitle()).then(seal(done), done);
        148 };
        149
        150 ret.toString = function() {
        151 return fn.toString();
        152 };
        153
        154 return ret;
        155}
        156
        157
        158/**
        159 * Ignores the test chained to this function if the provided predicate returns
        160 * true.
        161 * @param {function(): boolean} predicateFn A predicate to call to determine
        162 * if the test should be suppressed. This function MUST be synchronous.
        163 * @return {!Object} An object with wrapped versions of {@link #it()} and
        164 * {@link #describe()} that ignore tests as indicated by the predicate.
        165 */
        166function ignore(predicateFn) {
        167 var describe = wrap(exports.xdescribe, exports.describe);
        168 describe.only = wrap(exports.xdescribe, exports.describe.only);
        169
        170 var it = wrap(exports.xit, exports.it);
        171 it.only = wrap(exports.xit, exports.it.only);
        172
        173 return {
        174 describe: describe,
        175 it: it
        176 };
        177
        178 function wrap(onSkip, onRun) {
        179 return function(title, fn) {
        180 if (predicateFn()) {
        181 onSkip(title, fn);
        182 } else {
        183 onRun(title, fn);
        184 }
        185 };
        186 }
        187}
        188
        189
        190// PUBLIC API
        191
        192/**
        193 * Registers a new test suite.
        194 * @param {string} name The suite name.
        195 * @param {function()=} fn The suite function, or {@code undefined} to define
        196 * a pending test suite.
        197 */
        198exports.describe = global.describe;
        199
        200/**
        201 * Defines a suppressed test suite.
        202 * @param {string} name The suite name.
        203 * @param {function()=} fn The suite function, or {@code undefined} to define
        204 * a pending test suite.
        205 */
        206exports.xdescribe = global.xdescribe;
        207exports.describe.skip = global.describe.skip;
        208
        209/**
        210 * Register a function to call after the current suite finishes.
        211 * @param {function()} fn .
        212 */
        213exports.after = wrapped(global.after);
        214
        215/**
        216 * Register a function to call after each test in a suite.
        217 * @param {function()} fn .
        218 */
        219exports.afterEach = wrapped(global.afterEach);
        220
        221/**
        222 * Register a function to call before the current suite starts.
        223 * @param {function()} fn .
        224 */
        225exports.before = wrapped(global.before);
        226
        227/**
        228 * Register a function to call before each test in a suite.
        229 * @param {function()} fn .
        230 */
        231exports.beforeEach = wrapped(global.beforeEach);
        232
        233/**
        234 * Add a test to the current suite.
        235 * @param {string} name The test name.
        236 * @param {function()=} fn The test function, or {@code undefined} to define
        237 * a pending test case.
        238 */
        239exports.it = wrapped(global.it);
        240
        241/**
        242 * An alias for {@link #it()} that flags the test as the only one that should
        243 * be run within the current suite.
        244 * @param {string} name The test name.
        245 * @param {function()=} fn The test function, or {@code undefined} to define
        246 * a pending test case.
        247 */
        248exports.iit = exports.it.only = wrapped(global.it.only);
        249
        250/**
        251 * Adds a test to the current suite while suppressing it so it is not run.
        252 * @param {string} name The test name.
        253 * @param {function()=} fn The test function, or {@code undefined} to define
        254 * a pending test case.
        255 */
        256exports.xit = exports.it.skip = wrapped(global.xit);
        257
        258exports.ignore = ignore;
        \ No newline at end of file diff --git a/docs/types.js b/docs/types.js index 88ff729..2432b6c 100644 --- a/docs/types.js +++ b/docs/types.js @@ -1 +1 @@ -var TYPES = {"files":[{"name":"_base.js","href":"source/_base.js.src.html"},{"name":"builder.js","href":"source/builder.js.src.html"},{"name":"chrome.js","href":"source/chrome.js.src.html"},{"name":"error.js","href":"source/error.js.src.html"},{"name":"executors.js","href":"source/executors.js.src.html"},{"name":"http/index.js","href":"source/http/index.js.src.html"},{"name":"http/util.js","href":"source/http/util.js.src.html"},{"name":"index.js","href":"source/index.js.src.html"},{"name":"io/index.js","href":"source/io/index.js.src.html"},{"name":"lib/atoms/error.js","href":"source/lib/atoms/error.js.src.html"},{"name":"lib/atoms/json.js","href":"source/lib/atoms/json.js.src.html"},{"name":"lib/atoms/response.js","href":"source/lib/atoms/response.js.src.html"},{"name":"lib/atoms/userAgent.js","href":"source/lib/atoms/userAgent.js.src.html"},{"name":"lib/goog/array/array.js","href":"source/lib/goog/array/array.js.src.html"},{"name":"lib/goog/asserts/asserts.js","href":"source/lib/goog/asserts/asserts.js.src.html"},{"name":"lib/goog/base.js","href":"source/lib/goog/base.js.src.html"},{"name":"lib/goog/debug/error.js","href":"source/lib/goog/debug/error.js.src.html"},{"name":"lib/goog/deps.js","href":"source/lib/goog/deps.js.src.html"},{"name":"lib/goog/iter/iter.js","href":"source/lib/goog/iter/iter.js.src.html"},{"name":"lib/goog/json/json.js","href":"source/lib/goog/json/json.js.src.html"},{"name":"lib/goog/labs/testing/assertthat.js","href":"source/lib/goog/labs/testing/assertthat.js.src.html"},{"name":"lib/goog/labs/testing/logicmatcher.js","href":"source/lib/goog/labs/testing/logicmatcher.js.src.html"},{"name":"lib/goog/labs/testing/matcher.js","href":"source/lib/goog/labs/testing/matcher.js.src.html"},{"name":"lib/goog/labs/testing/numbermatcher.js","href":"source/lib/goog/labs/testing/numbermatcher.js.src.html"},{"name":"lib/goog/labs/testing/objectmatcher.js","href":"source/lib/goog/labs/testing/objectmatcher.js.src.html"},{"name":"lib/goog/labs/testing/stringmatcher.js","href":"source/lib/goog/labs/testing/stringmatcher.js.src.html"},{"name":"lib/goog/net/wrapperxmlhttpfactory.js","href":"source/lib/goog/net/wrapperxmlhttpfactory.js.src.html"},{"name":"lib/goog/net/xmlhttp.js","href":"source/lib/goog/net/xmlhttp.js.src.html"},{"name":"lib/goog/net/xmlhttpfactory.js","href":"source/lib/goog/net/xmlhttpfactory.js.src.html"},{"name":"lib/goog/object/object.js","href":"source/lib/goog/object/object.js.src.html"},{"name":"lib/goog/string/string.js","href":"source/lib/goog/string/string.js.src.html"},{"name":"lib/goog/structs/map.js","href":"source/lib/goog/structs/map.js.src.html"},{"name":"lib/goog/structs/structs.js","href":"source/lib/goog/structs/structs.js.src.html"},{"name":"lib/goog/uri/uri.js","href":"source/lib/goog/uri/uri.js.src.html"},{"name":"lib/goog/uri/utils.js","href":"source/lib/goog/uri/utils.js.src.html"},{"name":"lib/goog/useragent/product.js","href":"source/lib/goog/useragent/product.js.src.html"},{"name":"lib/goog/useragent/product_isversion.js","href":"source/lib/goog/useragent/product_isversion.js.src.html"},{"name":"lib/goog/useragent/useragent.js","href":"source/lib/goog/useragent/useragent.js.src.html"},{"name":"lib/webdriver/abstractbuilder.js","href":"source/lib/webdriver/abstractbuilder.js.src.html"},{"name":"lib/webdriver/actionsequence.js","href":"source/lib/webdriver/actionsequence.js.src.html"},{"name":"lib/webdriver/builder.js","href":"source/lib/webdriver/builder.js.src.html"},{"name":"lib/webdriver/button.js","href":"source/lib/webdriver/button.js.src.html"},{"name":"lib/webdriver/capabilities.js","href":"source/lib/webdriver/capabilities.js.src.html"},{"name":"lib/webdriver/command.js","href":"source/lib/webdriver/command.js.src.html"},{"name":"lib/webdriver/events.js","href":"source/lib/webdriver/events.js.src.html"},{"name":"lib/webdriver/firefoxdomexecutor.js","href":"source/lib/webdriver/firefoxdomexecutor.js.src.html"},{"name":"lib/webdriver/http/corsclient.js","href":"source/lib/webdriver/http/corsclient.js.src.html"},{"name":"lib/webdriver/http/http.js","href":"source/lib/webdriver/http/http.js.src.html"},{"name":"lib/webdriver/http/xhrclient.js","href":"source/lib/webdriver/http/xhrclient.js.src.html"},{"name":"lib/webdriver/key.js","href":"source/lib/webdriver/key.js.src.html"},{"name":"lib/webdriver/locators.js","href":"source/lib/webdriver/locators.js.src.html"},{"name":"lib/webdriver/logging.js","href":"source/lib/webdriver/logging.js.src.html"},{"name":"lib/webdriver/process.js","href":"source/lib/webdriver/process.js.src.html"},{"name":"lib/webdriver/promise.js","href":"source/lib/webdriver/promise.js.src.html"},{"name":"lib/webdriver/session.js","href":"source/lib/webdriver/session.js.src.html"},{"name":"lib/webdriver/stacktrace.js","href":"source/lib/webdriver/stacktrace.js.src.html"},{"name":"lib/webdriver/testing/asserts.js","href":"source/lib/webdriver/testing/asserts.js.src.html"},{"name":"lib/webdriver/webdriver.js","href":"source/lib/webdriver/webdriver.js.src.html"},{"name":"net/index.js","href":"source/net/index.js.src.html"},{"name":"net/portprober.js","href":"source/net/portprober.js.src.html"},{"name":"phantomjs.js","href":"source/phantomjs.js.src.html"},{"name":"proxy.js","href":"source/proxy.js.src.html"},{"name":"remote/index.js","href":"source/remote/index.js.src.html"},{"name":"testing/assert.js","href":"source/testing/assert.js.src.html"},{"name":"testing/index.js","href":"source/testing/index.js.src.html"}],"types":[{"isInterface":false,"name":"bot","isTypedef":false,"href":"namespace_bot.html"},{"isInterface":false,"name":"bot.Error","isTypedef":false,"href":"class_bot_Error.html"},{"isInterface":false,"name":"bot.Error.State","isTypedef":false,"href":"enum_bot_Error_State.html"},{"isInterface":false,"name":"bot.ErrorCode","isTypedef":false,"href":"enum_bot_ErrorCode.html"},{"isInterface":false,"name":"bot.json","isTypedef":false,"href":"namespace_bot_json.html"},{"isInterface":false,"name":"bot.response","isTypedef":false,"href":"namespace_bot_response.html"},{"name":"bot.response.ResponseObject","isTypedef":true,"href":"namespace_bot_response.html#bot.response.ResponseObject"},{"isInterface":false,"name":"bot.userAgent","isTypedef":false,"href":"namespace_bot_userAgent.html"},{"isInterface":false,"name":"goog","isTypedef":false,"href":"namespace_goog.html"},{"isInterface":false,"name":"goog.Uri","isTypedef":false,"href":"class_goog_Uri.html"},{"isInterface":false,"name":"goog.Uri.QueryData","isTypedef":false,"href":"class_goog_Uri_QueryData.html"},{"isInterface":false,"name":"goog.array","isTypedef":false,"href":"namespace_goog_array.html"},{"name":"goog.array.ArrayLike","isTypedef":true,"href":"namespace_goog_array.html#goog.array.ArrayLike"},{"isInterface":false,"name":"goog.asserts","isTypedef":false,"href":"namespace_goog_asserts.html"},{"isInterface":false,"name":"goog.asserts.AssertionError","isTypedef":false,"href":"class_goog_asserts_AssertionError.html"},{"isInterface":false,"name":"goog.debug","isTypedef":false,"href":"namespace_goog_debug.html"},{"isInterface":false,"name":"goog.debug.Error","isTypedef":false,"href":"class_goog_debug_Error.html"},{"isInterface":false,"name":"goog.iter","isTypedef":false,"href":"namespace_goog_iter.html"},{"name":"goog.iter.Iterable","isTypedef":true,"href":"namespace_goog_iter.html#goog.iter.Iterable"},{"isInterface":false,"name":"goog.iter.Iterator","isTypedef":false,"href":"class_goog_iter_Iterator.html"},{"isInterface":false,"name":"goog.json","isTypedef":false,"href":"namespace_goog_json.html"},{"name":"goog.json.Replacer","isTypedef":true,"href":"namespace_goog_json.html#goog.json.Replacer"},{"name":"goog.json.Reviver","isTypedef":true,"href":"namespace_goog_json.html#goog.json.Reviver"},{"isInterface":false,"name":"goog.json.Serializer","isTypedef":false,"href":"class_goog_json_Serializer.html"},{"isInterface":false,"name":"goog.labs","isTypedef":false,"href":"namespace_goog_labs.html"},{"isInterface":false,"name":"goog.labs.testing","isTypedef":false,"href":"namespace_goog_labs_testing.html"},{"isInterface":false,"name":"goog.labs.testing.AllOfMatcher","isTypedef":false,"href":"class_goog_labs_testing_AllOfMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.AnyOfMatcher","isTypedef":false,"href":"class_goog_labs_testing_AnyOfMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.CloseToMatcher","isTypedef":false,"href":"class_goog_labs_testing_CloseToMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.ContainsStringMatcher","isTypedef":false,"href":"class_goog_labs_testing_ContainsStringMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.EndsWithMatcher","isTypedef":false,"href":"class_goog_labs_testing_EndsWithMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.EqualToIgnoringWhitespaceMatcher","isTypedef":false,"href":"class_goog_labs_testing_EqualToIgnoringWhitespaceMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.EqualToMatcher","isTypedef":false,"href":"class_goog_labs_testing_EqualToMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.EqualsMatcher","isTypedef":false,"href":"class_goog_labs_testing_EqualsMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.GreaterThanEqualToMatcher","isTypedef":false,"href":"class_goog_labs_testing_GreaterThanEqualToMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.GreaterThanMatcher","isTypedef":false,"href":"class_goog_labs_testing_GreaterThanMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.HasPropertyMatcher","isTypedef":false,"href":"class_goog_labs_testing_HasPropertyMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.InstanceOfMatcher","isTypedef":false,"href":"class_goog_labs_testing_InstanceOfMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.IsNotMatcher","isTypedef":false,"href":"class_goog_labs_testing_IsNotMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.IsNullMatcher","isTypedef":false,"href":"class_goog_labs_testing_IsNullMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.IsNullOrUndefinedMatcher","isTypedef":false,"href":"class_goog_labs_testing_IsNullOrUndefinedMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.IsUndefinedMatcher","isTypedef":false,"href":"class_goog_labs_testing_IsUndefinedMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.LessThanEqualToMatcher","isTypedef":false,"href":"class_goog_labs_testing_LessThanEqualToMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.LessThanMatcher","isTypedef":false,"href":"class_goog_labs_testing_LessThanMatcher.html"},{"isInterface":true,"name":"goog.labs.testing.Matcher","isTypedef":false,"href":"interface_goog_labs_testing_Matcher.html"},{"isInterface":false,"name":"goog.labs.testing.MatcherError","isTypedef":false,"href":"class_goog_labs_testing_MatcherError.html"},{"isInterface":false,"name":"goog.labs.testing.ObjectEqualsMatcher","isTypedef":false,"href":"class_goog_labs_testing_ObjectEqualsMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.RegexMatcher","isTypedef":false,"href":"class_goog_labs_testing_RegexMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.StartsWithMatcher","isTypedef":false,"href":"class_goog_labs_testing_StartsWithMatcher.html"},{"isInterface":false,"name":"goog.labs.testing.StringContainsInOrderMatcher","isTypedef":false,"href":"class_goog_labs_testing_StringContainsInOrderMatcher.html"},{"isInterface":false,"name":"goog.net","isTypedef":false,"href":"namespace_goog_net.html"},{"isInterface":false,"name":"goog.net.DefaultXmlHttpFactory","isTypedef":false,"href":"class_goog_net_DefaultXmlHttpFactory.html"},{"isInterface":false,"name":"goog.net.WrapperXmlHttpFactory","isTypedef":false,"href":"class_goog_net_WrapperXmlHttpFactory.html"},{"isInterface":false,"name":"goog.net.XmlHttp","isTypedef":false,"href":"namespace_goog_net_XmlHttp.html"},{"isInterface":false,"name":"goog.net.XmlHttp.OptionType","isTypedef":false,"href":"enum_goog_net_XmlHttp_OptionType.html"},{"isInterface":false,"name":"goog.net.XmlHttp.ReadyState","isTypedef":false,"href":"enum_goog_net_XmlHttp_ReadyState.html"},{"isInterface":false,"name":"goog.net.XmlHttpFactory","isTypedef":false,"href":"class_goog_net_XmlHttpFactory.html"},{"isInterface":false,"name":"goog.object","isTypedef":false,"href":"namespace_goog_object.html"},{"isInterface":false,"name":"goog.string","isTypedef":false,"href":"namespace_goog_string.html"},{"isInterface":false,"name":"goog.string.Unicode","isTypedef":false,"href":"enum_goog_string_Unicode.html"},{"isInterface":false,"name":"goog.structs","isTypedef":false,"href":"namespace_goog_structs.html"},{"isInterface":false,"name":"goog.structs.Map","isTypedef":false,"href":"class_goog_structs_Map.html"},{"isInterface":false,"name":"goog.uri","isTypedef":false,"href":"namespace_goog_uri.html"},{"isInterface":false,"name":"goog.uri.utils","isTypedef":false,"href":"namespace_goog_uri_utils.html"},{"name":"goog.uri.utils.QueryArray","isTypedef":true,"href":"namespace_goog_uri_utils.html#goog.uri.utils.QueryArray"},{"name":"goog.uri.utils.QueryValue","isTypedef":true,"href":"namespace_goog_uri_utils.html#goog.uri.utils.QueryValue"},{"isInterface":false,"name":"goog.uri.utils.CharCode_","isTypedef":false,"href":"enum_goog_uri_utils_CharCode_.html"},{"isInterface":false,"name":"goog.uri.utils.ComponentIndex","isTypedef":false,"href":"enum_goog_uri_utils_ComponentIndex.html"},{"isInterface":false,"name":"goog.uri.utils.StandardQueryParam","isTypedef":false,"href":"enum_goog_uri_utils_StandardQueryParam.html"},{"isInterface":false,"name":"goog.userAgent","isTypedef":false,"href":"namespace_goog_userAgent.html"},{"isInterface":false,"name":"goog.userAgent.product","isTypedef":false,"href":"namespace_goog_userAgent_product.html"},{"isInterface":false,"name":"webdriver","isTypedef":false,"href":"namespace_webdriver.html"},{"isInterface":false,"name":"webdriver.AbstractBuilder","isTypedef":false,"href":"class_webdriver_AbstractBuilder.html"},{"isInterface":false,"name":"webdriver.ActionSequence","isTypedef":false,"href":"class_webdriver_ActionSequence.html"},{"isInterface":false,"name":"webdriver.Alert","isTypedef":false,"href":"class_webdriver_Alert.html"},{"isInterface":false,"name":"webdriver.Browser","isTypedef":false,"href":"enum_webdriver_Browser.html"},{"isInterface":false,"name":"webdriver.Builder","isTypedef":false,"href":"class_webdriver_Builder.html"},{"isInterface":false,"name":"webdriver.Button","isTypedef":false,"href":"enum_webdriver_Button.html"},{"isInterface":false,"name":"webdriver.By","isTypedef":false,"href":"namespace_webdriver_By.html"},{"name":"webdriver.By.Hash","isTypedef":true,"href":"namespace_webdriver_By.html#webdriver.By.Hash"},{"isInterface":false,"name":"webdriver.Capabilities","isTypedef":false,"href":"class_webdriver_Capabilities.html"},{"isInterface":false,"name":"webdriver.Capability","isTypedef":false,"href":"enum_webdriver_Capability.html"},{"isInterface":false,"name":"webdriver.Command","isTypedef":false,"href":"class_webdriver_Command.html"},{"isInterface":true,"name":"webdriver.CommandExecutor","isTypedef":false,"href":"interface_webdriver_CommandExecutor.html"},{"isInterface":false,"name":"webdriver.CommandName","isTypedef":false,"href":"enum_webdriver_CommandName.html"},{"isInterface":false,"name":"webdriver.EventEmitter","isTypedef":false,"href":"class_webdriver_EventEmitter.html"},{"isInterface":false,"name":"webdriver.FirefoxDomExecutor","isTypedef":false,"href":"class_webdriver_FirefoxDomExecutor.html"},{"isInterface":false,"name":"webdriver.FirefoxDomExecutor.Attribute_","isTypedef":false,"href":"enum_webdriver_FirefoxDomExecutor_Attribute_.html"},{"isInterface":false,"name":"webdriver.FirefoxDomExecutor.EventType_","isTypedef":false,"href":"enum_webdriver_FirefoxDomExecutor_EventType_.html"},{"isInterface":false,"name":"webdriver.Key","isTypedef":false,"href":"enum_webdriver_Key.html"},{"isInterface":false,"name":"webdriver.Locator","isTypedef":false,"href":"class_webdriver_Locator.html"},{"isInterface":false,"name":"webdriver.Session","isTypedef":false,"href":"class_webdriver_Session.html"},{"isInterface":false,"name":"webdriver.UnhandledAlertError","isTypedef":false,"href":"class_webdriver_UnhandledAlertError.html"},{"isInterface":false,"name":"webdriver.WebDriver","isTypedef":false,"href":"class_webdriver_WebDriver.html"},{"isInterface":false,"name":"webdriver.WebDriver.Logs","isTypedef":false,"href":"class_webdriver_WebDriver_Logs.html"},{"isInterface":false,"name":"webdriver.WebDriver.Navigation","isTypedef":false,"href":"class_webdriver_WebDriver_Navigation.html"},{"isInterface":false,"name":"webdriver.WebDriver.Options","isTypedef":false,"href":"class_webdriver_WebDriver_Options.html"},{"isInterface":false,"name":"webdriver.WebDriver.TargetLocator","isTypedef":false,"href":"class_webdriver_WebDriver_TargetLocator.html"},{"isInterface":false,"name":"webdriver.WebDriver.Timeouts","isTypedef":false,"href":"class_webdriver_WebDriver_Timeouts.html"},{"isInterface":false,"name":"webdriver.WebDriver.Window","isTypedef":false,"href":"class_webdriver_WebDriver_Window.html"},{"isInterface":false,"name":"webdriver.WebElement","isTypedef":false,"href":"class_webdriver_WebElement.html"},{"isInterface":false,"name":"webdriver.http","isTypedef":false,"href":"namespace_webdriver_http.html"},{"isInterface":true,"name":"webdriver.http.Client","isTypedef":false,"href":"interface_webdriver_http_Client.html"},{"isInterface":false,"name":"webdriver.http.CorsClient","isTypedef":false,"href":"class_webdriver_http_CorsClient.html"},{"isInterface":false,"name":"webdriver.http.Executor","isTypedef":false,"href":"class_webdriver_http_Executor.html"},{"isInterface":false,"name":"webdriver.http.Request","isTypedef":false,"href":"class_webdriver_http_Request.html"},{"isInterface":false,"name":"webdriver.http.Response","isTypedef":false,"href":"class_webdriver_http_Response.html"},{"isInterface":false,"name":"webdriver.http.XhrClient","isTypedef":false,"href":"class_webdriver_http_XhrClient.html"},{"isInterface":false,"name":"webdriver.logging","isTypedef":false,"href":"namespace_webdriver_logging.html"},{"name":"webdriver.logging.Preferences","isTypedef":true,"href":"namespace_webdriver_logging.html#webdriver.logging.Preferences"},{"isInterface":false,"name":"webdriver.logging.Entry","isTypedef":false,"href":"class_webdriver_logging_Entry.html"},{"isInterface":false,"name":"webdriver.logging.Level","isTypedef":false,"href":"enum_webdriver_logging_Level.html"},{"isInterface":false,"name":"webdriver.logging.LevelName","isTypedef":false,"href":"enum_webdriver_logging_LevelName.html"},{"isInterface":false,"name":"webdriver.logging.Type","isTypedef":false,"href":"enum_webdriver_logging_Type.html"},{"isInterface":false,"name":"webdriver.process","isTypedef":false,"href":"namespace_webdriver_process.html"},{"isInterface":false,"name":"webdriver.promise","isTypedef":false,"href":"namespace_webdriver_promise.html"},{"isInterface":false,"name":"webdriver.promise.CanceledTaskError_","isTypedef":false,"href":"class_webdriver_promise_CanceledTaskError_.html"},{"isInterface":false,"name":"webdriver.promise.ControlFlow","isTypedef":false,"href":"class_webdriver_promise_ControlFlow.html"},{"name":"webdriver.promise.ControlFlow.Timer","isTypedef":true,"href":"class_webdriver_promise_ControlFlow.html#webdriver.promise.ControlFlow.Timer"},{"isInterface":false,"name":"webdriver.promise.ControlFlow.EventType","isTypedef":false,"href":"enum_webdriver_promise_ControlFlow_EventType.html"},{"isInterface":false,"name":"webdriver.promise.Deferred","isTypedef":false,"href":"class_webdriver_promise_Deferred.html"},{"name":"webdriver.promise.Deferred.Listener_","isTypedef":true,"href":"class_webdriver_promise_Deferred.html#webdriver.promise.Deferred.Listener_"},{"isInterface":false,"name":"webdriver.promise.Deferred.State_","isTypedef":false,"href":"enum_webdriver_promise_Deferred_State_.html"},{"isInterface":false,"name":"webdriver.promise.Frame_","isTypedef":false,"href":"class_webdriver_promise_Frame_.html"},{"isInterface":false,"name":"webdriver.promise.Node_","isTypedef":false,"href":"class_webdriver_promise_Node_.html"},{"isInterface":false,"name":"webdriver.promise.Promise","isTypedef":false,"href":"class_webdriver_promise_Promise.html"},{"isInterface":false,"name":"webdriver.promise.Task_","isTypedef":false,"href":"class_webdriver_promise_Task_.html"},{"isInterface":false,"name":"webdriver.stacktrace","isTypedef":false,"href":"namespace_webdriver_stacktrace.html"},{"isInterface":false,"name":"webdriver.stacktrace.Frame","isTypedef":false,"href":"class_webdriver_stacktrace_Frame.html"},{"isInterface":false,"name":"webdriver.stacktrace.Snapshot","isTypedef":false,"href":"class_webdriver_stacktrace_Snapshot.html"},{"isInterface":false,"name":"webdriver.testing","isTypedef":false,"href":"namespace_webdriver_testing.html"},{"isInterface":false,"name":"webdriver.testing.Assertion","isTypedef":false,"href":"class_webdriver_testing_Assertion.html"},{"isInterface":false,"name":"webdriver.testing.Assertion.DelegatingMatcher_","isTypedef":false,"href":"class_webdriver_testing_Assertion_DelegatingMatcher_.html"},{"isInterface":false,"name":"webdriver.testing.ContainsMatcher","isTypedef":false,"href":"class_webdriver_testing_ContainsMatcher.html"},{"isInterface":false,"name":"webdriver.testing.NegatedAssertion","isTypedef":false,"href":"class_webdriver_testing_NegatedAssertion.html"},{"isInterface":false,"name":"webdriver.testing.assert","isTypedef":false,"href":"module_selenium-webdriver_testing_assert_namespace_dossier$$module__$usr$local$google$home$jleyba$dev$selenium$build$javascript$node$selenium_webdriver$testing$assert_exports.html"},{"isInterface":false,"name":"webdriver.testing.asserts","isTypedef":false,"href":"namespace_webdriver_testing_asserts.html"}],"modules":[{"name":"selenium-webdriver","types":[{"isInterface":false,"name":"Command","isTypedef":false,"href":"class_webdriver_Command.html"},{"isInterface":false,"name":"Key","isTypedef":false,"href":"enum_webdriver_Key.html"},{"isInterface":false,"name":"EventEmitter","isTypedef":false,"href":"class_webdriver_EventEmitter.html"},{"isInterface":false,"name":"Browser","isTypedef":false,"href":"enum_webdriver_Browser.html"},{"isInterface":false,"name":"ActionSequence","isTypedef":false,"href":"class_webdriver_ActionSequence.html"},{"isInterface":false,"name":"Button","isTypedef":false,"href":"enum_webdriver_Button.html"},{"isInterface":false,"name":"CommandName","isTypedef":false,"href":"enum_webdriver_CommandName.html"},{"isInterface":false,"name":"WebElement","isTypedef":false,"href":"class_webdriver_WebElement.html"},{"isInterface":false,"name":"Capability","isTypedef":false,"href":"enum_webdriver_Capability.html"},{"isInterface":false,"name":"Session","isTypedef":false,"href":"class_webdriver_Session.html"},{"isInterface":false,"name":"Builder","isTypedef":false,"href":"module_selenium-webdriver_class_Builder.html"},{"isInterface":false,"name":"Capabilities","isTypedef":false,"href":"class_webdriver_Capabilities.html"},{"isInterface":false,"name":"WebDriver","isTypedef":false,"href":"class_webdriver_WebDriver.html"}],"href":"module_selenium-webdriver.html"},{"name":"selenium-webdriver/_base","types":[],"href":"module_selenium-webdriver__base.html"},{"name":"selenium-webdriver/builder","types":[{"isInterface":false,"name":"Builder","isTypedef":false,"href":"module_selenium-webdriver_builder_class_Builder.html"}],"href":"module_selenium-webdriver_builder.html"},{"name":"selenium-webdriver/chrome","types":[{"isInterface":false,"name":"ServiceBuilder","isTypedef":false,"href":"module_selenium-webdriver_chrome_class_ServiceBuilder.html"},{"isInterface":false,"name":"Options","isTypedef":false,"href":"module_selenium-webdriver_chrome_class_Options.html"}],"href":"module_selenium-webdriver_chrome.html"},{"name":"selenium-webdriver/error","types":[{"isInterface":false,"name":"ErrorCode","isTypedef":false,"href":"enum_bot_ErrorCode.html"},{"isInterface":false,"name":"Error","isTypedef":false,"href":"class_bot_Error.html"}],"href":"module_selenium-webdriver_error.html"},{"name":"selenium-webdriver/executors","types":[],"href":"module_selenium-webdriver_executors.html"},{"name":"selenium-webdriver/http","types":[{"isInterface":false,"name":"HttpClient","isTypedef":false,"href":"module_selenium-webdriver_http_class_HttpClient.html"},{"isInterface":false,"name":"Response","isTypedef":false,"href":"class_webdriver_http_Response.html"},{"isInterface":false,"name":"Request","isTypedef":false,"href":"class_webdriver_http_Request.html"},{"isInterface":false,"name":"Executor","isTypedef":false,"href":"class_webdriver_http_Executor.html"}],"href":"module_selenium-webdriver_http.html"},{"name":"selenium-webdriver/http/util","types":[],"href":"module_selenium-webdriver_http_util.html"},{"name":"selenium-webdriver/io","types":[],"href":"module_selenium-webdriver_io.html"},{"name":"selenium-webdriver/net","types":[],"href":"module_selenium-webdriver_net.html"},{"name":"selenium-webdriver/net/portprober","types":[],"href":"module_selenium-webdriver_net_portprober.html"},{"name":"selenium-webdriver/phantomjs","types":[],"href":"module_selenium-webdriver_phantomjs.html"},{"name":"selenium-webdriver/proxy","types":[{"isInterface":false,"name":"ProxyConfig","isTypedef":true,"href":"module_selenium-webdriver_proxy_namespace_ProxyConfig.html"}],"href":"module_selenium-webdriver_proxy.html"},{"name":"selenium-webdriver/remote","types":[{"isInterface":false,"name":"DriverService","isTypedef":false,"href":"module_selenium-webdriver_remote_class_DriverService.html"},{"isInterface":false,"name":"SeleniumServer","isTypedef":false,"href":"module_selenium-webdriver_remote_class_SeleniumServer.html"},{"name":"SeleniumServer.Options","isTypedef":true,"href":""},{"isInterface":false,"name":"ServiceOptions","isTypedef":true,"href":"module_selenium-webdriver_remote_namespace_ServiceOptions.html"}],"href":"module_selenium-webdriver_remote.html"},{"name":"selenium-webdriver/testing","types":[],"href":"module_selenium-webdriver_testing.html"},{"name":"selenium-webdriver/testing/assert","types":[],"href":"module_selenium-webdriver_testing_assert.html"}]}; \ No newline at end of file +var TYPES = {"types":[{"name":"bot","href":"namespace_bot.html","namespace":true,"interface":false},{"name":"bot.Error","href":"class_bot_Error.html","namespace":false,"interface":false,"members":["code","isAutomationError","state","toString"]},{"name":"bot.Error.State","href":"enum_bot_Error_State.html","namespace":false,"interface":false},{"name":"bot.ErrorCode","href":"enum_bot_ErrorCode.html","namespace":false,"interface":false},{"name":"bot.json","href":"namespace_bot_json.html","namespace":true,"interface":false,"statics":["NATIVE_JSON","parse","stringify"]},{"name":"bot.response","href":"namespace_bot_response.html","namespace":true,"interface":false,"statics":["checkResponse","createErrorResponse","createResponse","isResponseObject"]},{"name":"bot.response.ResponseObject","href":"namespace_bot_response.html#response.ResponseObject"},{"name":"bot.userAgent","href":"namespace_bot_userAgent.html","namespace":true,"interface":false,"statics":["ANDROID_PRE_GINGERBREAD","ANDROID_PRE_ICECREAMSANDWICH","FIREFOX_EXTENSION","IE_DOC_10","IE_DOC_9","IE_DOC_PRE10","IE_DOC_PRE8","IE_DOC_PRE9","IOS","MOBILE","SAFARI_6","WINDOWS_PHONE","isEngineVersion","isProductVersion"]},{"name":"webdriver","href":"namespace_webdriver.html","namespace":true,"interface":false},{"name":"webdriver.ProxyConfig","href":"namespace_webdriver.html#webdriver.ProxyConfig"},{"name":"webdriver.ActionSequence","href":"class_webdriver_ActionSequence.html","namespace":false,"interface":false,"members":["click","doubleClick","dragAndDrop","keyDown","keyUp","mouseDown","mouseMove","mouseUp","perform","sendKeys"]},{"name":"webdriver.Alert","href":"class_webdriver_Alert.html","namespace":false,"interface":false,"members":["accept","dismiss","getText","sendKeys"]},{"name":"webdriver.AlertPromise","href":"class_webdriver_AlertPromise.html","namespace":false,"interface":false,"members":["accept","cancel","dismiss","getText","isPending","sendKeys","then","thenCatch","thenFinally"]},{"name":"webdriver.Browser","href":"enum_webdriver_Browser.html","namespace":false,"interface":false},{"name":"webdriver.Button","href":"enum_webdriver_Button.html","namespace":false,"interface":false},{"name":"webdriver.By","href":"namespace_webdriver_By.html","namespace":true,"interface":false,"statics":["className","css","id","js","linkText","name","partialLinkText","tagName","xpath"]},{"name":"webdriver.By.Hash","href":"namespace_webdriver_By.html#By.Hash"},{"name":"webdriver.Capabilities","href":"class_webdriver_Capabilities.html","namespace":false,"interface":false,"statics":["Capabilities.android","Capabilities.chrome","Capabilities.firefox","Capabilities.htmlunit","Capabilities.htmlunitwithjs","Capabilities.ie","Capabilities.ipad","Capabilities.iphone","Capabilities.opera","Capabilities.phantomjs","Capabilities.safari"],"members":["get","has","merge","serialize","set","setAlertBehavior","setEnableNativeEvents","setLoggingPrefs","setProxy","setScrollBehavior"]},{"name":"webdriver.Capability","href":"enum_webdriver_Capability.html","namespace":false,"interface":false},{"name":"webdriver.Command","href":"class_webdriver_Command.html","namespace":false,"interface":false,"members":["getName","getParameter","getParameters","setParameter","setParameters"]},{"name":"webdriver.CommandExecutor","href":"interface_webdriver_CommandExecutor.html","namespace":true,"interface":true,"members":["execute"]},{"name":"webdriver.CommandName","href":"enum_webdriver_CommandName.html","namespace":false,"interface":false},{"name":"webdriver.EventEmitter","href":"class_webdriver_EventEmitter.html","namespace":false,"interface":false,"members":["addListener","emit","listeners","on","once","removeAllListeners","removeListener"]},{"name":"webdriver.FileDetector","href":"class_webdriver_FileDetector.html","namespace":false,"interface":false,"members":["handleFile"]},{"name":"webdriver.Key","href":"enum_webdriver_Key.html","namespace":false,"interface":false,"statics":["Key.chord"]},{"name":"webdriver.Locator","href":"class_webdriver_Locator.html","namespace":false,"interface":false,"statics":["Locator.Strategy","Locator.checkLocator"],"members":["using","value","toString"]},{"name":"webdriver.Serializable","href":"class_webdriver_Serializable.html","namespace":false,"interface":false,"members":["serialize"]},{"name":"webdriver.Session","href":"class_webdriver_Session.html","namespace":false,"interface":false,"members":["getCapabilities","getCapability","getId","toJSON"]},{"name":"webdriver.TouchSequence","href":"class_webdriver_TouchSequence.html","namespace":false,"interface":false,"members":["doubleTap","flick","flickElement","longPress","move","perform","release","scroll","scrollFromElement","tap","tapAndHold"]},{"name":"webdriver.UnhandledAlertError","href":"class_webdriver_UnhandledAlertError.html","namespace":false,"interface":false,"members":["getAlertText"]},{"name":"webdriver.WebDriver","href":"class_webdriver_WebDriver.html","namespace":false,"interface":false,"statics":["WebDriver.attachToSession","WebDriver.createSession"],"members":["actions","call","close","controlFlow","executeAsyncScript","executeScript","findElement","findElements","get","getAllWindowHandles","getCapabilities","getCurrentUrl","getPageSource","getSession","getTitle","getWindowHandle","isElementPresent","manage","navigate","quit","schedule","setFileDetector","sleep","switchTo","takeScreenshot","touchActions","wait"]},{"name":"webdriver.WebDriver.Logs","href":"class_webdriver_WebDriver_Logs.html","namespace":false,"interface":false,"members":["get","getAvailableLogTypes"]},{"name":"webdriver.WebDriver.Navigation","href":"class_webdriver_WebDriver_Navigation.html","namespace":false,"interface":false,"members":["back","forward","refresh","to"]},{"name":"webdriver.WebDriver.Options","href":"class_webdriver_WebDriver_Options.html","namespace":false,"interface":false,"members":["addCookie","deleteAllCookies","deleteCookie","getCookie","getCookies","logs","timeouts","window"]},{"name":"webdriver.WebDriver.Options.Cookie","href":"class_webdriver_WebDriver_Options.html#Options.Cookie"},{"name":"webdriver.WebDriver.TargetLocator","href":"class_webdriver_WebDriver_TargetLocator.html","namespace":false,"interface":false,"members":["activeElement","alert","defaultContent","frame","window"]},{"name":"webdriver.WebDriver.Timeouts","href":"class_webdriver_WebDriver_Timeouts.html","namespace":false,"interface":false,"members":["implicitlyWait","pageLoadTimeout","setScriptTimeout"]},{"name":"webdriver.WebDriver.Window","href":"class_webdriver_WebDriver_Window.html","namespace":false,"interface":false,"members":["getPosition","getSize","maximize","setPosition","setSize"]},{"name":"webdriver.WebElement","href":"class_webdriver_WebElement.html","namespace":false,"interface":false,"statics":["WebElement.ELEMENT_KEY","WebElement.equals"],"members":["clear","click","findElement","findElements","getAttribute","getCssValue","getDriver","getId","getInnerHtml","getLocation","getOuterHtml","getRawId","getSize","getTagName","getText","isDisplayed","isElementPresent","isEnabled","isSelected","sendKeys","serialize","submit"]},{"name":"webdriver.WebElement.Id","href":"class_webdriver_WebElement.html#WebElement.Id"},{"name":"webdriver.WebElementPromise","href":"class_webdriver_WebElementPromise.html","namespace":false,"interface":false,"members":["cancel","getId","isPending","then","thenCatch","thenFinally"]},{"name":"webdriver.http","href":"namespace_webdriver_http.html","namespace":true,"interface":false},{"name":"webdriver.http.Client","href":"interface_webdriver_http_Client.html","namespace":true,"interface":true,"members":["send"]},{"name":"webdriver.http.Executor","href":"class_webdriver_http_Executor.html","namespace":false,"interface":false,"members":["defineCommand","execute"]},{"name":"webdriver.http.Request","href":"class_webdriver_http_Request.html","namespace":false,"interface":false,"members":["data","headers","method","path","toString"]},{"name":"webdriver.http.Response","href":"class_webdriver_http_Response.html","namespace":false,"interface":false,"statics":["Response.fromXmlHttpRequest"],"members":["body","headers","status","toString"]},{"name":"webdriver.logging","href":"namespace_webdriver_logging.html","namespace":true,"interface":false,"statics":["addConsoleHandler","getLevel","getLogger","installConsoleHandler","removeConsoleHandler"]},{"name":"webdriver.logging.Entry","href":"class_webdriver_logging_Entry.html","namespace":false,"interface":false,"statics":["Entry.fromClosureLogRecord"],"members":["level","message","timestamp","type","toJSON"]},{"name":"webdriver.logging.Level","href":"class_webdriver_logging_Level.html","namespace":false,"interface":false,"statics":["Level.ALL","Level.CONFIG","Level.DEBUG","Level.FINE","Level.FINER","Level.FINEST","Level.INFO","Level.OFF","Level.PREDEFINED_LEVELS","Level.SEVERE","Level.SHOUT","Level.WARNING","Level.getPredefinedLevel","Level.getPredefinedLevelByValue"],"members":["name","value","toString"]},{"name":"webdriver.logging.LogRecord","href":"class_webdriver_logging_LogRecord.html","namespace":false,"interface":false,"statics":["LogRecord.ENABLE_SEQUENCE_NUMBERS"],"members":["getException","getLevel","getLoggerName","getMessage","getMillis","getSequenceNumber","reset","setException","setLevel","setLoggerName","setMessage","setMillis"]},{"name":"webdriver.logging.Logger","href":"class_webdriver_logging_Logger.html","namespace":false,"interface":false,"statics":["Logger.ENABLE_HIERARCHY","Logger.ROOT_LOGGER_NAME","Logger.getLogger","Logger.logToProfilers"],"members":["addHandler","config","fine","finer","finest","getChildren","getEffectiveLevel","getLevel","getLogRecord","getName","getParent","info","isLoggable","log","logRecord","removeHandler","setLevel","severe","shout","warning"]},{"name":"webdriver.logging.Preferences","href":"class_webdriver_logging_Preferences.html","namespace":false,"interface":false,"members":["setLevel","toJSON"]},{"name":"webdriver.logging.Type","href":"enum_webdriver_logging_Type.html","namespace":false,"interface":false},{"name":"webdriver.promise","href":"namespace_webdriver_promise.html","namespace":true,"interface":false,"statics":["LONG_STACK_TRACES","all","asap","captureStackTrace","checkedNodeCall","consume","controlFlow","createFlow","defer","delayed","filter","fulfilled","fullyResolved","isGenerator","isPromise","map","rejected","setDefaultFlow","when"]},{"name":"webdriver.promise.CancellationError","href":"class_webdriver_promise_CancellationError.html","namespace":false,"interface":false,"statics":["CancellationError.wrap"]},{"name":"webdriver.promise.ControlFlow","href":"class_webdriver_promise_ControlFlow.html","namespace":false,"interface":false,"members":["execute","getSchedule","reset","timeout","toString","wait"]},{"name":"webdriver.promise.ControlFlow.EventType","href":"enum_webdriver_promise_ControlFlow_EventType.html","namespace":false,"interface":false},{"name":"webdriver.promise.Deferred","href":"class_webdriver_promise_Deferred.html","namespace":false,"interface":false,"members":["promise","cancel","fulfill","isPending","reject","then","thenCatch","thenFinally"]},{"name":"webdriver.promise.Promise","href":"class_webdriver_promise_Promise.html","namespace":false,"interface":false,"members":["cancel","isPending","then","thenCatch","thenFinally","toString"]},{"name":"webdriver.promise.Thenable","href":"interface_webdriver_promise_Thenable.html","namespace":true,"interface":true,"statics":["Thenable.addImplementation","Thenable.isImplementation"],"members":["cancel","isPending","then","thenCatch","thenFinally"]},{"name":"webdriver.stacktrace","href":"namespace_webdriver_stacktrace.html","namespace":true,"interface":false,"statics":["BROWSER_SUPPORTED","format","get","getStack"]},{"name":"webdriver.stacktrace.Frame","href":"class_webdriver_stacktrace_Frame.html","namespace":false,"interface":false,"members":["column","getColumn","getLine","getName","getUrl","isAnonymous","toString"]},{"name":"webdriver.stacktrace.Snapshot","href":"class_webdriver_stacktrace_Snapshot.html","namespace":false,"interface":false,"members":["getStacktrace"]},{"name":"webdriver.testing","href":"namespace_webdriver_testing.html","namespace":true,"interface":false},{"name":"webdriver.testing.Assertion","href":"class_webdriver_testing_Assertion.html","namespace":false,"interface":false,"members":["is","not","apply","closeTo","contains","endsWith","equalTo","greaterThan","greaterThanEqualTo","instanceOf","isFalse","isNull","isNullOrUndefined","isTrue","isUndefined","lessThan","lessThanEqualTo","matches","startsWith"]},{"name":"webdriver.testing.Assertion.DelegatingMatcher_","href":"class_webdriver_testing_Assertion_DelegatingMatcher_.html","namespace":false,"interface":false,"members":["describe","matches"]},{"name":"webdriver.testing.ContainsMatcher","href":"class_webdriver_testing_ContainsMatcher.html","namespace":false,"interface":false,"members":["describe","matches"]},{"name":"webdriver.testing.NegatedAssertion","href":"class_webdriver_testing_NegatedAssertion.html","namespace":false,"interface":false,"members":["value","apply"]},{"name":"webdriver.testing.assert","href":"namespace_webdriver_testing_assert.html","namespace":true,"interface":false,"statics":["register"]},{"name":"webdriver.testing.asserts","href":"namespace_webdriver_testing_asserts.html","namespace":true,"interface":false,"statics":["assertThat","equalTo"]},{"name":"webdriver.until","href":"namespace_webdriver_until.html","namespace":true,"interface":false,"statics":["ableToSwitchToFrame","alertIsPresent","elementIsDisabled","elementIsEnabled","elementIsNotSelected","elementIsNotVisible","elementIsSelected","elementIsVisible","elementLocated","elementTextContains","elementTextIs","elementTextMatches","elementsLocated","stalenessOf","titleContains","titleIs","titleMatches"]},{"name":"webdriver.until.Condition","href":"class_webdriver_until_Condition.html","namespace":false,"interface":false,"members":["description","fn"]}],"modules":[{"name":"selenium-webdriver","href":"module_selenium-webdriver.html","types":[{"name":"WebDriver","href":"module_selenium-webdriver_class_WebDriver.html","namespace":false,"interface":false},{"name":"Serializable","href":"module_selenium-webdriver_class_Serializable.html","namespace":false,"interface":false},{"name":"Capability","href":"module_selenium-webdriver_enum_Capability.html","namespace":false,"interface":false},{"name":"ActionSequence","href":"module_selenium-webdriver_class_ActionSequence.html","namespace":false,"interface":false},{"name":"Builder","href":"module_selenium-webdriver_class_Builder.html","namespace":false,"interface":false},{"name":"promise","href":"module_selenium-webdriver_namespace_promise.html","namespace":true,"interface":false},{"name":"WebElement","href":"module_selenium-webdriver_class_WebElement.html","namespace":false,"interface":false},{"name":"error","href":"module_selenium-webdriver_namespace_error.html","namespace":true,"interface":false},{"name":"WebElementPromise","href":"module_selenium-webdriver_class_WebElementPromise.html","namespace":false,"interface":false},{"name":"FileDetector","href":"module_selenium-webdriver_class_FileDetector.html","namespace":false,"interface":false},{"name":"stacktrace","href":"module_selenium-webdriver_namespace_stacktrace.html","namespace":true,"interface":false},{"name":"Button","href":"module_selenium-webdriver_enum_Button.html","namespace":false,"interface":false},{"name":"Command","href":"module_selenium-webdriver_class_Command.html","namespace":false,"interface":false},{"name":"EventEmitter","href":"module_selenium-webdriver_class_EventEmitter.html","namespace":false,"interface":false},{"name":"Capabilities","href":"module_selenium-webdriver_class_Capabilities.html","namespace":false,"interface":false},{"name":"By","href":"module_selenium-webdriver_namespace_By.html","namespace":true,"interface":false},{"name":"logging","href":"module_selenium-webdriver_namespace_logging.html","namespace":true,"interface":false},{"name":"until","href":"module_selenium-webdriver_namespace_until.html","namespace":true,"interface":false},{"name":"CommandName","href":"module_selenium-webdriver_enum_CommandName.html","namespace":false,"interface":false},{"name":"Key","href":"module_selenium-webdriver_enum_Key.html","namespace":false,"interface":false},{"name":"Browser","href":"module_selenium-webdriver_enum_Browser.html","namespace":false,"interface":false},{"name":"Session","href":"module_selenium-webdriver_class_Session.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/_base","href":"module_selenium-webdriver__base.html","statics":["closure","exportPublicApi","isDevMode","require"],"types":[{"name":"Context","href":"module_selenium-webdriver__base_class_Context.html","namespace":false,"interface":false,"members":["closure"]}]},{"name":"selenium-webdriver/builder","href":"module_selenium-webdriver_builder.html","types":[{"name":"Builder","href":"module_selenium-webdriver_builder_class_Builder.html","namespace":false,"interface":false,"members":["build","disableEnvironmentOverrides","forBrowser","getCapabilities","getServerUrl","getWebDriverProxy","setAlertBehavior","setChromeOptions","setControlFlow","setEnableNativeEvents","setFirefoxOptions","setIeOptions","setLoggingPrefs","setOperaOptions","setProxy","setSafariOptions","setScrollBehavior","usingServer","usingWebDriverProxy","withCapabilities"]}]},{"name":"selenium-webdriver/chrome","href":"module_selenium-webdriver_chrome.html","statics":["getDefaultService","setDefaultService"],"types":[{"name":"Options","href":"module_selenium-webdriver_chrome_class_Options.html","namespace":false,"interface":false,"statics":["Options.fromCapabilities"],"members":["addArguments","addExtensions","androidActivity","androidChrome","androidDeviceSerial","androidPackage","androidProcess","androidUseRunningApp","detachDriver","excludeSwitches","serialize","setChromeBinaryPath","setChromeLogFile","setChromeMinidumpPath","setLocalState","setLoggingPrefs","setMobileEmulation","setPerfLoggingPrefs","setProxy","setUserPreferences","toCapabilities"]},{"name":"Driver","href":"module_selenium-webdriver_chrome_class_Driver.html","namespace":false,"interface":false,"members":["launchApp","setFileDetector"]},{"name":"ServiceBuilder","href":"module_selenium-webdriver_chrome_class_ServiceBuilder.html","namespace":false,"interface":false,"members":["build","enableVerboseLogging","loggingTo","setAdbPort","setNumHttpThreads","setStdio","setUrlBasePath","usingPort","withEnvironment"]}]},{"name":"selenium-webdriver/error","href":"module_selenium-webdriver_error.html","types":[{"name":"Error","href":"module_selenium-webdriver_error_class_Error.html","namespace":false,"interface":false},{"name":"ErrorCode","href":"module_selenium-webdriver_error_enum_ErrorCode.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/executors","href":"module_selenium-webdriver_executors.html","statics":["createExecutor"],"types":[{"name":"DeferredExecutor","href":"module_selenium-webdriver_executors_class_DeferredExecutor.html","namespace":false,"interface":false,"members":["execute"]}]},{"name":"selenium-webdriver/firefox","href":"module_selenium-webdriver_firefox.html","types":[{"name":"Options","href":"module_selenium-webdriver_firefox_class_Options.html","namespace":false,"interface":false,"members":["setBinary","setLoggingPreferences","setProfile","setProxy","toCapabilities"]},{"name":"Driver","href":"module_selenium-webdriver_firefox_class_Driver.html","namespace":false,"interface":false,"members":["quit","setFileDetector"]},{"name":"Binary","href":"module_selenium-webdriver_firefox_class_Binary.html","namespace":false,"interface":false},{"name":"Profile","href":"module_selenium-webdriver_firefox_class_Profile.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/firefox/binary","href":"module_selenium-webdriver_firefox_binary.html","types":[{"name":"Binary","href":"module_selenium-webdriver_firefox_binary_class_Binary.html","namespace":false,"interface":false,"members":["addArguments","kill","launch","serialize"]}]},{"name":"selenium-webdriver/firefox/extension","href":"module_selenium-webdriver_firefox_extension.html","statics":["install"]},{"name":"selenium-webdriver/firefox/profile","href":"module_selenium-webdriver_firefox_profile.html","statics":["decode","loadUserPrefs"],"types":[{"name":"Profile","href":"module_selenium-webdriver_firefox_profile_class_Profile.html","namespace":false,"interface":false,"members":["acceptUntrustedCerts","addExtension","assumeUntrustedCertIssuer","encode","getPort","getPreference","nativeEventsEnabled","serialize","setAcceptUntrustedCerts","setAssumeUntrustedCertIssuer","setNativeEventsEnabled","setPort","setPreference","writeToDisk"]}]},{"name":"selenium-webdriver/http","href":"module_selenium-webdriver_http.html","types":[{"name":"Response","href":"module_selenium-webdriver_http_class_Response.html","namespace":false,"interface":false},{"name":"Executor","href":"module_selenium-webdriver_http_class_Executor.html","namespace":false,"interface":false},{"name":"HttpClient","href":"module_selenium-webdriver_http_class_HttpClient.html","namespace":false,"interface":false,"members":["send"]},{"name":"Request","href":"module_selenium-webdriver_http_class_Request.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/http/util","href":"module_selenium-webdriver_http_util.html","statics":["getStatus","waitForServer","waitForUrl"]},{"name":"selenium-webdriver/ie","href":"module_selenium-webdriver_ie.html","types":[{"name":"Options","href":"module_selenium-webdriver_ie_class_Options.html","namespace":false,"interface":false,"statics":["Options.fromCapabilities"],"members":["addArguments","browserAttachTimeout","enableElementCacheCleanup","enablePersistentHover","ensureCleanSession","forceCreateProcessApi","ignoreZoomSetting","initialBrowserUrl","introduceFlakinessByIgnoringProtectedModeSettings","requireWindowFocus","setExtractPath","setHost","setLogFile","setLogLevel","setProxy","silent","toCapabilities","usePerProcessProxy"]},{"name":"Driver","href":"module_selenium-webdriver_ie_class_Driver.html","namespace":false,"interface":false,"members":["setFileDetector"]},{"name":"Level","href":"module_selenium-webdriver_ie_enum_Level.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/io","href":"module_selenium-webdriver_io.html","statics":["copy","copyDir","exists","findInPath","rmDir","tmpDir","tmpFile","unlink"]},{"name":"selenium-webdriver/io/exec","href":"module_selenium-webdriver_io_exec.html"},{"name":"selenium-webdriver/net","href":"module_selenium-webdriver_net.html","statics":["getAddress","getLoopbackAddress"]},{"name":"selenium-webdriver/net/portprober","href":"module_selenium-webdriver_net_portprober.html","statics":["findFreePort","isFree"]},{"name":"selenium-webdriver/opera","href":"module_selenium-webdriver_opera.html","statics":["getDefaultService","setDefaultService"],"types":[{"name":"Options","href":"module_selenium-webdriver_opera_class_Options.html","namespace":false,"interface":false,"statics":["Options.fromCapabilities"],"members":["addArguments","addExtensions","serialize","setLoggingPrefs","setOperaBinaryPath","setProxy","toCapabilities"]},{"name":"Driver","href":"module_selenium-webdriver_opera_class_Driver.html","namespace":false,"interface":false,"members":["setFileDetector"]},{"name":"ServiceBuilder","href":"module_selenium-webdriver_opera_class_ServiceBuilder.html","namespace":false,"interface":false,"members":["build","enableVerboseLogging","loggingTo","setStdio","silent","usingPort","withEnvironment"]}]},{"name":"selenium-webdriver/phantomjs","href":"module_selenium-webdriver_phantomjs.html","types":[{"name":"Driver","href":"module_selenium-webdriver_phantomjs_class_Driver.html","namespace":false,"interface":false,"members":["executePhantomJS","setFileDetector"]}]},{"name":"selenium-webdriver/proxy","href":"module_selenium-webdriver_proxy.html","statics":["direct","manual","pac","system"]},{"name":"selenium-webdriver/remote","href":"module_selenium-webdriver_remote.html","types":[{"name":"FileDetector","href":"module_selenium-webdriver_remote_class_FileDetector.html","namespace":false,"interface":false,"members":["handleFile"]},{"name":"DriverService","href":"module_selenium-webdriver_remote_class_DriverService.html","namespace":false,"interface":false,"statics":["DriverService.DEFAULT_START_TIMEOUT_MS"],"members":["address","isRunning","kill","start","stop"]},{"name":"SeleniumServer","href":"module_selenium-webdriver_remote_class_SeleniumServer.html","namespace":false,"interface":false},{"name":"SeleniumServer.Options","href":"module_selenium-webdriver_remote_class_SeleniumServer.html#SeleniumServer.Options"}]},{"name":"selenium-webdriver/safari","href":"module_selenium-webdriver_safari.html","types":[{"name":"Options","href":"module_selenium-webdriver_safari_class_Options.html","namespace":false,"interface":false,"statics":["Options.fromCapabilities"],"members":["serialize","setCleanSession","setLoggingPrefs","toCapabilities"]},{"name":"Driver","href":"module_selenium-webdriver_safari_class_Driver.html","namespace":false,"interface":false}]},{"name":"selenium-webdriver/testing","href":"module_selenium-webdriver_testing.html","statics":["after","afterEach","before","beforeEach","describe","ignore","iit","it","xdescribe","xit"]},{"name":"selenium-webdriver/testing/assert","href":"module_selenium-webdriver_testing_assert.html","statics":["register"]}]}; \ No newline at end of file diff --git a/error.js b/error.js index 368bbc2..6e1fdd7 100644 --- a/error.js +++ b/error.js @@ -1,17 +1,19 @@ -// Copyright 2014 Selenium committers -// Copyright 2014 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; diff --git a/example/chrome_android.js b/example/chrome_android.js new file mode 100644 index 0000000..990a4c4 --- /dev/null +++ b/example/chrome_android.js @@ -0,0 +1,38 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview A basic example of working with Chrome on Android. Before + * running this example, you must start adb and connect a device (or start an + * AVD). + */ + +var webdriver = require('..'), + By = webdriver.By, + until = webdriver.until, + chrome = require('../chrome'); + +var driver = new webdriver.Builder() + .forBrowser('chrome') + .setChromeOptions(new chrome.Options().androidChrome()) + .build(); + +driver.get('http://www.google.com/ncr'); +driver.findElement(By.name('q')).sendKeys('webdriver'); +driver.findElement(By.name('btnG')).click(); +driver.wait(until.titleIs('webdriver - Google Search'), 1000); +driver.quit(); diff --git a/example/chrome_mobile_emulation.js b/example/chrome_mobile_emulation.js new file mode 100644 index 0000000..d308112 --- /dev/null +++ b/example/chrome_mobile_emulation.js @@ -0,0 +1,39 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview This is an example of emulating a mobile device using the + * ChromeDriver. + */ + +var webdriver = require('..'), + By = webdriver.By, + until = webdriver.until, + chrome = require('../chrome'); + + +var driver = new webdriver.Builder() + .forBrowser('chrome') + .setChromeOptions(new chrome.Options() + .setMobileEmulation({deviceName: 'Google Nexus 5'})) + .build(); + +driver.get('http://www.google.com/ncr'); +driver.findElement(By.name('q')).sendKeys('webdriver'); +driver.findElement(By.name('btnG')).click(); +driver.wait(until.titleIs('webdriver - Google Search'), 1000); +driver.quit(); diff --git a/example/google_search.js b/example/google_search.js index d09e41d..c624fa2 100644 --- a/example/google_search.js +++ b/example/google_search.js @@ -1,40 +1,50 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview An example WebDriver script. This requires the chromedriver * to be present on the system PATH. - * Usage: node selenium-webdriver/example/google_search.js + * + * Usage: + * // Default behavior + * node selenium-webdriver/example/google_search.js + * + * // Target Chrome locally; the chromedriver must be on your PATH + * SELENIUM_BROWSER=chrome node selenium-webdriver/example/google_search.js + * + * // Use a local copy of the standalone Selenium server + * SELENIUM_SERVER_JAR=/path/to/selenium-server-standalone.jar \ + * node selenium-webdriver/example/google_search.js + * + * // Target a remove Selenium server + * SELENIUM_REMOTE_URL=http://www.example.com:4444/wd/hub \ + * node selenium-webdriver/example/google_search.js */ -var fs = require('fs'); - var webdriver = require('..'), - remote = require('../remote'); - -var driver = new webdriver.Builder(). - withCapabilities(webdriver.Capabilities.chrome()). - build(); + By = webdriver.By, + until = webdriver.until; -driver.get('http://www.google.com'); -driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); -driver.findElement(webdriver.By.name('btnG')).click(); -driver.wait(function() { - return driver.getTitle().then(function(title) { - return 'webdriver - Google Search' === title; - }); -}, 1000); +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); -driver.quit(); +driver.get('http://www.google.com/ncr'); +driver.findElement(By.name('q')).sendKeys('webdriver'); +driver.findElement(By.name('btnG')).click(); +driver.wait(until.titleIs('webdriver - Google Search'), 1000); +driver.quit(); \ No newline at end of file diff --git a/example/google_search_generator.js b/example/google_search_generator.js new file mode 100644 index 0000000..90173bc --- /dev/null +++ b/example/google_search_generator.js @@ -0,0 +1,47 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview An example WebDriver script using Harmony generator functions. + * This requires node v0.11 or newer. + * + * Usage: node --harmony-generators \ + * selenium-webdriver/example/google_search_generator.js + */ + +var webdriver = require('..'), + By = webdriver.By; + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +driver.get('http://www.google.com/ncr'); +driver.call(function* () { + var query = yield driver.findElement(By.name('q')); + query.sendKeys('webdriver'); + + var submit = yield driver.findElement(By.name('btnG')); + submit.click(); +}); + +driver.wait(function* () { + var title = yield driver.getTitle(); + return 'webdriver - Google Search' === title; +}, 1000); + +driver.quit(); diff --git a/example/google_search_test.js b/example/google_search_test.js index 8ed6a7d..823e2c5 100644 --- a/example/google_search_test.js +++ b/example/google_search_test.js @@ -1,50 +1,47 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** - * @fileoverview An example test that may be run using Mocha. To run, you must - * have the chromedriver installed on the system PATH. + * @fileoverview An example test that may be run using Mocha. + * Usage: mocha -t 10000 selenium-webdriver/example/google_search_test.js */ -var assert = require('assert'), - fs = require('fs'); - var webdriver = require('..'), - test = require('../testing'), - remote = require('../remote'); - + By = webdriver.By, + until = webdriver.until, + test = require('../testing'); test.describe('Google Search', function() { var driver; test.before(function() { - driver = new webdriver.Builder(). - withCapabilities(webdriver.Capabilities.chrome()). - build(); + driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); }); test.it('should append query to title', function() { driver.get('http://www.google.com'); - driver.findElement(webdriver.By.name('q')).sendKeys('webdriver'); - driver.findElement(webdriver.By.name('btnG')).click(); - driver.wait(function() { - return driver.getTitle().then(function(title) { - return 'webdriver - Google Search' === title; - }); - }, 1000); + driver.findElement(By.name('q')).sendKeys('webdriver'); + driver.findElement(By.name('btnG')).click(); + driver.wait(until.titleIs('webdriver - Google Search'), 1000); }); - test.after(function() { driver.quit(); }); + test.after(function() { + driver.quit(); + }); }); diff --git a/example/logging.js b/example/logging.js new file mode 100644 index 0000000..ad198f1 --- /dev/null +++ b/example/logging.js @@ -0,0 +1,39 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Demonstrates how to use WebDriver's logging sysem. + */ + +'use strict'; + +var webdriver = require('..'), + By = webdriver.By, + until = webdriver.until; + +webdriver.logging.installConsoleHandler(); +webdriver.logging.getLogger().setLevel(webdriver.logging.Level.ALL); + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +driver.get('http://www.google.com/ncr'); +driver.findElement(By.name('q')).sendKeys('webdriver'); +driver.findElement(By.name('btnG')).click(); +driver.wait(until.titleIs('webdriver - Google Search'), 1000); +driver.quit(); diff --git a/example/parallel_flows.js b/example/parallel_flows.js new file mode 100644 index 0000000..f416922 --- /dev/null +++ b/example/parallel_flows.js @@ -0,0 +1,51 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview An example of starting multiple WebDriver clients that run + * in parallel in separate control flows. + */ + +var webdriver = require('..'), + By = webdriver.By, + until = webdriver.until; + +for (var i = 0; i < 3; i++) { + (function(n) { + var flow = new webdriver.promise.ControlFlow() + .on('uncaughtException', function(e) { + console.log('uncaughtException in flow %d: %s', n, e); + }); + + var driver = new webdriver.Builder(). + forBrowser('firefox'). + setControlFlow(flow). // Comment out this line to see the difference. + build(); + + // Position and resize window so it's easy to see them running together. + driver.manage().window().setSize(600, 400); + driver.manage().window().setPosition(300 * i, 400 * i); + + driver.get('http://www.google.com'); + driver.findElement(By.name('q')).sendKeys('webdriver'); + driver.findElement(By.name('btnG')).click(); + driver.wait(until.titleIs('webdriver - Google Search'), 1000); + + driver.quit(); + })(i); +} + diff --git a/executors.js b/executors.js index 79cf8c1..481040d 100644 --- a/executors.js +++ b/executors.js @@ -1,16 +1,19 @@ -// Copyright 2013 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Various utilities for working with @@ -45,15 +48,19 @@ var DeferredExecutor = function(delegate) { // PUBLIC API +exports.DeferredExecutor = DeferredExecutor; + /** * Creates a command executor that uses WebDriver's JSON wire protocol. * @param {(string|!webdriver.promise.Promise.)} url The server's URL, * or a promise that will resolve to that URL. + * @param {string=} opt_proxy (optional) The URL of the HTTP proxy for the + * client to use. * @returns {!webdriver.CommandExecutor} The new command executor. */ -exports.createExecutor = function(url) { +exports.createExecutor = function(url, opt_proxy) { return new DeferredExecutor(promise.when(url, function(url) { - var client = new HttpClient(url); + var client = new HttpClient(url, null, opt_proxy); return new HttpExecutor(client); })); }; diff --git a/firefox/binary.js b/firefox/binary.js new file mode 100644 index 0000000..843d9e8 --- /dev/null +++ b/firefox/binary.js @@ -0,0 +1,255 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Manages Firefox binaries. This module is considered internal; + * users should use {@link selenium-webdriver/firefox}. + */ + +'use strict'; + +var child = require('child_process'), + fs = require('fs'), + path = require('path'), + util = require('util'); + +var Serializable = require('..').Serializable, + promise = require('..').promise, + _base = require('../_base'), + io = require('../io'), + exec = require('../io/exec'); + + + +/** @const */ +var NO_FOCUS_LIB_X86 = _base.isDevMode() ? + path.join(__dirname, '../../../../cpp/prebuilt/i386/libnoblur.so') : + path.join(__dirname, '../lib/firefox/i386/libnoblur.so') ; + +/** @const */ +var NO_FOCUS_LIB_AMD64 = _base.isDevMode() ? + path.join(__dirname, '../../../../cpp/prebuilt/amd64/libnoblur64.so') : + path.join(__dirname, '../lib/firefox/amd64/libnoblur64.so') ; + +var X_IGNORE_NO_FOCUS_LIB = 'x_ignore_nofocus.so'; + +var foundBinary = null; + + +/** + * Checks the default Windows Firefox locations in Program Files. + * @return {!promise.Promise.} A promise for the located executable. + * The promise will resolve to {@code null} if Fireox was not found. + */ +function defaultWindowsLocation() { + var files = [ + process.env['PROGRAMFILES'] || 'C:\\Program Files', + process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)' + ].map(function(prefix) { + return path.join(prefix, 'Mozilla Firefox\\firefox.exe'); + }); + return io.exists(files[0]).then(function(exists) { + return exists ? files[0] : io.exists(files[1]).then(function(exists) { + return exists ? files[1] : null; + }); + }); +} + + +/** + * Locates the Firefox binary for the current system. + * @return {!promise.Promise.} A promise for the located binary. The + * promise will be rejected if Firefox cannot be located. + */ +function findFirefox() { + if (foundBinary) { + return foundBinary; + } + + if (process.platform === 'darwin') { + var osxExe = '/Applications/Firefox.app/Contents/MacOS/firefox-bin'; + foundBinary = io.exists(osxExe).then(function(exists) { + return exists ? osxExe : null; + }); + } else if (process.platform === 'win32') { + foundBinary = defaultWindowsLocation(); + } else { + foundBinary = promise.fulfilled(io.findInPath('firefox')); + } + + return foundBinary = foundBinary.then(function(found) { + if (found) { + return found; + } + throw Error('Could not locate Firefox on the current system'); + }); +} + + +/** + * Copies the no focus libs into the given profile directory. + * @param {string} profileDir Path to the profile directory to install into. + * @return {!promise.Promise.} The LD_LIBRARY_PATH prefix string to use + * for the installed libs. + */ +function installNoFocusLibs(profileDir) { + var x86 = path.join(profileDir, 'x86'); + var amd64 = path.join(profileDir, 'amd64'); + + return mkdir(x86) + .then(copyLib.bind(null, NO_FOCUS_LIB_X86, x86)) + .then(mkdir.bind(null, amd64)) + .then(copyLib.bind(null, NO_FOCUS_LIB_AMD64, amd64)) + .then(function() { + return x86 + ':' + amd64; + }); + + function mkdir(dir) { + return io.exists(dir).then(function(exists) { + if (!exists) { + return promise.checkedNodeCall(fs.mkdir, dir); + } + }); + } + + function copyLib(src, dir) { + return io.copy(src, path.join(dir, X_IGNORE_NO_FOCUS_LIB)); + } +} + + +/** + * Manages a Firefox subprocess configured for use with WebDriver. + * + * @param {string=} opt_exe Path to the Firefox binary to use. If not + * specified, will attempt to locate Firefox on the current system. + * @constructor + * @extends {Serializable.} + */ +var Binary = function(opt_exe) { + Serializable.call(this); + + /** @private {(string|undefined)} */ + this.exe_ = opt_exe; + + /** @private {!Array.} */ + this.args_ = []; + + /** @private {!Object.} */ + this.env_ = {}; + Object.keys(process.env).forEach(function(key) { + this.env_[key] = process.env[key]; + }.bind(this)); + this.env_['MOZ_CRASHREPORTER_DISABLE'] = '1'; + this.env_['MOZ_NO_REMOTE'] = '1'; + this.env_['NO_EM_RESTART'] = '1'; + + /** @private {promise.Promise.} */ + this.command_ = null; +}; +util.inherits(Binary, Serializable); + + +/** + * Add arguments to the command line used to start Firefox. + * @param {...(string|!Array.)} var_args Either the arguments to add as + * varargs, or the arguments as an array. + */ +Binary.prototype.addArguments = function(var_args) { + for (var i = 0; i < arguments.length; i++) { + if (util.isArray(arguments[i])) { + this.args_ = this.args_.concat(arguments[i]); + } else { + this.args_.push(arguments[i]); + } + } +}; + + +/** + * Launches Firefox and eturns a promise that will be fulfilled when the process + * terminates. + * @param {string} profile Path to the profile directory to use. + * @return {!promise.Promise.} A promise for the process result. + * @throws {Error} If this instance has already been started. + */ +Binary.prototype.launch = function(profile) { + if (this.command_) { + throw Error('Firefox is already running'); + } + + var env = {}; + Object.keys(this.env_).forEach(function(key) { + env[key] = this.env_[key]; + }.bind(this)); + env['XRE_PROFILE_PATH'] = profile; + + var args = ['-foreground'].concat(this.args_); + + this.command_ = promise.when(this.exe_ || findFirefox(), function(firefox) { + if (process.platform === 'win32' || process.platform === 'darwin') { + return exec(firefox, {args: args, env: env}); + } + return installNoFocusLibs(profile).then(function(ldLibraryPath) { + env['LD_LIBRARY_PATH'] = ldLibraryPath + ':' + env['LD_LIBRARY_PATH']; + env['LD_PRELOAD'] = X_IGNORE_NO_FOCUS_LIB; + return exec(firefox, {args: args, env: env}); + }); + }); + + return this.command_.then(function() { + // Don't return the actual command handle, just a promise to signal it has + // been started. + }); +}; + + +/** + * Kills the managed Firefox process. + * @return {!promise.Promise} A promise for when the process has terminated. + */ +Binary.prototype.kill = function() { + if (!this.command_) { + return promise.defer(); // Not running. + } + return this.command_.then(function(command) { + command.kill(); + return command.result(); + }); +}; + + +/** + * Returns a promise for the wire representation of this binary. Note: the + * FirefoxDriver only supports passing the path to the binary executable over + * the wire; all command line arguments and environment variables will be + * discarded. + * + * @return {!promise.Promise.} A promise for this binary's wire + * representation. + * @override + */ +Binary.prototype.serialize = function() { + return promise.when(this.exe_ || findFirefox()); +}; + + +// PUBLIC API + + +exports.Binary = Binary; + diff --git a/firefox/extension.js b/firefox/extension.js new file mode 100644 index 0000000..9feb05b --- /dev/null +++ b/firefox/extension.js @@ -0,0 +1,186 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** @fileoverview Utilities for working with Firefox extensions. */ + +'use strict'; + +var AdmZip = require('adm-zip'), + fs = require('fs'), + path = require('path'), + util = require('util'), + xml = require('xml2js'); + +var promise = require('..').promise, + checkedCall = promise.checkedNodeCall, + io = require('../io'); + + +/** + * Thrown when there an add-on is malformed. + * @param {string} msg The error message. + * @constructor + * @extends {Error} + */ +function AddonFormatError(msg) { + Error.call(this); + + Error.captureStackTrace(this, AddonFormatError); + + /** @override */ + this.name = AddonFormatError.name; + + /** @override */ + this.message = msg; +} +util.inherits(AddonFormatError, Error); + + + +/** + * Installs an extension to the given directory. + * @param {string} extension Path to the extension to install, as either a xpi + * file or a directory. + * @param {string} dir Path to the directory to install the extension in. + * @return {!promise.Promise.} A promise for the add-on ID once + * installed. + */ +function install(extension, dir) { + return getDetails(extension).then(function(details) { + function returnId() { return details.id; } + + var dst = path.join(dir, details.id); + if (extension.slice(-4) === '.xpi') { + if (!details.unpack) { + return io.copy(extension, dst + '.xpi').then(returnId); + } else { + return checkedCall(fs.readFile, extension).then(function(buff) { + var zip = new AdmZip(buff); + // TODO: find an async library for inflating a zip archive. + new AdmZip(buff).extractAllTo(dst, true); + }).then(returnId); + } + } else { + return io.copyDir(extension, dst).then(returnId); + } + }); +} + + +/** + * Describes a Firefox add-on. + * @typedef {{id: string, name: string, version: string, unpack: boolean}} + */ +var AddonDetails; + + +/** + * Extracts the details needed to install an add-on. + * @param {string} addonPath Path to the extension directory. + * @return {!promise.Promise.} A promise for the add-on details. + */ +function getDetails(addonPath) { + return readManifest(addonPath).then(function(doc) { + var em = getNamespaceId(doc, 'http://www.mozilla.org/2004/em-rdf#'); + var rdf = getNamespaceId( + doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + + var description = doc[rdf + 'RDF'][rdf + 'Description'][0]; + var details = { + id: getNodeText(description, em + 'id'), + name: getNodeText(description, em + 'name'), + version: getNodeText(description, em + 'version'), + unpack: getNodeText(description, em + 'unpack') || false + }; + + if (typeof details.unpack === 'string') { + details.unpack = details.unpack.toLowerCase() === 'true'; + } + + if (!details.id) { + throw new AddonFormatError('Could not find add-on ID for ' + addonPath); + } + + return details; + }); + + function getNodeText(node, name) { + return node[name] && node[name][0] || ''; + } + + function getNamespaceId(doc, url) { + var keys = Object.keys(doc); + if (keys.length !== 1) { + throw new AddonFormatError('Malformed manifest for add-on ' + addonPath); + } + + var namespaces = doc[keys[0]].$; + var id = ''; + Object.keys(namespaces).some(function(ns) { + if (namespaces[ns] !== url) { + return false; + } + + if (ns.indexOf(':') != -1) { + id = ns.split(':')[1] + ':'; + } + return true; + }); + return id; + } +} + + +/** + * Reads the manifest for a Firefox add-on. + * @param {string} addonPath Path to a Firefox add-on as a xpi or an extension. + * @return {!promise.Promise.} A promise for the parsed manifest. + */ +function readManifest(addonPath) { + var manifest; + + if (addonPath.slice(-4) === '.xpi') { + manifest = checkedCall(fs.readFile, addonPath).then(function(buff) { + var zip = new AdmZip(buff); + if (!zip.getEntry('install.rdf')) { + throw new AddonFormatError( + 'Could not find install.rdf in ' + addonPath); + } + var done = promise.defer(); + zip.readAsTextAsync('install.rdf', done.fulfill); + return done.promise; + }); + } else { + manifest = checkedCall(fs.stat, addonPath).then(function(stats) { + if (!stats.isDirectory()) { + throw Error( + 'Add-on path is niether a xpi nor a directory: ' + addonPath); + } + return checkedCall(fs.readFile, path.join(addonPath, 'install.rdf')); + }); + } + + return manifest.then(function(content) { + return checkedCall(xml.parseString, content); + }); +} + + +// PUBLIC API + + +exports.install = install; diff --git a/firefox/index.js b/firefox/index.js new file mode 100644 index 0000000..82db486 --- /dev/null +++ b/firefox/index.js @@ -0,0 +1,316 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines the {@linkplain Driver WebDriver} client for Firefox. + * Each FirefoxDriver instance will be created with an anonymous profile, + * ensuring browser historys do not share session data (cookies, history, cache, + * offline storage, etc.) + * + * __Customizing the Firefox Profile__ + * + * The {@link Profile} class may be used to configure the browser profile used + * with WebDriver, with functions to install additional + * {@linkplain Profile#addExtension extensions}, configure browser + * {@linkplain Profile#setPreference preferences}, and more. For example, you + * may wish to include Firebug: + * + * var firefox = require('selenium-webdriver/firefox'); + * + * var profile = new firefox.Profile(); + * profile.addExtension('/path/to/firebug.xpi'); + * profile.setPreference('extensions.firebug.showChromeErrors', true); + * + * var options = new firefox.Options().setProfile(profile); + * var driver = new firefox.Driver(options); + * + * The {@link Profile} class may also be used to configure WebDriver based on a + * pre-existing browser profile: + * + * var profile = new firefox.Profile( + * '/usr/local/home/bob/.mozilla/firefox/3fgog75h.testing'); + * var options = new firefox.Options().setProfile(profile); + * var driver = new firefox.Driver(options); + * + * The FirefoxDriver will _never_ modify a pre-existing profile; instead it will + * create a copy for it to modify. By extension, there are certain browser + * preferences that are required for WebDriver to function properly and they + * will always be overwritten. + * + * __Using a Custom Firefox Binary__ + * + * On Windows and OSX, the FirefoxDriver will search for Firefox in its + * default installation location: + * + * * Windows: C:\Program Files and C:\Program Files (x86). + * * Mac OS X: /Applications/Firefox.app + * + * For Linux, Firefox will be located on the PATH: `$(where firefox)`. + * + * You can configure WebDriver to start use a custom Firefox installation with + * the {@link Binary} class: + * + * var firefox = require('selenium-webdriver/firefox'); + * var binary = new firefox.Binary('/my/firefox/install/dir/firefox-bin'); + * var options = new firefox.Options().setBinary(binary); + * var driver = new firefox.Driver(options); + * + * __Remote Testing__ + * + * You may customize the Firefox binary and profile when running against a + * remote Selenium server. Your custom profile will be packaged as a zip and + * transfered to the remote host for use. The profile will be transferred + * _once for each new session_. The performance impact should be minimal if + * you've only configured a few extra browser preferences. If you have a large + * profile with several extensions, you should consider installing it on the + * remote host and defining its path via the {@link Options} class. Custom + * binaries are never copied to remote machines and must be referenced by + * installation path. + * + * var options = new firefox.Options() + * .setProfile('/profile/path/on/remote/host') + * .setBinary('/install/dir/on/remote/host/firefox-bin'); + * + * var driver = new (require('selenium-webdriver')).Builder() + * .forBrowser('firefox') + * .usingServer('http://127.0.0.1:4444/wd/hub') + * .setFirefoxOptions(options) + * .build(); + */ + +'use strict'; + +var url = require('url'), + util = require('util'); + +var Binary = require('./binary').Binary, + Profile = require('./profile').Profile, + decodeProfile = require('./profile').decode, + webdriver = require('..'), + executors = require('../executors'), + httpUtil = require('../http/util'), + io = require('../io'), + net = require('../net'), + portprober = require('../net/portprober'); + + +/** + * Configuration options for the FirefoxDriver. + * @constructor + */ +var Options = function() { + /** @private {Profile} */ + this.profile_ = null; + + /** @private {Binary} */ + this.binary_ = null; + + /** @private {webdriver.logging.Preferences} */ + this.logPrefs_ = null; + + /** @private {webdriver.ProxyConfig} */ + this.proxy_ = null; +}; + + +/** + * Sets the profile to use. The profile may be specified as a + * {@link Profile} object or as the path to an existing Firefox profile to use + * as a template. + * + * @param {(string|!Profile)} profile The profile to use. + * @return {!Options} A self reference. + */ +Options.prototype.setProfile = function(profile) { + if (typeof profile === 'string') { + profile = new Profile(profile); + } + this.profile_ = profile; + return this; +}; + + +/** + * Sets the binary to use. The binary may be specified as the path to a Firefox + * executable, or as a {@link Binary} object. + * + * @param {(string|!Binary)} binary The binary to use. + * @return {!Options} A self reference. + */ +Options.prototype.setBinary = function(binary) { + if (typeof binary === 'string') { + binary = new Binary(binary); + } + this.binary_ = binary; + return this; +}; + + +/** + * Sets the logging preferences for the new session. + * @param {webdriver.logging.Preferences} prefs The logging preferences. + * @return {!Options} A self reference. + */ +Options.prototype.setLoggingPreferences = function(prefs) { + this.logPrefs_ = prefs; + return this; +}; + + +/** + * Sets the proxy to use. + * + * @param {webdriver.ProxyConfig} proxy The proxy configuration to use. + * @return {!Options} A self reference. + */ +Options.prototype.setProxy = function(proxy) { + this.proxy_ = proxy; + return this; +}; + + +/** + * Converts these options to a {@link webdriver.Capabilities} instance. + * + * @return {!webdriver.Capabilities} A new capabilities object. + */ +Options.prototype.toCapabilities = function(opt_remote) { + var caps = webdriver.Capabilities.firefox(); + if (this.logPrefs_) { + caps.set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_); + } + if (this.proxy_) { + caps.set(webdriver.Capability.PROXY, this.proxy_); + } + if (this.binary_) { + caps.set('firefox_binary', this.binary_); + } + if (this.profile_) { + caps.set('firefox_profile', this.profile_); + } + return caps; +}; + + +/** + * A WebDriver client for Firefox. + * + * @param {(Options|webdriver.Capabilities|Object)=} opt_config The + * configuration options for this driver, specified as either an + * {@link Options} or {@link webdriver.Capabilities}, or as a raw hash + * object. + * @param {webdriver.promise.ControlFlow=} opt_flow The flow to + * schedule commands through. Defaults to the active flow object. + * @constructor + * @extends {webdriver.WebDriver} + */ +var Driver = function(opt_config, opt_flow) { + var caps; + if (opt_config instanceof Options) { + caps = opt_config.toCapabilities(); + } else { + caps = new webdriver.Capabilities(opt_config); + } + + var binary = caps.get('firefox_binary') || new Binary(); + if (typeof binary === 'string') { + binary = new Binary(binary); + } + + var profile = caps.get('firefox_profile') || new Profile(); + + caps.set('firefox_binary', null); + caps.set('firefox_profile', null); + + /** @private {?string} */ + this.profilePath_ = null; + + /** @private {!Binary} */ + this.binary_ = binary; + + var self = this; + var serverUrl = portprober.findFreePort().then(function(port) { + var prepareProfile; + if (typeof profile === 'string') { + prepareProfile = decodeProfile(profile).then(function(dir) { + var profile = new Profile(dir); + profile.setPreference('webdriver_firefox_port', port); + return profile.writeToDisk(); + }); + } else { + profile.setPreference('webdriver_firefox_port', port); + prepareProfile = profile.writeToDisk(); + } + + return prepareProfile.then(function(dir) { + self.profilePath_ = dir; + return binary.launch(dir); + }).then(function() { + var serverUrl = url.format({ + protocol: 'http', + hostname: net.getLoopbackAddress(), + port: port, + pathname: '/hub' + }); + + return httpUtil.waitForServer(serverUrl, 45 * 1000).then(function() { + return serverUrl; + }); + }); + }); + + var executor = executors.createExecutor(serverUrl); + var driver = webdriver.WebDriver.createSession(executor, caps, opt_flow); + + webdriver.WebDriver.call(this, driver.getSession(), executor, opt_flow); +}; +util.inherits(Driver, webdriver.WebDriver); + + +/** + * This function is a no-op as file detectors are not supported by this + * implementation. + * @override + */ +Driver.prototype.setFileDetector = function() { +}; + + +/** @override */ +Driver.prototype.quit = function() { + return this.call(function() { + var self = this; + return Driver.super_.prototype.quit.call(this) + .thenFinally(function() { + return self.binary_.kill(); + }) + .thenFinally(function() { + if (self.profilePath_) { + return io.rmDir(self.profilePath_); + } + }); + }, this); +}; + + +// PUBLIC API + + +exports.Binary = Binary; +exports.Driver = Driver; +exports.Options = Options; +exports.Profile = Profile; diff --git a/firefox/profile.js b/firefox/profile.js new file mode 100644 index 0000000..ca5b6f7 --- /dev/null +++ b/firefox/profile.js @@ -0,0 +1,431 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Profile management module. This module is considered internal; + * users should use {@link selenium-webdriver/firefox}. + */ + +'use strict'; + +var AdmZip = require('adm-zip'), + fs = require('fs'), + path = require('path'), + util = require('util'), + vm = require('vm'); + +var Serializable = require('..').Serializable, + promise = require('..').promise, + _base = require('../_base'), + io = require('../io'), + extension = require('./extension'); + + +/** @const */ +var WEBDRIVER_PREFERENCES_PATH = _base.isDevMode() + ? path.join(__dirname, '../../../firefox-driver/webdriver.json') + : path.join(__dirname, '../lib/firefox/webdriver.json'); + +/** @const */ +var WEBDRIVER_EXTENSION_PATH = _base.isDevMode() + ? path.join(__dirname, + '../../../../build/javascript/firefox-driver/webdriver.xpi') + : path.join(__dirname, '../lib/firefox/webdriver.xpi'); + +/** @const */ +var WEBDRIVER_EXTENSION_NAME = 'fxdriver@googlecode.com'; + + + +/** @type {Object} */ +var defaultPreferences = null; + +/** + * Synchronously loads the default preferences used for the FirefoxDriver. + * @return {!Object} The default preferences JSON object. + */ +function getDefaultPreferences() { + if (!defaultPreferences) { + var contents = fs.readFileSync(WEBDRIVER_PREFERENCES_PATH, 'utf8'); + defaultPreferences = JSON.parse(contents); + } + return defaultPreferences; +} + + +/** + * Parses a user.js file in a Firefox profile directory. + * @param {string} f Path to the file to parse. + * @return {!promise.Promise.} A promise for the parsed preferences as + * a JSON object. If the file does not exist, an empty object will be + * returned. + */ +function loadUserPrefs(f) { + var done = promise.defer(); + fs.readFile(f, function(err, contents) { + if (err && err.code === 'ENOENT') { + done.fulfill({}); + return; + } + + if (err) { + done.reject(err); + return; + } + + var prefs = {}; + var context = vm.createContext({ + 'user_pref': function(key, value) { + prefs[key] = value; + } + }); + + vm.runInContext(contents, context, f); + done.fulfill(prefs); + }); + return done.promise; +} + + +/** + * Copies the properties of one object into another. + * @param {!Object} a The destination object. + * @param {!Object} b The source object to apply as a mixin. + */ +function mixin(a, b) { + Object.keys(b).forEach(function(key) { + a[key] = b[key]; + }); +} + + +/** + * @param {!Object} defaults The default preferences to write. Will be + * overridden by user.js preferences in the template directory and the + * frozen preferences required by WebDriver. + * @param {string} dir Path to the directory write the file to. + * @return {!promise.Promise.} A promise for the profile directory, + * to be fulfilled when user preferences have been written. + */ +function writeUserPrefs(prefs, dir) { + var userPrefs = path.join(dir, 'user.js'); + return loadUserPrefs(userPrefs).then(function(overrides) { + mixin(prefs, overrides); + mixin(prefs, getDefaultPreferences()['frozen']); + + var contents = Object.keys(prefs).map(function(key) { + return 'user_pref(' + JSON.stringify(key) + ', ' + + JSON.stringify(prefs[key]) + ');'; + }).join('\n'); + + var done = promise.defer(); + fs.writeFile(userPrefs, contents, function(err) { + err && done.reject(err) || done.fulfill(dir); + }); + return done.promise; + }); +}; + + +/** + * Installs a group of extensions in the given profile directory. If the + * WebDriver extension is not included in this set, the default version + * bundled with this package will be installed. + * @param {!Array.} extensions The extensions to install, as a + * path to an unpacked extension directory or a path to a xpi file. + * @param {string} dir The profile directory to install to. + * @param {boolean=} opt_excludeWebDriverExt Whether to skip installation of + * the default WebDriver extension. + * @return {!promise.Promise.} A promise for the main profile directory + * once all extensions have been installed. + */ +function installExtensions(extensions, dir, opt_excludeWebDriverExt) { + var hasWebDriver = !!opt_excludeWebDriverExt; + var next = 0; + var extensionDir = path.join(dir, 'extensions'); + var done = promise.defer(); + + return io.exists(extensionDir).then(function(exists) { + if (!exists) { + return promise.checkedNodeCall(fs.mkdir, extensionDir); + } + }).then(function() { + installNext(); + return done.promise; + }); + + function installNext() { + if (!done.isPending()) { + return; + } + + if (next >= extensions.length) { + if (hasWebDriver) { + done.fulfill(dir); + } else { + install(WEBDRIVER_EXTENSION_PATH); + } + } else { + install(extensions[next++]); + } + } + + function install(ext) { + extension.install(ext, extensionDir).then(function(id) { + hasWebDriver = hasWebDriver || (id === WEBDRIVER_EXTENSION_NAME); + installNext(); + }, done.reject); + } +} + + +/** + * Decodes a base64 encoded profile. + * @param {string} data The base64 encoded string. + * @return {!promise.Promise.} A promise for the path to the decoded + * profile directory. + */ +function decode(data) { + return io.tmpFile().then(function(file) { + var buf = new Buffer(data, 'base64'); + return promise.checkedNodeCall(fs.writeFile, file, buf).then(function() { + return io.tmpDir(); + }).then(function(dir) { + var zip = new AdmZip(file); + zip.extractAllTo(dir); // Sync only? Why?? :-( + return dir; + }); + }); +} + + + +/** + * Models a Firefox proifle directory for use with the FirefoxDriver. The + * {@code Proifle} directory uses an in-memory model until {@link #writeToDisk} + * is called. + * @param {string=} opt_dir Path to an existing Firefox profile directory to + * use a template for this profile. If not specified, a blank profile will + * be used. + * @constructor + * @extends {Serializable.} + */ +var Profile = function(opt_dir) { + Serializable.call(this); + + /** @private {!Object} */ + this.preferences_ = {}; + + mixin(this.preferences_, getDefaultPreferences()['mutable']); + mixin(this.preferences_, getDefaultPreferences()['frozen']); + + /** @private {boolean} */ + this.nativeEventsEnabled_ = true; + + /** @private {(string|undefined)} */ + this.template_ = opt_dir; + + /** @private {number} */ + this.port_ = 0; + + /** @private {!Array.} */ + this.extensions_ = []; +}; +util.inherits(Profile, Serializable); + + +/** + * Registers an extension to be included with this profile. + * @param {string} extension Path to the extension to include, as either an + * unpacked extension directory or the path to a xpi file. + */ +Profile.prototype.addExtension = function(extension) { + this.extensions_.push(extension); +}; + + +/** + * Sets a desired preference for this profile. + * @param {string} key The preference key. + * @param {(string|number|boolean)} value The preference value. + * @throws {Error} If attempting to set a frozen preference. + */ +Profile.prototype.setPreference = function(key, value) { + var frozen = getDefaultPreferences()['frozen']; + if (frozen.hasOwnProperty(key) && frozen[key] !== value) { + throw Error('You may not set ' + key + '=' + JSON.stringify(value) + + '; value is frozen for proper WebDriver functionality (' + + key + '=' + JSON.stringify(frozen[key]) + ')'); + } + this.preferences_[key] = value; +}; + + +/** + * Returns the currently configured value of a profile preference. This does + * not include any defaults defined in the profile's template directory user.js + * file (if a template were specified on construction). + * @param {string} key The desired preference. + * @return {(string|number|boolean|undefined)} The current value of the + * requested preference. + */ +Profile.prototype.getPreference = function(key) { + return this.preferences_[key]; +}; + + +/** + * @return {number} The port this profile is currently configured to use, or + * 0 if the port will be selected at random when the profile is written + * to disk. + */ +Profile.prototype.getPort = function() { + return this.port_; +}; + + +/** + * Sets the port to use for the WebDriver extension loaded by this profile. + * @param {number} port The desired port, or 0 to use any free port. + */ +Profile.prototype.setPort = function(port) { + this.port_ = port; +}; + + +/** + * @return {boolean} Whether the FirefoxDriver is configured to automatically + * accept untrusted SSL certificates. + */ +Profile.prototype.acceptUntrustedCerts = function() { + return !!this.preferences_['webdriver_accept_untrusted_certs']; +}; + + +/** + * Sets whether the FirefoxDriver should automatically accept untrusted SSL + * certificates. + * @param {boolean} value . + */ +Profile.prototype.setAcceptUntrustedCerts = function(value) { + this.preferences_['webdriver_accept_untrusted_certs'] = !!value; +}; + + +/** + * Sets whether to assume untrusted certificates come from untrusted issuers. + * @param {boolean} value . + */ +Profile.prototype.setAssumeUntrustedCertIssuer = function(value) { + this.preferences_['webdriver_assume_untrusted_issuer'] = !!value; +}; + + +/** + * @return {boolean} Whether to assume untrusted certs come from untrusted + * issuers. + */ +Profile.prototype.assumeUntrustedCertIssuer = function() { + return !!this.preferences_['webdriver_assume_untrusted_issuer']; +}; + + +/** + * Sets whether to use native events with this profile. + * @param {boolean} enabled . + */ +Profile.prototype.setNativeEventsEnabled = function(enabled) { + this.nativeEventsEnabled_ = enabled; +}; + + +/** + * Returns whether native events are enabled in this profile. + * @return {boolean} . + */ +Profile.prototype.nativeEventsEnabled = function() { + return this.nativeEventsEnabled_; +}; + + +/** + * Writes this profile to disk. + * @param {boolean=} opt_excludeWebDriverExt Whether to exclude the WebDriver + * extension from the generated profile. Used to reduce the size of an + * {@link #encode() encoded profile} since the server will always install + * the extension itself. + * @return {!promise.Promise.} A promise for the path to the new + * profile directory. + */ +Profile.prototype.writeToDisk = function(opt_excludeWebDriverExt) { + var profileDir = io.tmpDir(); + if (this.template_) { + profileDir = profileDir.then(function(dir) { + return io.copyDir( + this.template_, dir, /(parent\.lock|lock|\.parentlock)/); + }.bind(this)); + } + + // Freeze preferences for async operations. + var prefs = {}; + mixin(prefs, this.preferences_); + + // Freeze extensions for async operations. + var extensions = this.extensions_.concat(); + + return profileDir.then(function(dir) { + return writeUserPrefs(prefs, dir); + }).then(function(dir) { + return installExtensions(extensions, dir, !!opt_excludeWebDriverExt); + }); +}; + + +/** + * Encodes this profile as a zipped, base64 encoded directory. + * @return {!promise.Promise.} A promise for the encoded profile. + */ +Profile.prototype.encode = function() { + return this.writeToDisk(true).then(function(dir) { + var zip = new AdmZip(); + zip.addLocalFolder(dir, ''); + return io.tmpFile().then(function(file) { + zip.writeZip(file); // Sync! Why oh why :-( + return promise.checkedNodeCall(fs.readFile, file); + }); + }).then(function(data) { + return new Buffer(data).toString('base64'); + }); +}; + + +/** + * Encodes this profile as a zipped, base64 encoded directory. + * @return {!promise.Promise.} A promise for the encoded profile. + * @override + */ +Profile.prototype.serialize = function() { + return this.encode(); +}; + + +// PUBLIC API + + +exports.Profile = Profile; +exports.decode = decode; +exports.loadUserPrefs = loadUserPrefs; diff --git a/http/index.js b/http/index.js index cb4f7a1..a636f97 100644 --- a/http/index.js +++ b/http/index.js @@ -1,19 +1,22 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** - * @fileoverview Defines a the {@code webdriver.http.Client} for use with + * @fileoverview Defines the {@code webdriver.http.Client} for use with * NodeJS. */ @@ -26,20 +29,29 @@ var base = require('../_base'), var KeepAliveAgent = require('keep-alive-agent'), agent = new KeepAliveAgent(); - /** * A {@link webdriver.http.Client} implementation using Node's built-in http * module. * @param {string} serverUrl URL for the WebDriver server to send commands to. + * @param {http.Agent=} opt_agent The agent to use for each request. + * Defaults to {@code http.globalAgent}. + * @param {string=} opt_proxy The proxy to use for the connection to the server. + * Default is to use no proxy. * @constructor * @implements {webdriver.http.Client} */ -var HttpClient = function(serverUrl) { +var HttpClient = function(serverUrl, opt_agent, opt_proxy) { var parsedUrl = url.parse(serverUrl); if (!parsedUrl.hostname) { throw new Error('Invalid server URL: ' + serverUrl); } + /** @private {http.Agent} */ + this.agent_ = opt_agent; + + /** @private {string} */ + this.proxy_ = opt_proxy; + /** * Base options for each request. * @private {!Object} @@ -69,14 +81,20 @@ HttpClient.prototype.send = function(httpRequest, callback) { path += httpRequest.path; } - sendRequest({ + var options = { method: httpRequest.method, host: this.options_.host, port: this.options_.port, path: path, headers: httpRequest.headers, agent: agent - }, callback, data); + }; + + if (this.agent_) { + options.agent = this.agent_; + } + + sendRequest(options, callback, data, this.proxy_); }; @@ -86,15 +104,40 @@ HttpClient.prototype.send = function(httpRequest, callback) { * @param {function(Error, !webdriver.http.Response=)} callback The function to * invoke with the server's response. * @param {string=} opt_data The data to send with the request. + * @param {string=} opt_proxy The proxy server to use for the request. */ -var sendRequest = function(options, callback, opt_data) { +var sendRequest = function(options, callback, opt_data, opt_proxy) { + var host = options.host; + var port = options.port; + + if (opt_proxy) { + var proxy = url.parse(opt_proxy); + + options.headers['Host'] = options.host; + options.host = proxy.hostname; + options.port = proxy.port; + + if (proxy.auth) { + options.headers['Proxy-Authorization'] = + 'Basic ' + new Buffer(proxy.auth).toString('base64'); + } + } + var request = http.request(options, function(response) { if (response.statusCode == 302 || response.statusCode == 303) { - var location = url.parse(response.headers['location']); + try { + var location = url.parse(response.headers['location']); + } catch (ex) { + callback(Error( + 'Failed to parse "Location" header for server redirect: ' + + ex.message + '\nResponse was: \n' + + new HttpResponse(response.statusCode, response.headers, ''))); + return; + } if (!location.hostname) { - location.hostname = options.host; - location.port = options.port; + location.hostname = host; + location.port = port; } request.abort(); @@ -106,7 +149,7 @@ var sendRequest = function(options, callback, opt_data) { headers: { 'Accept': 'application/json; charset=utf-8' } - }, callback); + }, callback, undefined, opt_proxy); return; } @@ -122,7 +165,7 @@ var sendRequest = function(options, callback, opt_data) { request.on('error', function(e) { if (e.code === 'ECONNRESET') { setTimeout(function() { - sendRequest(options, callback, opt_data); + sendRequest(options, callback, opt_data, opt_proxy); }, 15); } else { var message = e.message; diff --git a/http/util.js b/http/util.js index fcb2219..fa35714 100644 --- a/http/util.js +++ b/http/util.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Various HTTP utilities. diff --git a/ie.js b/ie.js new file mode 100644 index 0000000..8aca222 --- /dev/null +++ b/ie.js @@ -0,0 +1,465 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines a {@linkplain Driver WebDriver} client for Microsoft's + * Internet Explorer. Before using the IEDriver, you must download the latest + * [IEDriverServer](http://selenium-release.storage.googleapis.com/index.html) + * and place it on your + * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29). You must also apply + * the system configuration outlined on the Selenium project + * [wiki](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver) + */ + +'use strict'; + +var fs = require('fs'), + util = require('util'); + +var webdriver = require('./index'), + executors = require('./executors'), + io = require('./io'), + portprober = require('./net/portprober'), + remote = require('./remote'); + + +/** + * @const + * @final + */ +var IEDRIVER_EXE = 'IEDriverServer.exe'; + + + +/** + * IEDriverServer logging levels. + * @enum {string} + */ +var Level = { + FATAL: 'FATAL', + ERROR: 'ERROR', + WARN: 'WARN', + INFO: 'INFO', + DEBUG: 'DEBUG', + TRACE: 'TRACE' +}; + + + +/** + * Option keys: + * https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#ie-specific + * @enum {string} + */ +var Key = { + IGNORE_PROTECTED_MODE_SETTINGS: 'ignoreProtectedModeSettings', + IGNORE_ZOOM_SETTING: 'ignoreZoomSetting', + INITIAL_BROWSER_URL: 'initialBrowserUrl', + ENABLE_PERSISTENT_HOVER: 'enablePersistentHover', + ENABLE_ELEMENT_CACHE_CLEANUP: 'enableElementCacheCleanup', + REQUIRE_WINDOW_FOCUS: 'requireWindowFocus', + BROWSER_ATTACH_TIMEOUT: 'browserAttachTimeout', + FORCE_CREATE_PROCESS: 'ie.forceCreateProcessApi', + BROWSER_COMMAND_LINE_SWITCHES: 'ie.browserCommandLineSwitches', + USE_PER_PROCESS_PROXY: 'ie.usePerProcessProxy', + ENSURE_CLEAN_SESSION: 'ie.ensureCleanSession', + LOG_FILE: 'logFile', + LOG_LEVEL: 'logLevel', + HOST: 'host', + EXTRACT_PATH: 'extractPath', + SILENT: 'silent' +}; + + +/** + * Class for managing IEDriver specific options. + * @constructor + */ +var Options = function() { + /** @private {!Object<(boolean|number|string)>} */ + this.options_ = {}; + + /** @private {(webdriver.ProxyConfig|null)} */ + this.proxy_ = null; +}; + + + +/** + * Extracts the IEDriver specific options from the given capabilities + * object. + * @param {!webdriver.Capabilities} capabilities The capabilities object. + * @return {!Options} The IEDriver options. + */ +Options.fromCapabilities = function(capabilities) { + var options = new Options(); + var map = options.options_; + + Object.keys(Key).forEach(function(key) { + key = Key[key]; + if (capabilities.has(key)) { + map[key] = capabilities.get(key); + } + }); + + if (capabilities.has(webdriver.Capability.PROXY)) { + options.setProxy(capabilities.get(webdriver.Capability.PROXY)); + } + + return options; +}; + + +/** + * Whether to disable the protected mode settings check when the session is + * created. Disbling this setting may lead to significant instability as the + * browser may become unresponsive/hang. Only "best effort" support is provided + * when using this capability. + * + * For more information, refer to the IEDriver's + * [required system configuration](http://goo.gl/eH0Yi3). + * + * @param {boolean} ignoreSettings Whether to ignore protected mode settings. + * @return {!Options} A self reference. + */ +Options.prototype.introduceFlakinessByIgnoringProtectedModeSettings = + function(ignoreSettings) { + this.options_[Key.IGNORE_PROTECTED_MODE_SETTINGS] = !!ignoreSettings; + return this; + }; + + +/** + * Indicates whether to skip the check that the browser's zoom level is set to + * 100%. + * + * @parm {boolean} ignore Whether to ignore the browser's zoom level settings. + * @return {!Options} A self reference. + */ +Options.prototype.ignoreZoomSetting = function(ignore) { + this.options_[Key.IGNORE_ZOOM_SETTING] = !!ignore; + return this; +}; + + +/** + * Sets the initial URL loaded when IE starts. This is intended to be used with + * {@link #ignoreProtectedModeSettings} to allow the user to initialize IE in + * the proper Protected Mode zone. Setting this option may cause browser + * instability or flaky and unresponsive code. Only "best effort" support is + * provided when using this option. + * + * @param {string} url The initial browser URL. + * @return {!Options} A self reference. + */ +Options.prototype.initialBrowserUrl = function(url) { + this.options_[Key.INITIAL_BROWSER_URL] = url; + return this; +}; + + +/** + * Configures whether to enable persistent mouse hovering (true by default). + * Persistent hovering is achieved by continuously firing mouse over events at + * the last location the mouse cursor has been moved to. + * + * @param {boolean} enable Whether to enable persistent hovering. + * @return {!Options} A self reference. + */ +Options.prototype.enablePersistentHover = function(enable) { + this.options_[Key.ENABLE_PERSISTENT_HOVER] = !!enable; + return this; +}; + + +/** + * Configures whether the driver should attempt to remove obsolete + * {@linkplain webdriver.WebElement WebElements} from its internal cache on + * page navigation (true by default). Disabling this option will cause the + * driver to run with a larger memory footprint. + * + * @param {boolean} enable Whether to enable element reference cleanup. + * @return {!Options} A self reference. + */ +Options.prototype.enableElementCacheCleanup = function(enable) { + this.options_[Key.ENABLE_ELEMENT_CACHE_CLEANUP] = !!enable; + return this; +}; + + +/** + * Configures whether to require the IE window to have input focus before + * performing any user interactions (i.e. mouse or keyboard events). This + * option is disabled by default, but delivers much more accurate interaction + * events when enabled. + * + * @param {boolean} require Whether to require window focus. + * @return {!Options} A self reference. + */ +Options.prototype.requireWindowFocus = function(require) { + this.options_[Key.REQUIRE_WINDOW_FOCUS] = !!require; + return this; +}; + + +/** + * Configures the timeout, in milliseconds, that the driver will attempt to + * located and attach to a newly opened instance of Internet Explorer. The + * default is zero, which indicates waiting indefinitely. + * + * @param {number} timeout How long to wait for IE. + * @return {!Options} A self reference. + */ +Options.prototype.browserAttachTimeout = function(timeout) { + this.options_[Key.BROWSER_ATTACH_TIMEOUT] = Math.max(timeout, 0); + return this; +}; + + +/** + * Configures whether to launch Internet Explorer using the CreateProcess API. + * If this option is not specified, IE is launched using IELaunchURL, if + * available. For IE 8 and above, this option requires the TabProcGrowth + * registry value to be set to 0. + * + * @param {boolean} force Whether to use the CreateProcess API. + * @return {!Options} A self reference. + */ +Options.prototype.forceCreateProcessApi = function(force) { + this.options_[Key.FORCE_CREATE_PROCESS] = !!force; + return this; +}; + + +/** + * Specifies command-line switches to use when launching Internet Explorer. + * This is only valid when used with {@link #forceCreateProcessApi}. + * + * @param {...(string|!Array.)} var_args The arguments to add. + * @return {!Options} A self reference. + */ +Options.prototype.addArguments = function(var_args) { + var args = this.options_[Key.BROWSER_COMMAND_LINE_SWITCHES] || []; + args = args.concat.apply(args, arguments); + this.options_[Key.BROWSER_COMMAND_LINE_SWITCHES] = args; + return this; +}; + + +/** + * Configures whether proxies should be configured on a per-process basis. If + * not set, setting a {@linkplain #setProxy proxy} will configure the system + * proxy. The default behavior is to use the system proxy. + * + * @param {boolean} enable Whether to enable per-process proxy settings. + * @return {!Options} A self reference. + */ +Options.prototype.usePerProcessProxy = function(enable) { + this.options_[Key.USE_PER_PROCESS_PROXY] = !!enable; + return this; +}; + + +/** + * Configures whether to clear the cache, cookies, history, and saved form data + * before starting the browser. _Using this capability will clear session data + * for all running instances of Internet Explorer, including those started + * manually._ + * + * @param {boolean} cleanSession Whether to clear all session data on startup. + * @return {!Options} A self reference. + */ +Options.prototype.ensureCleanSession = function(cleanSession) { + this.options_[Key.ENSURE_CLEAN_SESSION] = !!cleanSession; + return this; +}; + + +/** + * Sets the path to the log file the driver should log to. + * @param {string} path The log file path. + * @return {!Options} A self reference. + */ +Options.prototype.setLogFile = function(file) { + this.options_[Key.LOG_FILE] = file; + return this; +}; + + +/** + * Sets the IEDriverServer's logging {@linkplain Level level}. + * @param {Level} level The logging level. + * @return {!Options} A self reference. + */ +Options.prototype.setLogLevel = function(level) { + this.options_[Key.LOG_LEVEL] = level; + return this; +}; + + +/** + * Sets the IP address of the driver's host adapter. + * @param {string} host The IP address to use. + * @return {!Options} A self reference. + */ +Options.prototype.setHost = function(host) { + this.options_[Key.HOST] = host; + return this; +}; + + +/** + * Sets the path of the temporary data directory to use. + * @param {string} path The log file path. + * @return {!Options} A self reference. + */ +Options.prototype.setExtractPath = function(path) { + this.options_[Key.EXTRACT_PATH] = path; + return this; +}; + + +/** + * Sets whether the driver should start in silent mode. + * @param {boolean} silent Whether to run in silent mode. + * @return {!Options} A self reference. + */ +Options.prototype.silent = function(silent) { + this.options_[Key.SILENT] = silent; + return this; +}; + + +/** + * Sets the proxy settings for the new session. + * @param {webdriver.ProxyConfig} proxy The proxy configuration to use. + * @return {!Options} A self reference. + */ +Options.prototype.setProxy = function(proxy) { + this.proxy_ = proxy; + return this; +}; + + +/** + * Converts this options instance to a {@link webdriver.Capabilities} object. + * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge + * these options into, if any. + * @return {!webdriver.Capabilities} The capabilities. + */ +Options.prototype.toCapabilities = function(opt_capabilities) { + var capabilities = opt_capabilities || webdriver.Capabilities.ie(); + if (this.proxy_) { + capabilities.set(webdriver.Capability.PROXY, this.proxy_); + } + Object.keys(this.options_).forEach(function(key) { + capabilities.set(key, this.options_[key]); + }, this); + return capabilities; +}; + + +function createServiceFromCapabilities(capabilities) { + if (process.platform !== 'win32') { + throw Error( + 'The IEDriver may only be used on Windows, but you appear to be on ' + + process.platform + '. Did you mean to run against a remote ' + + 'WebDriver server?'); + } + + var exe = io.findInPath(IEDRIVER_EXE, true); + if (!fs.existsSync(exe)) { + throw Error('File does not exist: ' + exe); + } + + var args = []; + if (capabilities.has(Key.HOST)) { + args.push('--host=' + capabilities.get(Key.HOST)); + } + if (capabilities.has(Key.LOG_FILE)) { + args.push('--log-file=' + capabilities.get(Key.LOG_FILE)); + } + if (capabilities.has(Key.LOG_LEVEL)) { + args.push('--log-level=' + capabilities.get(Key.LOG_LEVEL)); + } + if (capabilities.has(Key.EXTRACT_PATH)) { + args.push('--extract-path=' + capabilities.get(Key.EXTRACT_PATH)); + } + if (capabilities.get(Key.SILENT)) { + args.push('--silent'); + } + + var port = portprober.findFreePort(); + return new remote.DriverService(exe, { + loopback: true, + port: port, + args: port.then(function(port) { + return args.concat('--port=' + port); + }), + stdio: 'ignore' + }); +} + + +/** + * A WebDriver client for Microsoft's Internet Explorer. + * + * @param {(webdriver.Capabilities|Options)=} opt_config The configuration + * options. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or + * {@code null} to use the currently active flow. + * @constructor + * @extends {webdriver.WebDriver} + */ +var Driver = function(opt_config, opt_flow) { + var capabilities = opt_config instanceof Options ? + opt_config.toCapabilities() : + (opt_config || webdriver.Capabilities.ie()); + + var service = createServiceFromCapabilities(capabilities); + var executor = executors.createExecutor(service.start()); + var driver = webdriver.WebDriver.createSession( + executor, capabilities, opt_flow); + + webdriver.WebDriver.call( + this, driver.getSession(), executor, driver.controlFlow()); + + var boundQuit = this.quit.bind(this); + + /** @override */ + this.quit = function() { + return boundQuit().thenFinally(service.kill.bind(service)); + }; +}; +util.inherits(Driver, webdriver.WebDriver); + + +/** + * This function is a no-op as file detectors are not supported by this + * implementation. + * @override + */ +Driver.prototype.setFileDetector = function() { +}; + + +// PUBLIC API + + +exports.Driver = Driver; +exports.Options = Options; +exports.Level = Level; diff --git a/index.js b/index.js index 7391fdb..b45b560 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,19 @@ -// Copyright 2012 Selenium committers -// Copyright 2012 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview The main user facing module. Exports WebDriver's primary @@ -52,6 +54,14 @@ exports.Command = base.require('webdriver.Command'); exports.EventEmitter = base.require('webdriver.EventEmitter'); +/** @type {function(new: webdriver.FileDetector)} */ +exports.FileDetector = base.require('webdriver.FileDetector'); + + +/** @type {function(new: webdriver.Serializable)} */ +exports.Serializable = base.require('webdriver.Serializable'); + + /** @type {function(new: webdriver.Session)} */ exports.Session = base.require('webdriver.Session'); @@ -64,6 +74,10 @@ exports.WebDriver = base.require('webdriver.WebDriver'); exports.WebElement = base.require('webdriver.WebElement'); +/** @type {function(new: webdriver.WebElementPromise)} */ +exports.WebElementPromise = base.require('webdriver.WebElementPromise'); + + // Export the remainder of our API through getters to keep things cleaner // when this module is used in a REPL environment. @@ -126,3 +140,9 @@ exports.WebElement = base.require('webdriver.WebElement'); (exports.__defineGetter__('stacktrace', function() { return base.exportPublicApi('webdriver.stacktrace'); })); + + +/** @type {webdriver.until.} */ +(exports.__defineGetter__('until', function() { + return base.exportPublicApi('webdriver.until'); +})); diff --git a/io/exec.js b/io/exec.js new file mode 100644 index 0000000..187ea78 --- /dev/null +++ b/io/exec.js @@ -0,0 +1,140 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var childProcess = require('child_process'); + +var promise = require('..').promise; + + +/** + * A hash with configuration options for an executed command. + * + * - `args` - Command line arguments. + * - `env` - Command environment; will inherit from the current process if + * missing. + * - `stdio` - IO configuration for the spawned server process. For more + * information, refer to the documentation of `child_process.spawn`. + * + * @typedef {{ + * args: (!Array.|undefined), + * env: (!Object.|undefined), + * stdio: (string|!Array.|undefined) + * }} + */ +var Options; + + +/** + * Describes a command's termination conditions. + * @param {?number} code The exit code, or {@code null} if the command did not + * exit normally. + * @param {?string} signal The signal used to kill the command, or + * {@code null}. + * @constructor + */ +var Result = function(code, signal) { + /** @type {?number} */ + this.code = code; + + /** @type {?string} */ + this.signal = signal; +}; + + +/** @override */ +Result.prototype.toString = function() { + return 'Result(code=' + this.code + ', signal=' + this.signal + ')'; +}; + + + +/** + * Represents a command running in a sub-process. + * @param {!promise.Promise.} result The command result. + * @constructor + */ +var Command = function(result, onKill) { + /** @return {boolean} Whether this command is still running. */ + this.isRunning = function() { + return result.isPending(); + }; + + /** + * @return {!promise.Promise.} A promise for the result of this + * command. + */ + this.result = function() { + return result; + }; + + /** + * Sends a signal to the underlying process. + * @param {string=} opt_signal The signal to send; defaults to + * {@code SIGTERM}. + */ + this.kill = function(opt_signal) { + onKill(opt_signal || 'SIGTERM'); + }; +}; + + +// PUBLIC API + + +/** + * Spawns a child process. The returned {@link Command} may be used to wait + * for the process result or to send signals to the process. + * + * @param {string} command The executable to spawn. + * @param {Options=} opt_options The command options. + * @return {!Command} The launched command. + */ +module.exports = function(command, opt_options) { + var options = opt_options || {}; + + var proc = childProcess.spawn(command, options.args || [], { + env: options.env || process.env, + stdio: options.stdio || 'ignore' + }).once('exit', onExit); + + // This process should not wait on the spawned child, however, we do + // want to ensure the child is killed when this process exits. + proc.unref(); + process.once('exit', killCommand); + + var result = promise.defer(); + var cmd = new Command(result.promise, function(signal) { + if (!result.isPending() || !proc) { + return; // No longer running. + } + proc.kill(signal); + }); + return cmd; + + function onExit(code, signal) { + proc = null; + process.removeListener('exit', killCommand); + result.fulfill(new Result(code, signal)); + } + + function killCommand() { + process.removeListener('exit', killCommand); + proc && proc.kill('SIGTERM'); + } +}; diff --git a/io/index.js b/io/index.js index 5d5a3de..3077c46 100644 --- a/io/index.js +++ b/io/index.js @@ -1,28 +1,197 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. var fs = require('fs'), - path = require('path'); + path = require('path'), + rimraf = require('rimraf'), + tmp = require('tmp'); +var promise = require('..').promise; -var PATH_SEPARATOR = process.platform === 'win32' ? ';' : ':'; // PUBLIC API + +/** + * Recursively removes a directory and all of its contents. This is equivalent + * to {@code rm -rf} on a POSIX system. + * @param {string} path Path to the directory to remove. + * @return {!promise.Promise} A promise to be resolved when the operation has + * completed. + */ +exports.rmDir = function(path) { + return new promise.Promise(function(fulfill, reject) { + var numAttempts = 0; + attemptRm(); + function attemptRm() { + numAttempts += 1; + rimraf(path, function(err) { + if (err) { + if (err.code === 'ENOTEMPTY' && numAttempts < 2) { + attemptRm(); + return; + } + reject(err); + } else { + fulfill(); + } + }); + } + }); +}; + + +/** + * Copies one file to another. + * @param {string} src The source file. + * @param {string} dst The destination file. + * @return {!promise.Promise.} A promise for the copied file's path. + */ +exports.copy = function(src, dst) { + var copied = promise.defer(); + + var rs = fs.createReadStream(src); + rs.on('error', copied.reject); + rs.on('end', function() { + copied.fulfill(dst); + }); + + var ws = fs.createWriteStream(dst); + ws.on('error', copied.reject); + + rs.pipe(ws); + + return copied.promise; +}; + + +/** + * Recursively copies the contents of one directory to another. + * @param {string} src The source directory to copy. + * @param {string} dst The directory to copy into. + * @param {(RegEx|function(string): boolean)=} opt_exclude An exclusion filter + * as either a regex or predicate function. All files matching this filter + * will not be copied. + * @return {!promise.Promise.} A promise for the destination + * directory's path once all files have been copied. + */ +exports.copyDir = function(src, dst, opt_exclude) { + var predicate = opt_exclude; + if (opt_exclude && typeof opt_exclude !== 'function') { + predicate = function(p) { + return !opt_exclude.test(p); + }; + } + + // TODO(jleyba): Make this function completely async. + if (!fs.existsSync(dst)) { + fs.mkdirSync(dst); + } + + var files = fs.readdirSync(src); + files = files.map(function(file) { + return path.join(src, file); + }); + + if (predicate) { + files = files.filter(predicate); + } + + var results = []; + files.forEach(function(file) { + var stats = fs.statSync(file); + var target = path.join(dst, path.basename(file)); + + if (stats.isDirectory()) { + if (!fs.existsSync(target)) { + fs.mkdirSync(target, stats.mode); + } + results.push(exports.copyDir(file, target, predicate)); + } else { + results.push(exports.copy(file, target)); + } + }); + + return promise.all(results).then(function() { + return dst; + }); +}; + + +/** + * Tests if a file path exists. + * @param {string} path The path to test. + * @return {!promise.Promise.} A promise for whether the file exists. + */ +exports.exists = function(path) { + var result = promise.defer(); + fs.exists(path, result.fulfill); + return result.promise; +}; + + +/** + * Deletes a name from the filesystem and possibly the file it refers to. Has + * no effect if the file does not exist. + * @param {string} path The path to remove. + * @return {!promise.Promise} A promise for when the file has been removed. + */ +exports.unlink = function(path) { + return new promise.Promise(function(fulfill, reject) { + fs.exists(path, function(exists) { + if (exists) { + fs.unlink(path, function(err) { + err && reject(err) || fulfill(); + }); + } else { + fulfill(); + } + }); + }); +}; + + +/** + * @return {!promise.Promise.} A promise for the path to a temporary + * directory. + * @see https://www.npmjs.org/package/tmp + */ +exports.tmpDir = function() { + return promise.checkedNodeCall(tmp.dir); +}; + + +/** + * @param {{postfix: string}=} opt_options Temporary file options. + * @return {!promise.Promise.} A promise for the path to a temporary + * file. + * @see https://www.npmjs.org/package/tmp + */ +exports.tmpFile = function(opt_options) { + // |tmp.file| checks arguments length to detect options rather than doing a + // truthy check, so we must only pass options if there are some to pass. + return opt_options ? + promise.checkedNodeCall(tmp.file, opt_options) : + promise.checkedNodeCall(tmp.file); +}; + + /** * Searches the {@code PATH} environment variable for the given file. * @param {string} file The file to locate on the PATH. @@ -40,7 +209,7 @@ exports.findInPath = function(file, opt_checkCwd) { } } - var dirs = process.env['PATH'].split(PATH_SEPARATOR); + var dirs = process.env['PATH'].split(path.delimiter); var found = null; dirs.forEach(function(dir) { var tmp = path.join(dir, file); diff --git a/lib/atoms/error.js b/lib/atoms/error.js index 5b23ff9..6fb2e1f 100644 --- a/lib/atoms/error.js +++ b/lib/atoms/error.js @@ -1,21 +1,23 @@ -// Copyright 2010 WebDriver committers -// Copyright 2010 Google Inc. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Utilities for working with errors as defined by WebDriver's - * wire protocol: http://code.google.com/p/selenium/wiki/JsonWireProtocol. + * wire protocol: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol */ goog.provide('bot.Error'); @@ -23,8 +25,8 @@ goog.provide('bot.ErrorCode'); /** - * Error codes from the WebDriver wire protocol: - * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes + * Error codes from the Selenium WebDriver protocol: + * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#response-status-codes * * @enum {number} */ @@ -46,8 +48,8 @@ bot.ErrorCode = { NO_SUCH_WINDOW: 23, INVALID_COOKIE_DOMAIN: 24, UNABLE_TO_SET_COOKIE: 25, - MODAL_DIALOG_OPENED: 26, - NO_MODAL_DIALOG_OPEN: 27, + UNEXPECTED_ALERT_OPEN: 26, + NO_SUCH_ALERT: 27, SCRIPT_TIMEOUT: 28, INVALID_ELEMENT_COORDINATES: 29, IME_NOT_AVAILABLE: 30, @@ -63,11 +65,8 @@ bot.ErrorCode = { }; - /** - * Error extension that includes error status codes from the WebDriver wire - * protocol: - * http://code.google.com/p/selenium/wiki/JsonWireProtocol#Response_Status_Codes + * Represents an error returned from a WebDriver command request. * * @param {!bot.ErrorCode} code The error's status code. * @param {string=} opt_message Optional error message. @@ -116,35 +115,34 @@ goog.inherits(bot.Error, Error); /** - * Status strings enumerated in the W3C WebDriver working draft. + * Status strings enumerated in the W3C WebDriver protocol. * @enum {string} - * @see http://www.w3.org/TR/webdriver/#status-codes + * @see https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors */ bot.Error.State = { ELEMENT_NOT_SELECTABLE: 'element not selectable', ELEMENT_NOT_VISIBLE: 'element not visible', - IME_ENGINE_ACTIVATION_FAILED: 'ime engine activation failed', - IME_NOT_AVAILABLE: 'ime not available', + INVALID_ARGUMENT: 'invalid argument', INVALID_COOKIE_DOMAIN: 'invalid cookie domain', INVALID_ELEMENT_COORDINATES: 'invalid element coordinates', INVALID_ELEMENT_STATE: 'invalid element state', INVALID_SELECTOR: 'invalid selector', + INVALID_SESSION_ID: 'invalid session id', JAVASCRIPT_ERROR: 'javascript error', MOVE_TARGET_OUT_OF_BOUNDS: 'move target out of bounds', NO_SUCH_ALERT: 'no such alert', - NO_SUCH_DOM: 'no such dom', NO_SUCH_ELEMENT: 'no such element', NO_SUCH_FRAME: 'no such frame', NO_SUCH_WINDOW: 'no such window', SCRIPT_TIMEOUT: 'script timeout', SESSION_NOT_CREATED: 'session not created', STALE_ELEMENT_REFERENCE: 'stale element reference', - SUCCESS: 'success', TIMEOUT: 'timeout', UNABLE_TO_SET_COOKIE: 'unable to set cookie', UNEXPECTED_ALERT_OPEN: 'unexpected alert open', UNKNOWN_COMMAND: 'unknown command', UNKNOWN_ERROR: 'unknown error', + UNKNOWN_METHOD: 'unknown method', UNSUPPORTED_OPERATION: 'unsupported operation' }; @@ -161,8 +159,8 @@ goog.scope(function() { map[code.ELEMENT_NOT_SELECTABLE] = state.ELEMENT_NOT_SELECTABLE; map[code.ELEMENT_NOT_VISIBLE] = state.ELEMENT_NOT_VISIBLE; - map[code.IME_ENGINE_ACTIVATION_FAILED] = state.IME_ENGINE_ACTIVATION_FAILED; - map[code.IME_NOT_AVAILABLE] = state.IME_NOT_AVAILABLE; + map[code.IME_ENGINE_ACTIVATION_FAILED] = state.UNKNOWN_ERROR; + map[code.IME_NOT_AVAILABLE] = state.UNKNOWN_ERROR; map[code.INVALID_COOKIE_DOMAIN] = state.INVALID_COOKIE_DOMAIN; map[code.INVALID_ELEMENT_COORDINATES] = state.INVALID_ELEMENT_COORDINATES; map[code.INVALID_ELEMENT_STATE] = state.INVALID_ELEMENT_STATE; @@ -172,17 +170,16 @@ goog.scope(function() { map[code.JAVASCRIPT_ERROR] = state.JAVASCRIPT_ERROR; map[code.METHOD_NOT_ALLOWED] = state.UNSUPPORTED_OPERATION; map[code.MOVE_TARGET_OUT_OF_BOUNDS] = state.MOVE_TARGET_OUT_OF_BOUNDS; - map[code.NO_MODAL_DIALOG_OPEN] = state.NO_SUCH_ALERT; + map[code.NO_SUCH_ALERT] = state.NO_SUCH_ALERT; map[code.NO_SUCH_ELEMENT] = state.NO_SUCH_ELEMENT; map[code.NO_SUCH_FRAME] = state.NO_SUCH_FRAME; map[code.NO_SUCH_WINDOW] = state.NO_SUCH_WINDOW; map[code.SCRIPT_TIMEOUT] = state.SCRIPT_TIMEOUT; map[code.SESSION_NOT_CREATED] = state.SESSION_NOT_CREATED; map[code.STALE_ELEMENT_REFERENCE] = state.STALE_ELEMENT_REFERENCE; - map[code.SUCCESS] = state.SUCCESS; map[code.TIMEOUT] = state.TIMEOUT; map[code.UNABLE_TO_SET_COOKIE] = state.UNABLE_TO_SET_COOKIE; - map[code.MODAL_DIALOG_OPENED] = state.UNEXPECTED_ALERT_OPEN; + map[code.UNEXPECTED_ALERT_OPEN] = state.UNEXPECTED_ALERT_OPEN map[code.UNKNOWN_ERROR] = state.UNKNOWN_ERROR; map[code.UNSUPPORTED_OPERATION] = state.UNKNOWN_COMMAND; }); // goog.scope diff --git a/lib/atoms/json.js b/lib/atoms/json.js index b3e863c..24e1c77 100644 --- a/lib/atoms/json.js +++ b/lib/atoms/json.js @@ -1,17 +1,19 @@ -// Copyright 2012 WebDriver committers -// Copyright 2012 Google Inc. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Provides JSON utilities that uses native JSON parsing where @@ -47,10 +49,10 @@ bot.json.NATIVE_JSON = true; * @private {boolean} */ bot.json.SUPPORTS_NATIVE_JSON_ = - // List WebKit and Opera first since every supported version of these - // browsers supports native JSON (and we can compile away large chunks of - // code for individual fragments by setting the appropriate compiler flags). - goog.userAgent.WEBKIT || goog.userAgent.OPERA || + // List WebKit first since every supported version supports + // native JSON (and we can compile away large chunks of code for + // individual fragments by setting the appropriate compiler flags). + goog.userAgent.WEBKIT || (goog.userAgent.GECKO && bot.userAgent.isEngineVersion(3.5)) || (goog.userAgent.IE && bot.userAgent.isEngineVersion(8)); diff --git a/lib/atoms/response.js b/lib/atoms/response.js index d929a66..b14aeeb 100644 --- a/lib/atoms/response.js +++ b/lib/atoms/response.js @@ -1,20 +1,23 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Utilities for working with WebDriver response objects. - * @see: http://code.google.com/p/selenium/wiki/JsonWireProtocol#Responses + * @see: hhttps://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#responses */ goog.provide('bot.response'); @@ -27,7 +30,7 @@ goog.require('bot.ErrorCode'); /** * Type definition for a response object, as defined by the JSON wire protocol. * @typedef {{status: bot.ErrorCode, value: (*|{message: string})}} - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Responses + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#responses */ bot.response.ResponseObject; @@ -87,7 +90,7 @@ bot.response.createErrorResponse = function(error) { * check. * @return {!bot.response.ResponseObject} The checked response object. * @throws {bot.Error} If the response describes an error. - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Failed_Commands + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#failed-commands */ bot.response.checkResponse = function(responseObj) { var status = responseObj['status']; diff --git a/lib/atoms/userAgent.js b/lib/atoms/userAgent.js index 60964d0..f4a75c2 100644 --- a/lib/atoms/userAgent.js +++ b/lib/atoms/userAgent.js @@ -1,17 +1,19 @@ -// Copyright 2011 WebDriver committers -// Copyright 2011 Google Inc. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Similar to goog.userAgent.isVersion, but with support for diff --git a/lib/firefox/amd64/libnoblur64.so b/lib/firefox/amd64/libnoblur64.so new file mode 100644 index 0000000000000000000000000000000000000000..991e6422ab717d06ee41393ae50ad6b4f7e881be GIT binary patch literal 41623 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 Y!m=QGBio>Ysp1EM=o}aj!%n6D34qoY{r~^~ literal 0 HcmV?d00001 diff --git a/lib/firefox/i386/libnoblur.so b/lib/firefox/i386/libnoblur.so new file mode 100644 index 0000000000000000000000000000000000000000..716a8e036ad1998e52b64732a6d1f4fca8c1c75a GIT binary patch literal 36970 zcmeHweSB2Kx&Pz^h!7-dKvdKfg9U|{HxZC(0xT~YAVQ*8#U;sZ$U?H4?hAw#lyr#_ z*KMgUty*8W)vC3&qEe-b8cigqv__?hO50eYT@0;wrIxm~d%xeAIlHr)MSJh>bMNQ( zM-NQqd}p5LnP;AP=H;9{XU?_u%DEXC8NxcUM3x{b&ljQ$ag|TylnI*{CMJq;;v|`G z(KDX2N`)Z=c|zv*dxYq}Muo|lLTp7Cs`GIw4k7;#@)`Po8JM4~Lq3$SLIKhkth}lZ z0?wEk^{`}~Q5mPq3e(AF6 z`Ey9igM4QpOhh;v;T!}WQxFOfiV#W=tfLesQxVSB3DXhJM7T)fa>Nw~vk>MW*b(L< zSjPgKT#Qhu6BZ&~jIadZFEcie`09C!KP!x^-dexox7TLIE)14l=o&xp#>dC5U*A0B zwQ<+I8(hA4U;js?->G>rGV*7iJ$=zdfyF)Ek2+tT{P?J!-SN#}^sVZ@W_|bFx<7QD zJu!CCGbb197=6oke>-many$bV;#c>cdB=+Oo*OC-ZvSM#bDKxJfAHK7PhEb)jenYY z{E2UWec8CD-@j>n|NGZo^QQf0;~)6Wp}W5S?3BoC`>zj9T>XzVS>72l?hTKuD0||9 zAN(ada>_qHEW2&^Zv(BrhlFK0Ad@$AtV1d;78cd`Zyl( z3r0S1T?%%m;8Hq^K3Lw#DfwX|pYk5Fh!K;1i$PDk#K<3y^VVw^I!>G@`jCG%I{#JRWBwV)56=_g zQY~*2dggyc@vp#6Z-i z^DjYu2>d5#d?Cu;kMbYV`m95G4)~D_{xM=d;vbh`d<7rnAA|VM=LvDO=AQ$;NwEL@ z8gD{e5a&Nplpuf03?XjU`9}~>H1huldM_Ie!t zK&>y}a0rLn;|>T%10vyAP`%LMbopE>-Tr{fSG{m{lh@;_uB~fwsl4<#j{4SG3aD*z ze*=UUuW>BV5@t8m`u#4yxB_93SJZe`d%SBsqSak58d`jAPoP2gb={*9TOV+* zaXHqyJx=dh;r44XZ!nMmZV%6Wu9jLKie@qHhILGGx**BB&PrM7sIRSWbU7V{TC(WD z$udRLL9##C6cDJhOElLuHF@hrlh?aC*aH168p$A(nhv)UWxE?1(0rbHmyi~5prLhT zygo;*bCp#mS8KqB3=P36+)h*jRdD#%x&!r%4!=7PtPQxm9^oHs5sTL2T5E|&&^sJ# zbU0T-t-C29SL>n8g9d<$)i!C<*45UpcDNfH9+%6BtYu4GfdwA8!d$kG@HcvcO-|I# zUyqhgw1#f>wArwda*Br9)h@K~y5`yz-6lE%ZB<%Q2Pg2agDTA;=%Kfv@jUg-Eo@0w zbFis4fTojVOtD<3f!6fZH^O7<13@3$*4yIpu(4{LqM^RY>qn!xnzfvupFPN87Pa;D zF27$mnfe)sX_e`j&Olf2Ekl zT8|U`)*nEJ_MtTV_4>^2;s|&hO2-d; z8lZ&3<#CF6l?!Igc9awr7na7)%i?Fn@v|Z|?1@bIFAIMK0tQSRLv$+hn5uG_qp zh-)CmxL7M=JPd0+#yPm|VGN6vGsZ$?4&xEH-eGLVH4fuhxMpRXD?~M8)MpvvW3e`3 zJPPY3#(9|A8DA(wBV$|?G%?2Fu!V7@5CO)pLo4HHLab-JM2L-y^Ra$ljElL=j7JM` zBjYhxmoPpNYdgkcv6g3ik`Q+?#vHzt@ySBm$M{?!9%Ouq5Dzo{iV)ivj}u}$V_X34 zV2rioPR0|2=wv)ah;GJnh1kuwN{Bs-PenU3o{V;8T!waLjJ3di#;2p58DnkH&lqc@ zDC0BG&Wv&8dWdle+L`fqv@_!~(aww)VVxK{m=n#)#wBi7mlw;-P=72IYwrvUi9UuH;JlsiF+lkCf-8aDKU#~ zyp4F9#8(k-CEhCW3gQQew@BW%w|izHr8+(&Gacq4I?I8WkD#D|E5#G8qa5Fh#n>whCL##z)~;w{8E#Jv*VLYzn3 zDe-N@V~Mv(d?&Guc&o%)i6;_ok@!C1BI1n_KS(@{xJBZJiOY#sNW6`BK5>=A+li}) z%O&1Hyo|U=;+@1Rh;0&g5;qd(N!(4$%cTQCVw$$GmH5!-tp6V3jl_Kt)6|WdiF+m9 zOT2}+Q(~IF@iyXZ67MJ8O1xF#KH>+7w@BPiyp4FH#B7?z9mFjXA0+N1ULi4?s&O}Q zmBfdMdx^^>X45t9BQBEobK*W?8*q4BL07|;#J`%350*y%fO@udJ+K;s!NL4U=mZc) zZqCEsNxh*XL)Q)sb?0BXyek~q3p$J&hdS~LV!Zj5QnAQRjLcfzFe$HRm8@SlbP#@yYK#tt4l98pA~!Ql zR!d4aFaa4xrCoj*%BzfCA zX>la9PM@9?vG;;d$wepvPA2fwU8Z)Z7Uj?l-!e3bqS zv)>}w`(33&=m=P9))#T9 zx&Ejl!$TMe?aT$mNjhy1aIS8Yl0s(lHs+BXpQs(k}-ui7^d z_o{tVOL^5ks-?VYAJtM`wU26j)M4MJicMAPBdT>9Yz?*cArsBHSJAY_+oPK_*`KjF z4=38=4P5Yrx~tX9GiaO+U=FfcqafOY0@}K+uh%0NX22Yl)q@#r1X>qyHmB%FXd4Sf zX`f+Y?Xh#EA_l$|+Hfc@IKkk_P2$<9d0qeyCrjP{hr{%mJb|vq zlo`O3S!qt0y~#Q}4s}D_GcA9Xqv2K2yHI#r*K%19M#B`pm7^j27IXMsbpFth3j@PK zM=lDC92gS`eNOrIUN-%ZU#U|yp!6zgWrB(!pVB=*KA8<0#vm$JD;3F9&q*qyQ-6Cho#-md20 zwyv+$>aL5rqU-i1bzKbJ{c5d}&IEl46ZC2C?^D#NCXn8L2O0t^>|$=sI~uL-11U;`+uf%-+WVfy>PTi_4^#AmzNiGyg=ia3&r`?LJ+qqH38S;^fQ87NK9l)s0HGmlG)dT-B>ii>;=(A)V!^0qNO*6 zfWzUce7V4ui)YoIxvwhEXLmD2>AQH!Peq$~~Mc@RuJ;zYCWae1RQp(vIVj?f6czpiK}Ix1;vg7Bpd7 zSCOTg8WR3T{uId#&LKWw@+EowBvVz3`_b&(#C| z!?1W;SJPE`$mmX9s3Z^gSpH_OwX3CQ_G{Ksw4TLa+{c>0txvHg@aj{HCP96Q(c~I^ zYEBHiAj5@N%EUl>%ZWh^0GJ@;)WDS`CI~oyoEq#q<;mayAh;Oh!?vzxzh=tjW%FUqlkuf&d9s|B4pL5{Q_YD! zDaEXnVld0^{h}$30}v+p3^mDLLD6$-+q0tvtVQX>~ss~c4aHd%WdP-Ha zNY>%dvgA7OR*QYlp!G(Noj2k{Xu}>6K#RkST;rp2@8N7R0maGd38arpYO$9SE%t7j z7UQTlxW$HA)tx^!v|&Fkf3SMz?V+f?J%sxx`Qcz+e32QSEe|GJVbMo$Jqj2B0n~Hh zffTdk;y!<}7TwWeCT5Rdscwv8dfm&2m>jG%LN;*qxash5yi>ktW z`)IyK+>cFa^~+@f3I~O}5S`0}#KngiIL|;!wspOSCr;E<-(%;kWLTJYkP*qtG0Va! zUN8YVbl9VEBs48QQl=l2>89oX3JhTiNRHWx|yvV}{AH+pm)*G~o9N$OlQ*(SzKU(aA%5rc# z`f61U7<$x?*L*TdkiA5X`e&eCuv}u)KXh91s9zTSCrZNnw>04*38%28!9=&qgTVjp z&`=%MNv_f(G%efFvh|Jl?J+(yU=*JdT_troTk7;>lc~JbV_tNv*G9%N=|+8uWzwDc z)HJ=mqmx)B$*FZYs;K5AtS~UTjD>%4NWdkqz7TsjULk4ZozNeY^rH;aEy>;+@msEW zL75-@HpI7eEy8cm=#pU(Ix>FL_0J9!LahPs`?a+O+@}i3VaJvBm&W@hER&q_7E%t} z2?3GNdudtb4`NvZPd?z5EChj1S zmiNihJA`#PAuY@LK`hr{Tt9G^WH~)8%Ys2H-^H|ZV3lM!CoRjEK`gCgIbX7rre%3w zHoRH=Utr2TkS$qere)bSh-E8TUdv|n7o=s`Fo*c;_sG&DS+=HS89Rt&^dYcJmn;vZW%=O!)LyxOEPNm*68d>smd6IMEGG+} zNsffN)3RJUh^3n>TP4fhv@Df_SZY22OS5G8AT7&DgII$936>d><EQC{~=k%r)60vSvYv76u*a@D_(V=qLDix+rEglQehM}$2-`bHZMum}_9#!U zm#6VxH~51>;@6Gklz@qUELg2yH_irQxaQFFzt7C*4GUFLV{%CyAm^_YAVY!EU-0co z`NiTkSWPcho8P5qX^;3yLf*ar3+qNjb9%}#)atXG_!)myQMq~XGye9b(#FQm#>LNU zJhP;SjkC~^;iGQp9Q8zJA~SU4n88V-2PYk8Bt_jQzwPWK3w|h8mMzs3G3d> zoM;`a(AG5`#Az=Llbt0TqP(!Uz@8gCQG@bq-r?huGkDYnf3@pILM^)8ir$IElqbQj z=t=Oa{fOXK``B{HzW&(T^ebHA91=me{2@?eM?$x1S^db}LHivzk3}hUqz0$O0exj0 z=u|?y)%ru7S?VzoECZ!KK`-2;mohr4vg7=?tqL!uGos*ay5Q9is4wX>;e5FC!AMm` zN6kS9jO1tVUP0#3Gjow??}x1}M>j!Kr5$eIoTKyfg%?Jje=jp*H+(!ZWRJp`qYwgT zennrBK8^;_{o1I&LD?~MyDKBrF0daC4wF?ISj0=%nmmdL9f_@% z*H#wHxb_&W*g%1rJghP4+PCC`NymbjlB=X-3>pdTq^YJ!O^(><;yLmmq~LIQmWwJi z;*8AqT56f&2A|AQOe7^!PdVS(RB7@OiDgTN%ZD-)^fy>s5~d6_af|WpL!KulH>@B!0(NNYdSwpFk$OEuTC|Qrm6*WbS!YK=<1zlA=JHo>Vjg-e(v|ZW;vB3jIkCdPvVOxMMe<0O8JLXoa24G`-NV-&5zr;@a!gXosW84Yq0ic) zAETtUu197WU0NbbXnQ3Ls7$&;7x@ynNtUN* z6GQl4HNk@*P@?ZoSo2dQ2a^z&lU8L%LVXlg#nlZ*tHYVWGve-)&n^xXmZ<`DCmOg^ zmJ7oN3J_p1Dg&-lPT=&6OWKF({vJzeDMos^B++LF-kWKe0WxDN9x2-Es zk!m_F2ot6|MVd|)bwb+uO@TIWtM*qChWLnTSUUX!`3ar&Xq{F{om4}N+QgNwo-=e! zddOw96-VV0BB5MPvZ(So;61QW6{q{F9&Rg8Fn*!C=5#HkjZ$Q1IZyL`jJxv#$KpXX zjeiFWOZJqHMLos96k}M2a=0%TJV9pjWpzu%0c%!DGV0}*g}OJgQS6)O=Jt)ot;Qbv zEhD798>z4Twy+&1puAI_fOxAs!LJ_oh3%UOa^0nh=6cugjdhjA4j8Y@*9zGHai~XV(aXTMA4?V-bBA_R^#XapSfm^3d zbu{#}ug2-rvR&=XBw@cD3LPSnLRwME8^Q;LC^R2s!al=fKNuJyXHATwuF}!$sCZti z6sop$Q@{8F8rjhcrKbP95K7+*+rx9@GBwnl9Vg0K2%|(3PkPj+P1DCvQAu|bky9}fQC2PtH-{* zj0O*7VS5hY*^SkRiMjC#`E+kodegU8|Q^@a=VTdf7r5G8LR zT6m;x;Y%^Uhq?ptmMe%l(57u&r+<}=kc;~DQ%@ERTT!#;peKr12;u#ziZXdDs>u8A z=ULU#|s_Shbh9Rxv7dmqVgL_(*$ zi&R)OEOSWr5KPh0-VcfctXNo{f+RN*I#m(p$Yy}Dk@inPMy3y-!>A{vj+BUcjfX+0wyvHTNdsI@`yQ4dHJSV_ZLl9W5}G5=DI3oo%X8`y32l+* zG`9VVM!$Bo$RH^z6Gz*5DSJjpkP}XzYR069pTs^-t*|EGYDh zEN)c$t)OlQJtGwl;H}M{bp#Ds{_-x|VMLcp2}D@Ewfs&9V~5vmqj(lSl$z#Ty{ecR&)BQj>h^Ufkbp z>!v#?-?AZkM(H<1gAu~eH z$ZU)d$cr7ADDTo%=-P%Q301rfb?%V-5v(9&r)7c23d_`3Z%6xjBt$n*b*T*2C>E`K zBW2tqt7+A$0E93Deu3M>%%FsU6D^i_?RWNYNtLk1ozO({Jc=v~q@xG%?1sfO%}9`^ zr74JOgDIYm-yEMPW3yxm)~J+lOMW7&8Cl_*SqVNGpr0wD^I!q3oAV>66_(AwrPd~6 zsdZp0nn7ucW&B2I$9P4%c1cYqShZ0SV4A0(X(ZI9rQb+$3wpcHba7BY`Q z#-vwrkYX>(`p`|-P~#+GTZb3oNiA!x{t;@{)=kGWp7qU-{tU&ob$R$sYJJ?`J>@xc zbGj|3hR_bJ)uT{8?&w(6+*IsjxkFxOot4!`gS{@?@ytbDPm|l@sjVzK)X9!1!R(1Y+g!fz0w2;@V%`e)c|VmN_5#DpoOL7URu@(DPY zGtg**zt|dG?v;&bEu7YSgKRhFa53B4vaSMqSICp9rdrf}f>Zm&za|5I#PXYKS9yKX z2F-2{&mjCVx8GgoZgL0K)p)#h{(5Ye;WQGfuni5mMbo;)gY)JF&2$!^Ja2+u6{q%I zv3iEti;UiprtZTVfAqS2iGGu9Nvw)G-im|D`2wBdObIh3+=tH178zv8+B%eB87`@~ z3g(Det|n}%Q|)b8&>&`cG0-&QWN}N2*U$Zbq|BrWB#n+=+^!tWzFa4xBd@7#3TjS;Mnwl_7St&IhcRWq&T(60(8e%IXIq;drqwh3hub0!rcEr%Y;gPh0q$|6X8EMC9YU(Gmy}=NWC_}_ z7F!d|lJmPtR_9jELQK+LkD6_TofD-#uXNR}ap`lpJJ14;knv({iUrXyo5j_NuIv(+ z%Uc@V^?tj8i@f?IF&$|7*_e0&mgqTZ^DLcJ<4K5xO>`>GmMH%cOZ4_zmMCk?O~n>H zlv;y{NM|o``LSV{l{ee%tC#ydSl%_u z3zc_REv)nOK52T~k)FeuRBYNFi!to|X)HGEXR+7}gl2@T2yY+^eFQ(sAT%M|gYY`S zkZrNpG=$X%cO$%pko{;Zc0R%?gu4)W5wae`T~-7)!kq}OB4j=ui_Jt>gRl+ZBZN~? zelEIBn=ewN6O)`)w0iY?E!CU^CqLa=CdMmPe8r zd+fQ?US4XI;J3N`wna6Sl~@LGdp1J12XnTcJKuQ%g>r0CGHhx`H0p|}0|Oz|P`jDg z*4DBF)=}d$R;xL0CD<-!(ba-msR&;7_!wx65mS~H1bDn@?Gj&&|7g5^F07%T$3Ai zR}_06tf@%0!BvWOFJh9GO+CD?4C-L;Jyfkhro? z*kCQ)7^*&&m4q7Lz}wV>B{~*0P`p-Ok=V|Kzo_dEUWiR9w9z4@#NqLQ(Yo@ZHMMG5 zkwirbZM1^k+Kvjr1Lh$`x`_>24(d?=HPpP)-3E(<;gwZQez)g;z^?RPV6Yk{oRToz zaA9AvWbqQ)6kEIz3>8ri?dk~?uw!16)8=hpV_DUiqFg{Z2s{SI3(v=540j+rq(dd* z%Mo5isMGQLh*u%_5N<&DA;LEhNb?xNP5PYJ4Btw@%zFyKC$7aV*rIZd0~^jQsaSG_ zqpG5MzL+5<_|YHhmK7JPjqA=0`h84DN`&6=bpCQRwQP#&!z(?(6v8;2yL8&rqfl8i zQ_GT=t>n^@q~r;HQCczun;8le%LrGq9v3Z*+YB(R_D%NAS}YlCxZ3e^JqLnisK^X~ zMEI>7W9jBVV3orw1! z-i)~6Md*k4Y~1hKhj;_-2FMgCPRHXrdjh?@|5Ud5B0h%fGq#kM0}h^H|2Bfe!H)+zWQ?{>rmi2s6k z4&scrp&w#Cqp%6_R}kNacn0Eb#0wGkBVL0z`xtER^e*gyI1{(~st}Js+=6&3;#&}x z;eOu^#J@z`huHsH@L?3n`5pKWM-f*cKJz`?w?W*BXL=t*{71ySi0^nm7CVgiImBb} zbNbEwSTiABhv(l{AkO#@dLmwh_(8?U}Pi4w)0m%5-&Aya-tnk?cfx2{f;pG;^=d zx-L63B=g0tid_{wl9uIdLGV5ii=AxJUPei3Z~JXXcO(B_k+mPRqnYTB&g%mgvAS&wC)fBqNg%Pv5EEApR4egJV?9_*I2 zIzt&w%h`(jVd$&r>|p~r)l~A2+m52Jh4cZ~5 zUKZ_<1g#CUGeP^<&RFbeQ!d;7F;zBcR)A&!#+GMNX|^Zy+XR~5gNDy+#p_^|`P1Yw zw}G|=W7Is87G++et!6{|UeMeOnw2RunO7(D;+Xj6m(YxZ-=Bo>?~BSR2hB{-+>u(= zcN6tZBwA~v@P63jeK=WQXnvs9CnVhe$Z9Cj$bcvZqj9LQs!h`bI>Lafkw`U);w0F%CaGUBJ%G> z{tY-!lF_DQkfste*>4=(PpESXXabZ_?49?o#E+`h#ZW z-dK!}LZryL!IaexI=+h4XVOvDTS^xDOim8Q9z6QU??NQY$|^GZ3~9?idk-E-Cfz*f_#X-TJ`5T@**w9dK|L^q z$%dkwUg`##y(u)AuO#Fg2F+ru=RQoO`AvdmEY@zv;rYAGsWjInXl8;Y2%4mMm3CdB zXk`6C^Au>Drmaw~tYxs2axdBEH-ql8cVjVrXCrPW(p6z?q3P(S+dy~rZ`1dA8}fUR zKMMK$K1W;*b(*ila6CN>nwg;CH#Fij3)Ffn>yG5Lj143O7$Ba&J60FrJWdapYqVyh znGc%RKvR=KlUb>V__TZzXfm-=e-=ba191U+c2=@BIfsUMl!wx*AIyKJq2vTFF5Y;I*#8};8;%l=YPh$)4;~~Vmpyb~Wv#kHEgWun?jy15ybqL=>xF6vOgk1=4B7BJO8NyIh zW(>kv2vZT}B3y>hfUpMPI)v{b+>h`C!Y+h25k5rt3}GlLJ_g||gsBK~5iUb$Kv;ut z9m4kz?nih6VHd)i2p=MRhQRMqj6paHVJgC0gv$^b5Y`}Ehwwdw`w^Z%*oE*W!iNZ- zAq+)>j6paHVJaJ^4Ra#ezY<|F!o1nDXV@mz)ZrPKpslQMT4C9glAuJz*A$l(7L`p> zse=hirW8-tl!~SVU-ZMbQUZALsgPf7k#VDS=J5txg}6stSQo_2@hSKek3@~N{zg&g zT<76eR%9ITsT_UpHVHV8hhMlzr%qb%?Vv*5pcRDye6mFp$`3ph`n>WBD}}B`M}v<~ zf;Bq9X(7ddug~~u*C`I`d{sTjAiTC2pKdB#iKnIbr%;xnB;u1TqOjiE+>D=NEI;~J zV)iq1Bf+H&-xy>lFc{hAcwkpq@frF6>Dd1mHX_J=hBVSK;JH@W2T8|%$xwvO$~=(E z815^|5y(qA_G1S2U+W+(kNF7fi=<;eWZ)X#DwpXDTt~6*@|^vbp$g|_xq$NC8kTdC zhy9;n3$P`R^c)A85iB~67YrOnNXN0pqFaM=>dLW!;~2x4IJXX~N?%8UMYjSp41HFi z$ODuz*Bf-@=obu!@r@AcARUiyAz1P_ZZfbNS%+0F*W;w4UD&@Ejz|Bs4vTIJ5-d9N zd!DIucN=sMI`GXDgga>veOU4y1cu3^z2x^tJ*s}GblZ`DPr*n!j`Pu@&~<`_`q5+u zL3i*dbT1;GZ9zKuT~pBY;oQ=TG28c72$np4kEa(OP$@#1b&!tX4Fro$Yy@c035a-D zbo+50r)vX1gIjH7(J}r#0?VgRjQm+TtoLTYM7Zx{CdTM+R0oTvt`;Vlpv87XoiFa0PIm zfw7;TERRi^f$|ykLi{4ImH!Xm?_=?1h9tD0=)1L->JuZ;>djt8oyUU9pw&-U9 z{|4_+?;@o8%t=9uH+)y@YfAI3wXVO z=K)`D;7Z_|416i@54f1pnAlYT$Li zy=NxMzZUpi1GfWzXy7pLCkFm5@aG2pKJeZt33+z`^ILN27$NxD)C>dv7`WNMj{t8q z@XvwyX#$J?Y2cyfCh+sX(+%7M++^Ti0^eicH-KL^@Y}#ctjQAo^gG~b2L1qewSfnK z?>6u!z^@sY-%-vkPU!a!;PVa4&#tX9@G-!58F&24FGhF^m|G{B-?i=l&W3!d#qUB#fX(kZ-vexZSNbtv^SjH>0b7R+C+`B6LmpRV{7&`9 zz}H=z`0n;_wWO0Q$KmfdVDr1vrAGeo$e*2pYkJkJwq!;3J zVDr1{j&td;c3?^E>pP8T2;L4>)B@Y!~JD;DcQ+WTJM@H`y7|kBjXp?{djs^eUjZsayamH^Oa5BI($Ui ziC1m>j@n?Wz#X_2zUAXAoO<5$Vn|LyBtQ19?(E_7$^qXxe7KtT0-eF;=5=5(ko;JB z5^0(T(eYXhz9WqnjZjwnbrW@Ny;lM>%XsM|4vqYzS5H7-H{L!0G+#gg)Q=XkNO{WF zP;j2~CJL~A=>%sf@1r0k{z?kY2fdYow4@hPfRo=%L4x^u3Si<56$PvpL4Z=;Q$fn? zlvh>EB!e;eg%v5ud}oDR)@v&230E2+o@5A0d3gm?@%K~I`zvOO!5618#3#SV!UB`t zWkCW=(Sm2$HIKtlwZvXseT8Fg&7#@W3l=YOI0~m1O*Qh=ueM(CuZ|QV2+}tG<3+)d3 zqB$6`)bM4UA+k=`-qibWL*7tBT~UY0Yy|BkQG z*W>MOhogGoY)kaK%EhxPDjkdG&RuG+c2rl)sQCFZ;>|bdVUFV`?hK9ueNuW&4=UWjMBt#KI| zbD*xSt!r|Ll@9q%k<(%5f?s*?_PfK|;82gLI|5$3ee7}KRak!ja(w0G)>rO}1b?^D zWvzC(T3{w$us(oM+T}9Ikn9Y$B&DRQs@tP$>Tt=QY8vDN?g_SH(d=rjZ%z`Fc=1Ze z*#i3+V1`KW6b)*;L|&O#Hkam?M$k6En@av5ev)dzdp(xnF;2iL*gRk*T1x5!q4lk` zEKt5xkPum1SSlLU`tX8qBCUw_sCAkNFi3sNIy0peKUc+{%*RV7+U5?tYaFcQtBwM$ z>hi2Hd9Yal9MMdXj^gloEFbau9JS6>R{J>|E1QuAU-kvi8{`X3y7ls=je!MeusOj&|}&!NA9)={?(=30}SK#^#o zU?9mY;tiy`bi%0`@GcAA$8D}{p^vlYtHH)`@fs`!)LL$KQ?1{R!Dpp*7QQ(g^b0jA zINVOu)7^kq%v^ZoO!x=4DykjybHK5xNME*GRF5rkm`XcH!Q`HlPEU4e0zFXEVsde{ rRBBe?ahk(|{Be01UUgjHfng*aTAx)yH)z0{L}qhBS2cFJJ>tIriCq~9 literal 0 HcmV?d00001 diff --git a/lib/firefox/webdriver.json b/lib/firefox/webdriver.json new file mode 100644 index 0000000..14ba421 --- /dev/null +++ b/lib/firefox/webdriver.json @@ -0,0 +1,73 @@ +{ + "frozen": { + "app.update.auto": false, + "app.update.enabled": false, + "browser.displayedE10SNotice": 4, + "browser.download.manager.showWhenStarting": false, + "browser.EULA.override": true, + "browser.EULA.3.accepted": true, + "browser.link.open_external": 2, + "browser.link.open_newwindow": 2, + "browser.offline": false, + "browser.reader.detectedFirstArticle": true, + "browser.safebrowsing.enabled": false, + "browser.safebrowsing.malware.enabled": false, + "browser.search.update": false, + "browser.selfsupport.url" : "", + "browser.sessionstore.resume_from_crash": false, + "browser.shell.checkDefaultBrowser": false, + "browser.tabs.warnOnClose": false, + "browser.tabs.warnOnOpen": false, + "datareporting.healthreport.service.enabled": false, + "datareporting.healthreport.uploadEnabled": false, + "datareporting.healthreport.service.firstRun": false, + "datareporting.healthreport.logging.consoleEnabled": false, + "datareporting.policy.dataSubmissionEnabled": false, + "datareporting.policy.dataSubmissionPolicyAccepted": false, + "devtools.errorconsole.enabled": true, + "dom.disable_open_during_load": false, + "extensions.autoDisableScopes": 10, + "extensions.blocklist.enabled": false, + "extensions.logging.enabled": true, + "extensions.update.enabled": false, + "extensions.update.notifyUser": false, + "javascript.enabled": true, + "network.manage-offline-status": false, + "network.http.phishy-userpass-length": 255, + "offline-apps.allow_by_default": true, + "prompts.tab_modal.enabled": false, + "security.csp.enable": false, + "security.fileuri.origin_policy": 3, + "security.fileuri.strict_origin_policy": false, + "security.warn_entering_secure": false, + "security.warn_entering_secure.show_once": false, + "security.warn_entering_weak": false, + "security.warn_entering_weak.show_once": false, + "security.warn_leaving_secure": false, + "security.warn_leaving_secure.show_once": false, + "security.warn_submit_insecure": false, + "security.warn_viewing_mixed": false, + "security.warn_viewing_mixed.show_once": false, + "signon.rememberSignons": false, + "toolkit.networkmanager.disable": true, + "toolkit.telemetry.prompted": 2, + "toolkit.telemetry.enabled": false, + "toolkit.telemetry.rejected": true, + "xpinstall.signatures.required": false + }, + "mutable": { + "browser.dom.window.dump.enabled": true, + "browser.newtab.url": "about:blank", + "browser.newtabpage.enabled": false, + "browser.startup.page": 0, + "browser.startup.homepage": "about:blank", + "dom.max_chrome_script_run_time": 30, + "dom.max_script_run_time": 30, + "dom.report_all_js_exceptions": true, + "javascript.options.showInConsole": true, + "network.http.max-connections-per-server": 10, + "startup.homepage_welcome_url": "about:blank", + "webdriver_accept_untrusted_certs": true, + "webdriver_assume_untrusted_issuer": true + } +} diff --git a/lib/firefox/webdriver.xpi b/lib/firefox/webdriver.xpi new file mode 100644 index 0000000000000000000000000000000000000000..16c8579a35bed629dcbf2c7ee6818b6b4298bce3 GIT binary patch literal 687832 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

        2&K7S|vY*omu(efjTc6WXxA$qkl?xNt zj*yg%jQeslPFT|ljZaKexSsftLD>T^j9Ge>ePJ=csihD`AxGN_@<`|m-%IeyDHG=) zoD*9VZZxXL^EnuSQVTQozz&9pK$MIJQe6jcY`Y8 z0Nt6+AX+wgo6Qn7fDrTIt-+fRg;~9d(Us=?wMLp-`%0QYVj&(J^KGNZ@^+lXpNr=l zX>yP-f%z`r_#?xc^>72hU<*|g0@nMr0Sw(i3ej!3m*rp1gtNss6q-PnsYf!J(V#5$ z0d+m00nH(W1Fey>DuhQlA#W)c>2Sa^eG~Pca3x1 zH_ho|Xshmpb73}NU0o`O25cSt}&4f7t#&op*?4= zIu`ep%xHj~#uR?wH&Ro5Dk_R2)f)`Us_5EaSFLkv8JP_L6P98U!bnDAhyPNg-BfRv zx1uCPHr0KlS4eiL%@7t%%+J4WHFK7V9pbuoYB;(4Mz=&^R|aT@T+575@>O9@Ia!$(B--sRD%@{i{(ZI)!vR8N zZICgotA&t?AXJl90De_|!ZId!%#x(lk+hzR>r+sfa)m1oH3lX}EXGPAe{k(dN`Dz` z2+gpFItC?aOqE3ZEA%a?h*yj_s;5GC53AQigL|fj~wKs|*#Jt&?h}s2FEDakyLH01fe92sbGK z4pzpt(#p{Wzefq_577!;p3tp?lsQL!aNVhEq{6gaapSsM$!21^pPa=4=_8Ko1NlR$ z44JiKEYUk+Xgv`BD(XZlzP=y_ISBdxXL=Y@u-tb6H}*iKOJEtum>v z`JqA_UH(A~NIdLV`S=bOPM2z>x7adcv(Fep7MNO;9xWiHqU5vgys5d#xk0sPo@)AK zkKLR%wg$LFhu|_a@>V(K=b=qLymfk@S#}wZduz}rWZd?^8~8h=5i_}kulpFbJ?7=q zp_aqS%2|WW9IDuJcUQrQLj`;4?h5L>y|c#Mxi`)tFmS8|%44EG4)w>_c_k`Og;E*? zhMUxvi&k|yHy=c^i_zphtzKbGyU|ff&nG;J-lw;qBb#F=m&N61(%7s8S7|c3zG^(f z5L@x*#tR<>pbZt9C|F7&n+m|H;ge;nux=VElxd1_tr}?=X`u3eVY|2t5J}UXwk(-) zk|@S4I9B+fu9mMgV^*&ez}ArkoPgh)qIrp+T}4ndFBP=wh>h)H*S)n$QndtELFQI> ziVYV1PynDHQ3-OWf=izEdlpcG)ZWbn-C@o`cGlU$DPMjnM2G*+WE!0DYZ*YJ``{at77Tn z`g}SbEh=G+_PD{)cIif?hS8{MbpiiO|9+hj&J?V)!?W;_O@3&5CzH|QIK4=bReEwt zxZ3m8AD@`Zy)VkFmkrow>!~`p5-`Vbn?mB{P7m~|C4IXYDDNQ zM9Bu0q>HghuhkUNI`Bq}1A?Vr<5f`}Ki!~o_%3(KkoBkJd}(rLtec_3p`36{W46Wd zGobkp^pR_ZcW~r~jSyL)a4U2Sxc)!cXi^F7b7F75)9m5SIy#2?uHL8qVe_EnxU?;M zdA+d-ch?~{k4y$$l*RKoeK7S?_|`3FI4)m(!)2Ngo5owM8-eMT=)o8Fk9tN8qM2>U z?%|jyLEs*s7E#!3|H`-p#MycbRx~W2Y{Wfvv3F_5A)+rd;0D1#H!$MO7V6X*8|&-S z8$lo7A=DVtCB~>PCe`B;B;3SC#Sb=UjqLa-lCI=7Vs8utBQ;QOCQ%`;_{sZ7m!_1m zd>#&(z2o-Be($(>*o8x##}Kg8=VSJg!pW!->+QDNus3;j_^jR@rGN+X$j!EIor3(NRsT`o*c|bj&AobD{zji?W zU|8Kh?DYCw^03nb5t1K)qzm)v?{$t3`g`rx{w@l?r`AF?9gEvMZn<8+ zJ?Zs2hdyL*yx(uWZS6bWQ@A@m^jaT#(D}KF5_^Pv&P!PDQCVT0U8R+LI4DfD6>C3- z!ci8Qt1vL&fpTOB!z_#h8VyD?0IP^)#4|*=*AHs2kgfVkD&=x%x=w@IEpq(r5O%fo ziU#Wf*_F~9gSM8S3({=(kj%T{oiy#Fszv+;|NiJJFr>`rfw`OV7|Oh{?$Lgm>_@Q2 z?l*gc^LPsi3&L}BTLOyYc4QA8TPhDhQMNY;kC|IXzm2JFfFzM z?bc0uk*TE}o90r5i_L9Rg~7b4iu)5b9z9zv%7kr|>H0NtB`nfE*=a^g+~H5(!7Mk9 zo;Xh}VarM}jXyu1rtxSvB4;+ZTj40xFl>CzyKSVzyUIrBo*W%@j(g;t^JrT7ydf0p zm*dXKk(Zb~_$*?)XpThhIYCUmpKfb;!%+rEi0Ipa?+8g4Un^P7@1uzIf|;VV8qHXT zHjh>0VCA)tstu+>;=6njr}G72+`PnYV5oMtgzDL|%Lb11rOZI9r%4+sOa~fFb))Z< zayxmS(DuV!rLN@Ugk3u~)$|(sUV{{ zs4y`S1nb4@I(4^Zh*6B4WnOT4QG~ctHKWzCCQrTSY?w6U7)@1;A8h=B)xv%#0Aajr zG(do;`VsSZn59wDnZUiXC19w4buD5}m~45BtP~t0MMZhI>}AS|(At6;TEo;=-mxXB z%s<~^ST4BJAF_kF(wE~_(IJ&vdY)3Yl%Awc$kh(%Wr|QIh$|KOO1*SZ)eJG)Ty6$3 z_qIlIi6!h^Vhv$U8?L^*Yg7V+Am^G$B`4I5A>oV4q+rmFa$cM6#h)8Dq zVgAD`FPWMOFHvP;5wXlpmn6d+I#VfWp&|q^T%j}9yW1x38XOvxxCCAF!W$FTxzL@d zVEy*sf|e&XH>UO{GcRzX8yVqQnz2*8h_H>%JB-7lX73$UrBt*w$YLpNR2^rAL$6A9 zRQ5St`^sHim^K_55*8N`T3i5_cyuE}-Q#Fi#yS_+p=hKCn!!aDkfgbbtySc_e8wCY z3H=CAC>g}x6i~kt0-ad7)H;QgIq`Hx-3101tg0$%?OXj_dv~{WXiPIDMQ+Q_)5Sp} zsYuzsE3TRNcyB`i%)+xZSv2vsOIH7cr7n^72OWfy%7s0au7{< zKu7@D&;;npus*_|BW#ln>)>cE{o|B(lC)TZVvQ)Ft(RLP#wCMJhf@R}qg;9!4?EqR zR|%b4}k_XH`S5$L7Jj1B**1An$BtTlA$(k{vu`+qcWg`RHtw(P!MM;6DEtK z9%eQtJd2<{vBW*yUOaz_R$My+P+UAM5ErzgXa#ShVkHo%`$Cdp^U6SkV{{TkxPw0I z9JU&CO0|Fz$OZ>_Eg%cs+23!3G;VsRvnys>C1L22t(36gm!-50BJOdyCD_szK(|4S zW{WulZv$g&8I8h`7skk5Q*;l?lizT)7N5nPU_s)-8K{W~1p+GRYb@OQoiqt#jKWg# zqL*slLNdz3yNHTqy3VE)vTAA*z%XV9~G{IQbyPPpZ)&qr<3Lbd1%{{Ff z0gt_B7rWJq-Re|jP=etS_*4-#E~xkwp1T)SXF&cuqZmEqnx12g4TuNN7`mDD3qfvb zGC25aztn%ZPda(CpV#xK``-)jLx)j@jvd5ix1thuv5FtUWabKy5R zxMpMlfzashZuOW6Q?H=%Sr9g88AJX~8Ha&KBR;d>i#AowW0GC;7IRiIzh5GBuvnC+ zdEuVC@OnX0ukcY_nal^9^TK|A`@liU?i?^)I6i0{!t_!Fq=9p4!CG)7Gc+;Y&j1RR zxmji)H%`)2X6|zK7Tcu@M(V^^`@=#}X*>UO!}FQ44d(Jt{6PTy{Dy;Sm18U~jiI>^ z(pZSPicd8U_5Lc2BID#t0Mgh+@4?f>78Fh*)U zcST@dH3k>VozeIcEoYcDsBQTQaZ#DsDj2aY`%u|#OmC{&u6JjuC5A?>b8cTtiI|oJ z07{R`%Y+aeuSf*94^Oj%mTOJ@z_N&7Mi@tcV6cRB2M1Pe@!>(byvf`IMOjkKoI~FJ znLV!bxO9|^*$EfZY+kX5sUklNc-)j&B;|6O$L6J5&4>H{5xH0W_*nv}^Re-uc_ah8 z4797D{EB!RYOc2urdtSD?4rccs^(_p92wq@Db@%L7V1P5kCO_XSXLH}$I;c49quBeRZTh6XDX>-iD&KV zN-QgdMHuktX|ys=KAY3Ki23F2e!x@G`bWU&nrjfsD#zMF5;pj%brX-TfuB0eCT}4Y zF~rt_=)*gg5%|EQvw4oQNkrJs>6nzRJo)YPx3liqO|*W#`VaZ@$0rxHAT;bY;%q`r zxfJw2JL5E(q{;qhk~;elyb-;qBZnU1*pd@as3NuOJ)7vZy-T(S_$ww36UA8uws{Im z`pvu=^POmUHR?O4)I{Ly&9W<*gUkp=RRq|IVa-w41;mudocHNkV6l>SjmI(o1~thJ z9YOMVu(6V+aJwwPR7vGORSmAB(&^V@?AAWqZy&b$yY24Le)CiRZRaCeyA9RE-oix5 zW8X-NUh1|C4mF|}9gDp?E}RcFnla5%90i5RONp|IwG1OUD;Lzm=X$a>qWTjW&Qsia zjfLqfyFnqVbv0ewRus@q)ATctb#ydl864Hsr%OIzy3-ooT2OZSv%*dXqy+G?zZb#= z1E;UeVXMM=e=e*5W}Ryb?p_-5RFE#$g+j}Kgzb+BRS0XB{}vU~Aj2R|Gt;9{-CCTE z$Ly{X(=D06CVQ7&Rz;NRVCa^?sp^ez4vYw;NH`~Kov$aqMO|^NAiY>MHtJHu!(+9y zEb(CwHEuO)CPhjyAe;y!TgnCna!)9`(?243fU0~jv?r$=so7#LL&NWM+Uz7ifry14 zs3GzB1GUA3-5E#>;y<(T_0>dsSc7~hQ@62l30UNv3iObLB|uX#c}kw1U2m?h5Abh_ ze~0ISH_-gQ5gX%(%w`lvL|iEnvNDY zP3$pYd7qS4D!={XY+kLLb*oU(ZQrKIORLBO8txl+c*I=8%Q%{wUFrUUq~I9jNu?my zQbmSl0Gg(esHWMpq7XDEwTcR)P15&KAQDrd~kSfKXud;Y8e`>B= zha=6bL!`S{=5F7*f6p`Ra2@J2g^_+AtHm~W@|&5`>nwSU`MjvnCofepOVnAVW3~fI zE}rEJVjs|yU<4P0J&(iajy6L0WCBjpbKU%&!~Sm6N*F=#tYHl2c*3EEiRJ9SQtb#S+nhG7|C@4FhCFL2d+4~AN7Ng)FtjjWKppk(R)?Lo5 zr5@`pn)w5NCYff;lDhEKLLE%8m8$v>8T`Y%WK8Rq*?a+-K~?~`y;Ln_4N?x2t3hca z;wsK%O;hBACtM=HEVg7>;S!+_SJkfTqh*(b0b%_K|5I7Ut;4XXOQ}s=x|~g2tpt)= ztLfpNYJ8Q-q%h}Ptc<*{G%Jf8T2i=2syAE+-KOq4ox>i1Y^dE!JEbWgTcbI1&ZS~i z#TgHc{&ycB8<16Sr<#k`?x|6#9~w9A%uOR1XDz)zA>%MS-F9S)|PHzF~8D zkvb^qy^}Y5dfBUkwf+??We(D01o!?b3?%^pI=QoR8vcy{eKZ~uk~zD0LRNWxnU2R# zXbWpstck6S@SLkjR>XcN4lZ?a`2Mi-;jq8cIXGw@?wX7c9@^b>_61_DP?&$vhF*2e z4sQKO&sQlen|G1)cMX|X7vlMttP1)ARjF^QUhG3UIE=oGE|6qZ-A(OgaTnm#o26-c zkzQ5nGeSoA(}TJv!XTZWuj|f%3I!jeP9`JLOFOZVz$n|lNGItG@dY%${;zKWZNDlj zsN>T0^(a{r)wUo#XGq3#JtqGW zs&LrR$pK-2u5U);Q8c@4km=qu2H+kLQ!)Yj{zM_X_`AN}`K7O}>NvZ&038}c=_WrR zbCvjpH9%i-!a?0;>}CFqy)t+V)tb&Brz4{%f?)G&dMC`7qgs-I7 zj~ordY8_P6(=_?dZU>_-Z)t-liUI1%c_1t{DVG{)O)zfMc-vqJM8V`h|3ri4Bk!pj zJqkuuuvP~uviB3x4bK6aGh-T_Tftc*L#N^B3IH3P zg9dn>UC#({c_QHdBI-d4HtAOU%LnovsBZ9*3lm(dJ$I(@a z)W&-fitY>qKZ-8W{VYnzVNa%XaqDMf%W)*rSvpL~YLRZ&=Cp($gGc1s+Zh3wo<@eO zFZu8;nnPM`mJLM^Q$pXP1a6#gbRo6rMea13L3?>cG43s1H>z7^GFlL}6Xr~gjC~@R z0pF9qy&<>q95w~ov6GXH479?`0b?G_hZ16H@rT4Qp%j@{g%IJ44_rfTl=80Ps}BUJRw+Q^|tm z55fzRj;mL@1Agf14Udyp<7@ft_sCYg#oEgJdg*UZ%3XMOAU;3u?uM5WU2GI2Om`%L!*jkWo^ z>OYB)TgNM!&x#Qa&iAvAD`lb>0U`#|uEQPPukZVywm$Cc*Vr^Zv(G$f40*nu63X)# zO<_5($?{zikW>+h_vy9!uD#bQ*(Wnz+mL-;-mA~%{t`VZ2vx<^zfX?`tzL7fE;;&E zg-~E6x&vAcW~^SzKry?zHxS;$uUb_ipH2YyU_z^Yq@$ zylw6fR5>CNqiF2ey&LN_--=eA-ycN#t>&?4=f%C-Id1h%jt@mcFCX5Jh6AredzEDy zWLp#Ptb!Zw0z(WEd?iQZ1S5)3+^veyzrKHOg8X2ANMDk|{-XQ$heQDT!u@?&)YsGG z{(aHykDcMR9v4n*aBnm?YMyjkCKkl^Zm!kcX&%X4muL+E>3O;-KBf;C^e?UciKG<6 zhqbiZ`5<=G#`^u|ez+@Ixj!v^*EtaI+qgd^-EZynL^JoNqzGgcH#Y81NRQjUypv

        ;9Cqeb{Xs%kkWwlJ2(lTRpjE4yh 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

        62es(tjukN6^q#Hua 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 z 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.array.ArrayLike} array The array. + * @return {T} Last item in array. + * @template T */ goog.array.peek = function(array) { return array[array.length - 1]; }; +/** + * Returns the last element in an array without removing it. + * Same as goog.array.peek. + * @param {Array|goog.array.ArrayLike} array The array. + * @return {T} Last item in array. + * @template T + */ +goog.array.last = goog.array.peek; + + /** * Reference to the original {@code Array.prototype}. * @private @@ -73,19 +94,21 @@ goog.array.ARRAY_PROTOTYPE_ = Array.prototype; /** - * Returns the index of the first element of an array with a specified - * value, or -1 if the element is not present in the array. + * Returns the index of the first element of an array with a specified value, or + * -1 if the element is not present in the array. * * See {@link http://tinyurl.com/developer-mozilla-org-array-indexof} * - * @param {goog.array.ArrayLike} arr The array to be searched. - * @param {*} obj The object for which we are searching. + * @param {Array|goog.array.ArrayLike} arr The array to be searched. + * @param {T} obj The object for which we are searching. * @param {number=} opt_fromIndex The index at which to start the search. If * omitted the search starts at index 0. * @return {number} The index of the first matching array element. + * @template T */ goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.indexOf ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.indexOf) ? function(arr, obj, opt_fromIndex) { goog.asserts.assert(arr.length != null); @@ -118,14 +141,16 @@ goog.array.indexOf = goog.NATIVE_ARRAY_PROTOTYPES && * * See {@link http://tinyurl.com/developer-mozilla-org-array-lastindexof} * - * @param {goog.array.ArrayLike} arr The array to be searched. - * @param {*} obj The object for which we are searching. + * @param {!Array|!goog.array.ArrayLike} arr The array to be searched. + * @param {T} obj The object for which we are searching. * @param {?number=} opt_fromIndex The index at which to start the search. If * omitted the search starts at the end of the array. * @return {number} The index of the last matching array element. + * @template T */ goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.lastIndexOf ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.lastIndexOf) ? function(arr, obj, opt_fromIndex) { goog.asserts.assert(arr.length != null); @@ -161,7 +186,7 @@ goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && * Calls a function for each element in an array. Skips holes in the array. * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach} * - * @param {Array.|goog.array.ArrayLike} arr Array or array like object over + * @param {Array|goog.array.ArrayLike} arr Array or array like object over * which to iterate. * @param {?function(this: S, T, number, ?): ?} f The function to call for every * element. This function takes 3 arguments (the element, the index and the @@ -170,7 +195,8 @@ goog.array.lastIndexOf = goog.NATIVE_ARRAY_PROTOTYPES && * @template T,S */ goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.forEach ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.forEach) ? function(arr, f, opt_obj) { goog.asserts.assert(arr.length != null); @@ -191,7 +217,7 @@ goog.array.forEach = goog.NATIVE_ARRAY_PROTOTYPES && * Calls a function for each element in an array, starting from the last * element rather than the first. * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this: S, T, number, ?): ?} f The function to call for every * element. This function @@ -218,7 +244,7 @@ goog.array.forEachRight = function(arr, f, opt_obj) { * * See {@link http://tinyurl.com/developer-mozilla-org-array-filter} * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?):boolean} f The function to call for * every element. This function @@ -227,12 +253,13 @@ goog.array.forEachRight = function(arr, f, opt_obj) { * result array. If it is false the element is not included. * @param {S=} opt_obj The object to be used as the value of 'this' * within f. - * @return {!Array} a new array in which only elements that passed the test are - * present. + * @return {!Array} a new array in which only elements that passed the test + * are present. * @template T,S */ goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.filter ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.filter) ? function(arr, f, opt_obj) { goog.asserts.assert(arr.length != null); @@ -261,19 +288,19 @@ goog.array.filter = goog.NATIVE_ARRAY_PROTOTYPES && * * See {@link http://tinyurl.com/developer-mozilla-org-array-map} * - * @param {Array.|goog.array.ArrayLike} arr Array or array - * like object over which to iterate. - * @param {?function(this:S, T, number, ?):?} f The function to call for every - * element. This function - * takes 3 arguments (the element, the index and the array) and should - * return something. The result will be inserted into a new array. - * @param {S=} opt_obj The object to be used as the value of 'this' - * within f. - * @return {!Array} a new array with the results from f. - * @template T,S + * @param {Array|goog.array.ArrayLike} arr Array or array like object + * over which to iterate. + * @param {function(this:THIS, VALUE, number, ?): RESULT} f The function to call + * for every element. This function takes 3 arguments (the element, + * the index and the array) and should return something. The result will be + * inserted into a new array. + * @param {THIS=} opt_obj The object to be used as the value of 'this' within f. + * @return {!Array} a new array with the results from f. + * @template THIS, VALUE, RESULT */ goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.map ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.map) ? function(arr, f, opt_obj) { goog.asserts.assert(arr.length != null); @@ -302,9 +329,9 @@ goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0); * returns 10 * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. - * @param {?function(this:S, R, T, number, ?) : R} f The function to call for + * @param {function(this:S, R, T, number, ?) : R} f The function to call for * every element. This function * takes 4 arguments (the function's previous result or the initial value, * the value of the current array element, the current array index, and the @@ -316,20 +343,23 @@ goog.array.map = goog.NATIVE_ARRAY_PROTOTYPES && * @return {R} Result of evaluating f repeatedly across the values of the array. * @template T,S,R */ -goog.array.reduce = function(arr, f, val, opt_obj) { - if (arr.reduce) { - if (opt_obj) { - return arr.reduce(goog.bind(f, opt_obj), val); - } else { - return arr.reduce(f, val); - } - } - var rval = val; - goog.array.forEach(arr, function(val, index) { - rval = f.call(opt_obj, rval, val, index, arr); - }); - return rval; -}; +goog.array.reduce = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.reduce) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return goog.array.ARRAY_PROTOTYPE_.reduce.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEach(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr); + }); + return rval; + }; /** @@ -343,7 +373,7 @@ goog.array.reduce = function(arr, f, val, opt_obj) { * goog.array.reduceRight(a, function(r, v, i, arr) {return r + v;}, ''); * returns 'cba' * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, R, T, number, ?) : R} f The function to call for * every element. This function @@ -358,20 +388,23 @@ goog.array.reduce = function(arr, f, val, opt_obj) { * values of the array. * @template T,S,R */ -goog.array.reduceRight = function(arr, f, val, opt_obj) { - if (arr.reduceRight) { - if (opt_obj) { - return arr.reduceRight(goog.bind(f, opt_obj), val); - } else { - return arr.reduceRight(f, val); - } - } - var rval = val; - goog.array.forEachRight(arr, function(val, index) { - rval = f.call(opt_obj, rval, val, index, arr); - }); - return rval; -}; +goog.array.reduceRight = goog.NATIVE_ARRAY_PROTOTYPES && + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.reduceRight) ? + function(arr, f, val, opt_obj) { + goog.asserts.assert(arr.length != null); + if (opt_obj) { + f = goog.bind(f, opt_obj); + } + return goog.array.ARRAY_PROTOTYPE_.reduceRight.call(arr, f, val); + } : + function(arr, f, val, opt_obj) { + var rval = val; + goog.array.forEachRight(arr, function(val, index) { + rval = f.call(opt_obj, rval, val, index, arr); + }); + return rval; + }; /** @@ -381,7 +414,7 @@ goog.array.reduceRight = function(arr, f, val, opt_obj) { * * See {@link http://tinyurl.com/developer-mozilla-org-array-some} * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call for * for every element. This function takes 3 arguments (the element, the @@ -392,7 +425,8 @@ goog.array.reduceRight = function(arr, f, val, opt_obj) { * @template T,S */ goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.some ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.some) ? function(arr, f, opt_obj) { goog.asserts.assert(arr.length != null); @@ -417,7 +451,7 @@ goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && * * See {@link http://tinyurl.com/developer-mozilla-org-array-every} * - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call for * for every element. This function takes 3 arguments (the element, the @@ -428,7 +462,8 @@ goog.array.some = goog.NATIVE_ARRAY_PROTOTYPES && * @template T,S */ goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && - goog.array.ARRAY_PROTOTYPE_.every ? + (goog.array.ASSUME_NATIVE_FUNCTIONS || + goog.array.ARRAY_PROTOTYPE_.every) ? function(arr, f, opt_obj) { goog.asserts.assert(arr.length != null); @@ -450,7 +485,7 @@ goog.array.every = goog.NATIVE_ARRAY_PROTOTYPES && * Counts the array elements that fulfill the predicate, i.e. for which the * callback function returns true. Skips holes in the array. * - * @param {!(Array.|goog.array.ArrayLike)} arr Array or array like object + * @param {!(Array|goog.array.ArrayLike)} arr Array or array like object * over which to iterate. * @param {function(this: S, T, number, ?): boolean} f The function to call for * every element. Takes 3 arguments (the element, the index and the array). @@ -472,13 +507,13 @@ goog.array.count = function(arr, f, opt_obj) { /** * Search an array for the first element that satisfies a given condition and * return that element. - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call * for every element. This function takes 3 arguments (the element, the * index and the array) and should return a boolean. * @param {S=} opt_obj An optional "this" context for the function. - * @return {T} The first array element that passes the test, or null if no + * @return {T|null} The first array element that passes the test, or null if no * element is found. * @template T,S */ @@ -491,7 +526,7 @@ goog.array.find = function(arr, f, opt_obj) { /** * Search an array for the first element that satisfies a given condition and * return its index. - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call for * every element. This function @@ -517,14 +552,14 @@ goog.array.findIndex = function(arr, f, opt_obj) { /** * Search an array (in reverse order) for the last element that satisfies a * given condition and return that element. - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call * for every element. This function * takes 3 arguments (the element, the index and the array) and should * return a boolean. * @param {S=} opt_obj An optional "this" context for the function. - * @return {T} The last array element that passes the test, or null if no + * @return {T|null} The last array element that passes the test, or null if no * element is found. * @template T,S */ @@ -537,13 +572,13 @@ goog.array.findRight = function(arr, f, opt_obj) { /** * Search an array (in reverse order) for the last element that satisfies a * given condition and return its index. - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call * for every element. This function * takes 3 arguments (the element, the index and the array) and should * return a boolean. - * @param {Object=} opt_obj An optional "this" context for the function. + * @param {S=} opt_obj An optional "this" context for the function. * @return {number} The index of the last array element that passes the test, * or -1 if no element is found. * @template T,S @@ -600,7 +635,7 @@ goog.array.clear = function(arr) { /** * Pushes an item into an array, if it's not already in the array. - * @param {Array.} arr Array into which to insert the item. + * @param {Array} arr Array into which to insert the item. * @param {T} obj Value to add. * @template T */ @@ -637,7 +672,7 @@ goog.array.insertArrayAt = function(arr, elementsToAdd, opt_i) { /** * Inserts an object into an array before a specified object. - * @param {Array.} arr The array to modify. + * @param {Array} arr The array to modify. * @param {T} obj The object to insert. * @param {T=} opt_obj2 The object before which obj should be inserted. If obj2 * is omitted or not found, obj is inserted at the end of the array. @@ -655,9 +690,11 @@ goog.array.insertBefore = function(arr, obj, opt_obj2) { /** * Removes the first occurrence of a particular value from an array. - * @param {goog.array.ArrayLike} arr Array from which to remove value. - * @param {*} obj Object to remove. + * @param {Array|goog.array.ArrayLike} arr Array from which to remove + * value. + * @param {T} obj Object to remove. * @return {boolean} True if an element was removed. + * @template T */ goog.array.remove = function(arr, obj) { var i = goog.array.indexOf(arr, obj); @@ -688,7 +725,7 @@ goog.array.removeAt = function(arr, i) { /** * Removes the first value that satisfies the given condition. - * @param {Array.|goog.array.ArrayLike} arr Array or array + * @param {Array|goog.array.ArrayLike} arr Array or array * like object over which to iterate. * @param {?function(this:S, T, number, ?) : boolean} f The function to call * for every element. This function @@ -708,6 +745,31 @@ goog.array.removeIf = function(arr, f, opt_obj) { }; +/** + * Removes all values that satisfy the given condition. + * @param {Array|goog.array.ArrayLike} arr Array or array + * like object over which to iterate. + * @param {?function(this:S, T, number, ?) : boolean} f The function to call + * for every element. This function + * takes 3 arguments (the element, the index and the array) and should + * return a boolean. + * @param {S=} opt_obj An optional "this" context for the function. + * @return {number} The number of items removed + * @template T,S + */ +goog.array.removeAllIf = function(arr, f, opt_obj) { + var removedCount = 0; + goog.array.forEachRight(arr, function(val, index) { + if (f.call(opt_obj, val, index, arr)) { + if (goog.array.removeAt(arr, index)) { + removedCount++; + } + } + }); + return removedCount; +}; + + /** * Returns a new array that is the result of joining the arguments. If arrays * are passed then their items are added, however, if non-arrays are passed they @@ -733,7 +795,7 @@ goog.array.removeIf = function(arr, f, opt_obj) { * * @param {...*} var_args Items to concatenate. Arrays will have each item * added, while primitives and objects will be added as is. - * @return {!Array} The new resultant array. + * @return {!Array} The new resultant array. */ goog.array.concat = function(var_args) { return goog.array.ARRAY_PROTOTYPE_.concat.apply( @@ -741,13 +803,27 @@ goog.array.concat = function(var_args) { }; +/** + * Returns a new array that contains the contents of all the arrays passed. + * @param {...!Array} var_args + * @return {!Array} + * @template T + */ +goog.array.join = function(var_args) { + return goog.array.ARRAY_PROTOTYPE_.concat.apply( + goog.array.ARRAY_PROTOTYPE_, arguments); +}; + + /** * Converts an object to an array. - * @param {goog.array.ArrayLike} object The object to convert to an array. - * @return {!Array} The object converted into an array. If object has a + * @param {Array|goog.array.ArrayLike} object The object to convert to an + * array. + * @return {!Array} The object converted into an array. If object has a * length property, every property indexed with a non-negative number * less than length will be included in the result. If object does not * have a length property, an empty array will be returned. + * @template T */ goog.array.toArray = function(object) { var length = object.length; @@ -768,8 +844,10 @@ goog.array.toArray = function(object) { /** * Does a shallow copy of an array. - * @param {goog.array.ArrayLike} arr Array or array-like object to clone. - * @return {!Array} Clone of the input array. + * @param {Array|goog.array.ArrayLike} arr Array or array-like object to + * clone. + * @return {!Array} Clone of the input array. + * @template T */ goog.array.clone = goog.array.toArray; @@ -785,28 +863,18 @@ goog.array.clone = goog.array.toArray; * goog.array.extend(a, 2); * a; // [0, 1, 2] * - * @param {Array} arr1 The array to modify. - * @param {...*} var_args The elements or arrays of elements to add to arr1. + * @param {Array} arr1 The array to modify. + * @param {...(Array|VALUE)} var_args The elements or arrays of elements + * to add to arr1. + * @template VALUE */ goog.array.extend = function(arr1, var_args) { for (var i = 1; i < arguments.length; i++) { var arr2 = arguments[i]; - // If we have an Array or an Arguments object we can just call push - // directly. - var isArrayLike; - if (goog.isArray(arr2) || - // Detect Arguments. ES5 says that the [[Class]] of an Arguments object - // is "Arguments" but only V8 and JSC/Safari gets this right. We instead - // detect Arguments by checking for array like and presence of "callee". - (isArrayLike = goog.isArrayLike(arr2)) && - // The getter for callee throws an exception in strict mode - // according to section 10.6 in ES5 so check for presence instead. - Object.prototype.hasOwnProperty.call(arr2, 'callee')) { - arr1.push.apply(arr1, arr2); - } else if (isArrayLike) { - // Otherwise loop over arr2 to prevent copying the object. - var len1 = arr1.length; - var len2 = arr2.length; + if (goog.isArrayLike(arr2)) { + var len1 = arr1.length || 0; + var len2 = arr2.length || 0; + arr1.length = len1 + len2; for (var j = 0; j < len2; j++) { arr1[len1 + j] = arr2[j]; } @@ -822,15 +890,16 @@ goog.array.extend = function(arr1, var_args) { * splice. This means that it might work on other objects similar to arrays, * such as the arguments object. * - * @param {goog.array.ArrayLike} arr The array to modify. + * @param {Array|goog.array.ArrayLike} arr The array to modify. * @param {number|undefined} index The index at which to start changing the * array. If not defined, treated as 0. * @param {number} howMany How many elements to remove (0 means no removal. A * value below 0 is treated as zero and so is any other non number. Numbers * are floored). - * @param {...*} var_args Optional, additional elements to insert into the + * @param {...T} var_args Optional, additional elements to insert into the * array. - * @return {!Array} the removed elements. + * @return {!Array} the removed elements. + * @template T */ goog.array.splice = function(arr, index, howMany, var_args) { goog.asserts.assert(arr.length != null); @@ -845,11 +914,11 @@ goog.array.splice = function(arr, index, howMany, var_args) { * Array slice. This means that it might work on other objects similar to * arrays, such as the arguments object. * - * @param {Array.|goog.array.ArrayLike} arr The array from + * @param {Array|goog.array.ArrayLike} arr The array from * which to copy a segment. * @param {number} start The index of the first element to copy. * @param {number=} opt_end The index after the last element to copy. - * @return {!Array.} A new array containing the specified segment of the + * @return {!Array} A new array containing the specified segment of the * original array. * @template T */ @@ -876,27 +945,36 @@ goog.array.slice = function(arr, start, opt_end) { * For objects, duplicates are identified as having the same unique ID as * defined by {@link goog.getUid}. * + * Alternatively you can specify a custom hash function that returns a unique + * value for each item in the array it should consider unique. + * * Runtime: N, * Worstcase space: 2N (no dupes) * - * @param {goog.array.ArrayLike} arr The array from which to remove duplicates. + * @param {Array|goog.array.ArrayLike} arr The array from which to remove + * duplicates. * @param {Array=} opt_rv An optional array in which to return the results, * instead of performing the removal inplace. If specified, the original * array will remain unchanged. + * @param {function(T):string=} opt_hashFn An optional function to use to + * apply to every item in the array. This function should return a unique + * value for each item in the array it should consider unique. + * @template T */ -goog.array.removeDuplicates = function(arr, opt_rv) { +goog.array.removeDuplicates = function(arr, opt_rv, opt_hashFn) { var returnArray = opt_rv || arr; - - var seen = {}, cursorInsert = 0, cursorRead = 0; - while (cursorRead < arr.length) { - var current = arr[cursorRead++]; - + var defaultHashFn = function(item) { // Prefix each type with a single character representing the type to // prevent conflicting keys (e.g. true and 'true'). - var key = goog.isObject(current) ? - 'o' + goog.getUid(current) : + return goog.isObject(current) ? 'o' + goog.getUid(current) : (typeof current).charAt(0) + current; + }; + var hashFn = opt_hashFn || defaultHashFn; + var seen = {}, cursorInsert = 0, cursorRead = 0; + while (cursorRead < arr.length) { + var current = arr[cursorRead++]; + var key = hashFn(current); if (!Object.prototype.hasOwnProperty.call(seen, key)) { seen[key] = true; returnArray[cursorInsert++] = current; @@ -919,16 +997,18 @@ goog.array.removeDuplicates = function(arr, opt_rv) { * * Runtime: O(log n) * - * @param {goog.array.ArrayLike} arr The array to be searched. - * @param {*} target The sought value. - * @param {Function=} opt_compareFn Optional comparison function by which the - * array is ordered. Should take 2 arguments to compare, and return a - * negative number, zero, or a positive number depending on whether the - * first argument is less than, equal to, or greater than the second. + * @param {Array|goog.array.ArrayLike} arr The array to be searched. + * @param {TARGET} target The sought value. + * @param {function(TARGET, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. * @return {number} Lowest index of the target value if found, otherwise * (-(insertion point) - 1). The insertion point is where the value should * be inserted into arr to preserve the sorted property. Return value >= 0 * iff target is found. + * @template TARGET, VALUE */ goog.array.binarySearch = function(arr, target, opt_compareFn) { return goog.array.binarySearch_(arr, @@ -946,18 +1026,20 @@ goog.array.binarySearch = function(arr, target, opt_compareFn) { * * Runtime: O(log n) * - * @param {goog.array.ArrayLike} arr The array to be searched. - * @param {Function} evaluator Evaluator function that receives 3 arguments - * (the element, the index and the array). Should return a negative number, - * zero, or a positive number depending on whether the desired index is - * before, at, or after the element passed to it. - * @param {Object=} opt_obj The object to be used as the value of 'this' + * @param {Array|goog.array.ArrayLike} arr The array to be searched. + * @param {function(this:THIS, VALUE, number, ?): number} evaluator + * Evaluator function that receives 3 arguments (the element, the index and + * the array). Should return a negative number, zero, or a positive number + * depending on whether the desired index is before, at, or after the + * element passed to it. + * @param {THIS=} opt_obj The object to be used as the value of 'this' * within evaluator. * @return {number} Index of the leftmost element matched by the evaluator, if * such exists; otherwise (-(insertion point) - 1). The insertion point is * the index of the first element for which the evaluator returns negative, * or arr.length if no such element exists. The return value is non-negative * iff a match is found. + * @template THIS, VALUE */ goog.array.binarySelect = function(arr, evaluator, opt_obj) { return goog.array.binarySearch_(arr, evaluator, true /* isEvaluator */, @@ -977,19 +1059,22 @@ goog.array.binarySelect = function(arr, evaluator, opt_obj) { * * Runtime: O(log n) * - * @param {goog.array.ArrayLike} arr The array to be searched. - * @param {Function} compareFn Either an evaluator or a comparison function, - * as defined by binarySearch and binarySelect above. + * @param {Array|goog.array.ArrayLike} arr The array to be searched. + * @param {function(TARGET, VALUE): number| + * function(this:THIS, VALUE, number, ?): number} compareFn Either an + * evaluator or a comparison function, as defined by binarySearch + * and binarySelect above. * @param {boolean} isEvaluator Whether the function is an evaluator or a * comparison function. - * @param {*=} opt_target If the function is a comparison function, then this is - * the target to binary search for. - * @param {Object=} opt_selfObj If the function is an evaluator, this is an + * @param {TARGET=} opt_target If the function is a comparison function, then + * this is the target to binary search for. + * @param {THIS=} opt_selfObj If the function is an evaluator, this is an * optional this object for the evaluator. * @return {number} Lowest index of the target value if found, otherwise * (-(insertion point) - 1). The insertion point is where the value should * be inserted into arr to preserve the sorted property. Return value >= 0 * iff target is found. + * @template THIS, VALUE, TARGET * @private */ goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target, @@ -1032,7 +1117,7 @@ goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target, * * Runtime: Same as Array.prototype.sort * - * @param {Array.} arr The array to be sorted. + * @param {Array} arr The array to be sorted. * @param {?function(T,T):number=} opt_compareFn Optional comparison * function by which the * array is to be ordered. Should take 2 arguments to compare, and return a @@ -1042,10 +1127,7 @@ goog.array.binarySearch_ = function(arr, compareFn, isEvaluator, opt_target, */ goog.array.sort = function(arr, opt_compareFn) { // TODO(arv): Update type annotation since null is not accepted. - goog.asserts.assert(arr.length != null); - - goog.array.ARRAY_PROTOTYPE_.sort.call( - arr, opt_compareFn || goog.array.defaultCompare); + arr.sort(opt_compareFn || goog.array.defaultCompare); }; @@ -1059,7 +1141,7 @@ goog.array.sort = function(arr, opt_compareFn) { * Runtime: Same as Array.prototype.sort, plus an additional * O(n) overhead of copying the array twice. * - * @param {Array.} arr The array to be sorted. + * @param {Array} arr The array to be sorted. * @param {?function(T, T): number=} opt_compareFn Optional comparison function * by which the array is to be ordered. Should take 2 arguments to compare, * and return a negative number, zero, or a positive number depending on @@ -1082,28 +1164,55 @@ goog.array.stableSort = function(arr, opt_compareFn) { }; +/** + * Sort the specified array into ascending order based on item keys + * returned by the specified key function. + * If no opt_compareFn is specified, the keys are compared in ascending order + * using goog.array.defaultCompare. + * + * Runtime: O(S(f(n)), where S is runtime of goog.array.sort + * and f(n) is runtime of the key function. + * + * @param {Array} arr The array to be sorted. + * @param {function(T): K} keyFn Function taking array element and returning + * a key used for sorting this element. + * @param {?function(K, K): number=} opt_compareFn Optional comparison function + * by which the keys are to be ordered. Should take 2 arguments to compare, + * and return a negative number, zero, or a positive number depending on + * whether the first argument is less than, equal to, or greater than the + * second. + * @template T + * @template K + */ +goog.array.sortByKey = function(arr, keyFn, opt_compareFn) { + var keyCompareFn = opt_compareFn || goog.array.defaultCompare; + goog.array.sort(arr, function(a, b) { + return keyCompareFn(keyFn(a), keyFn(b)); + }); +}; + + /** * Sorts an array of objects by the specified object key and compare * function. If no compare function is provided, the key values are * compared in ascending order using goog.array.defaultCompare. * This won't work for keys that get renamed by the compiler. So use * {'foo': 1, 'bar': 2} rather than {foo: 1, bar: 2}. - * @param {Array.} arr An array of objects to sort. + * @param {Array} arr An array of objects to sort. * @param {string} key The object key to sort by. * @param {Function=} opt_compareFn The function to use to compare key * values. */ goog.array.sortObjectsByKey = function(arr, key, opt_compareFn) { - var compare = opt_compareFn || goog.array.defaultCompare; - goog.array.sort(arr, function(a, b) { - return compare(a[key], b[key]); - }); + goog.array.sortByKey(arr, + function(obj) { return obj[key]; }, + opt_compareFn); }; /** * Tells if the array is sorted. - * @param {!Array.} arr The array. + * @param {!Array} arr The array. * @param {?function(T,T):number=} opt_compareFn Function to compare the * array elements. * Should take 2 arguments to compare, and return a negative number, zero, @@ -1154,30 +1263,21 @@ goog.array.equals = function(arr1, arr2, opt_equalsFn) { }; -/** - * @deprecated Use {@link goog.array.equals}. - * @param {goog.array.ArrayLike} arr1 See {@link goog.array.equals}. - * @param {goog.array.ArrayLike} arr2 See {@link goog.array.equals}. - * @param {Function=} opt_equalsFn See {@link goog.array.equals}. - * @return {boolean} See {@link goog.array.equals}. - */ -goog.array.compare = function(arr1, arr2, opt_equalsFn) { - return goog.array.equals(arr1, arr2, opt_equalsFn); -}; - - /** * 3-way array compare function. - * @param {!goog.array.ArrayLike} arr1 The first array to compare. - * @param {!goog.array.ArrayLike} arr2 The second array to compare. - * @param {?function(?, ?): number=} opt_compareFn Optional comparison function - * by which the array is to be ordered. Should take 2 arguments to compare, - * and return a negative number, zero, or a positive number depending on - * whether the first argument is less than, equal to, or greater than the - * second. + * @param {!Array|!goog.array.ArrayLike} arr1 The first array to + * compare. + * @param {!Array|!goog.array.ArrayLike} arr2 The second array to + * compare. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is to be ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. * @return {number} Negative number, zero, or a positive number depending on * whether the first argument is less than, equal to, or greater than the * second. + * @template VALUE */ goog.array.compare3 = function(arr1, arr2, opt_compareFn) { var compare = opt_compareFn || goog.array.defaultCompare; @@ -1195,16 +1295,33 @@ goog.array.compare3 = function(arr1, arr2, opt_compareFn) { /** * Compares its two arguments for order, using the built in < and > * operators. - * @param {*} a The first object to be compared. - * @param {*} b The second object to be compared. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. * @return {number} A negative number, zero, or a positive number as the first - * argument is less than, equal to, or greater than the second. + * argument is less than, equal to, or greater than the second, + * respectively. + * @template VALUE */ goog.array.defaultCompare = function(a, b) { return a > b ? 1 : a < b ? -1 : 0; }; +/** + * Compares its two arguments for inverse order, using the built in < and > + * operators. + * @param {VALUE} a The first object to be compared. + * @param {VALUE} b The second object to be compared. + * @return {number} A negative number, zero, or a positive number as the first + * argument is greater than, equal to, or less than the second, + * respectively. + * @template VALUE + */ +goog.array.inverseDefaultCompare = function(a, b) { + return -goog.array.defaultCompare(a, b); +}; + + /** * Compares its two arguments for equality, using the built in === operator. * @param {*} a The first object to compare. @@ -1219,15 +1336,15 @@ goog.array.defaultCompareEquality = function(a, b) { /** * Inserts a value into a sorted array. The array is not modified if the * value is already present. - * @param {Array.} array The array to modify. - * @param {T} value The object to insert. - * @param {?function(T,T):number=} opt_compareFn Optional comparison function by - * which the - * array is ordered. Should take 2 arguments to compare, and return a - * negative number, zero, or a positive number depending on whether the - * first argument is less than, equal to, or greater than the second. + * @param {Array|goog.array.ArrayLike} array The array to modify. + * @param {VALUE} value The object to insert. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. * @return {boolean} True if an element was inserted. - * @template T + * @template VALUE */ goog.array.binaryInsert = function(array, value, opt_compareFn) { var index = goog.array.binarySearch(array, value, opt_compareFn); @@ -1241,13 +1358,15 @@ goog.array.binaryInsert = function(array, value, opt_compareFn) { /** * Removes a value from a sorted array. - * @param {Array} array The array to modify. - * @param {*} value The object to remove. - * @param {Function=} opt_compareFn Optional comparison function by which the - * array is ordered. Should take 2 arguments to compare, and return a - * negative number, zero, or a positive number depending on whether the - * first argument is less than, equal to, or greater than the second. + * @param {!Array|!goog.array.ArrayLike} array The array to modify. + * @param {VALUE} value The object to remove. + * @param {function(VALUE, VALUE): number=} opt_compareFn Optional comparison + * function by which the array is ordered. Should take 2 arguments to + * compare, and return a negative number, zero, or a positive number + * depending on whether the first argument is less than, equal to, or + * greater than the second. * @return {boolean} True if an element was removed. + * @template VALUE */ goog.array.binaryRemove = function(array, value, opt_compareFn) { var index = goog.array.binarySearch(array, value, opt_compareFn); @@ -1257,8 +1376,8 @@ goog.array.binaryRemove = function(array, value, opt_compareFn) { /** * Splits an array into disjoint buckets according to a splitting function. - * @param {Array.} array The array. - * @param {function(this:S, T,number,Array.):?} sorter Function to call for + * @param {Array} array The array. + * @param {function(this:S, T,number,Array):?} sorter Function to call for * every element. This takes 3 arguments (the element, the index and the * array) and must return a valid object key (a string, number, etc), or * undefined, if that object should not be placed in a bucket. @@ -1289,7 +1408,7 @@ goog.array.bucket = function(array, sorter, opt_obj) { /** * Creates a new object built from the provided array and the key-generation * function. - * @param {Array.|goog.array.ArrayLike} arr Array or array like object over + * @param {Array|goog.array.ArrayLike} arr Array or array like object over * which to iterate whose elements will be the values in the new object. * @param {?function(this:S, T, number, ?) : string} keyFunc The function to * call for every element. This function takes 3 arguments (the element, the @@ -1299,7 +1418,7 @@ goog.array.bucket = function(array, sorter, opt_obj) { * implementation-defined. * @param {S=} opt_obj The object to be used as the value of 'this' * within keyFunc. - * @return {!Object.} The new object. + * @return {!Object} The new object. * @template T,S */ goog.array.toObject = function(arr, keyFunc, opt_obj) { @@ -1327,7 +1446,7 @@ goog.array.toObject = function(arr, keyFunc, opt_obj) { * @param {number=} opt_end The optional end value of the range. * @param {number=} opt_step The step size between range values. Defaults to 1 * if opt_step is undefined or 0. - * @return {!Array.} An array of numbers for the requested range. May be + * @return {!Array} An array of numbers for the requested range. May be * an empty array if adding the step would not converge toward the end * value. */ @@ -1362,9 +1481,10 @@ goog.array.range = function(startOrEnd, opt_end, opt_step) { /** * Returns an array consisting of the given value repeated N times. * - * @param {*} value The value to repeat. + * @param {VALUE} value The value to repeat. * @param {number} n The repeat count. - * @return {!Array} An array with the repeated value. + * @return {!Array} An array with the repeated value. + * @template VALUE */ goog.array.repeat = function(value, n) { var array = []; @@ -1380,14 +1500,22 @@ goog.array.repeat = function(value, n) { * expanded in-place recursively. * * @param {...*} var_args The values to flatten. - * @return {!Array} An array containing the flattened values. + * @return {!Array} An array containing the flattened values. */ goog.array.flatten = function(var_args) { + var CHUNK_SIZE = 8192; + var result = []; for (var i = 0; i < arguments.length; i++) { var element = arguments[i]; if (goog.isArray(element)) { - result.push.apply(result, goog.array.flatten.apply(null, element)); + for (var c = 0; c < element.length; c += CHUNK_SIZE) { + var chunk = goog.array.slice(element, c, c + CHUNK_SIZE); + var recurseResult = goog.array.flatten.apply(null, chunk); + for (var r = 0; r < recurseResult.length; r++) { + result.push(recurseResult[r]); + } + } } else { result.push(element); } @@ -1405,9 +1533,9 @@ goog.array.flatten = function(var_args) { * For example, suppose list comprises [t, a, n, k, s]. After invoking * rotate(array, 1) (or rotate(array, -4)), array will comprise [s, t, a, n, k]. * - * @param {!Array.} array The array to rotate. + * @param {!Array} array The array to rotate. * @param {number} n The amount to rotate. - * @return {!Array.} The array. + * @return {!Array} The array. * @template T */ goog.array.rotate = function(array, n) { @@ -1425,6 +1553,28 @@ goog.array.rotate = function(array, n) { }; +/** + * Moves one item of an array to a new position keeping the order of the rest + * of the items. Example use case: keeping a list of JavaScript objects + * synchronized with the corresponding list of DOM elements after one of the + * elements has been dragged to a new position. + * @param {!(Array|Arguments|{length:number})} arr The array to modify. + * @param {number} fromIndex Index of the item to move between 0 and + * {@code arr.length - 1}. + * @param {number} toIndex Target index between 0 and {@code arr.length - 1}. + */ +goog.array.moveItem = function(arr, fromIndex, toIndex) { + goog.asserts.assert(fromIndex >= 0 && fromIndex < arr.length); + goog.asserts.assert(toIndex >= 0 && toIndex < arr.length); + // Remove 1 item at fromIndex. + var removedItems = goog.array.ARRAY_PROTOTYPE_.splice.call(arr, fromIndex, 1); + // Insert the removed item at toIndex. + goog.array.ARRAY_PROTOTYPE_.splice.call(arr, toIndex, 0, removedItems[0]); + // We don't use goog.array.insertAt and goog.array.removeAt, because they're + // significantly slower than splice. +}; + + /** * Creates a new array for which the element at position i is an array of the * ith element of the provided arrays. The returned array will only be as long @@ -1435,7 +1585,8 @@ goog.array.rotate = function(array, n) { * http://docs.python.org/library/functions.html#zip} * * @param {...!goog.array.ArrayLike} var_args Arrays to be combined. - * @return {!Array.} A new array of arrays created from provided arrays. + * @return {!Array>} A new array of arrays created from + * provided arrays. */ goog.array.zip = function(var_args) { if (!arguments.length) { @@ -1465,7 +1616,7 @@ goog.array.zip = function(var_args) { * * Runtime: O(n) * - * @param {!Array} arr The array to be shuffled. + * @param {!Array} arr The array to be shuffled. * @param {function():number=} opt_randFn Optional random function to use for * shuffling. * Takes no arguments, and returns a random number on the interval [0, 1). @@ -1483,3 +1634,22 @@ goog.array.shuffle = function(arr, opt_randFn) { arr[j] = tmp; } }; + + +/** + * Returns a new array of elements from arr, based on the indexes of elements + * provided by index_arr. For example, the result of index copying + * ['a', 'b', 'c'] with index_arr [1,0,0,2] is ['b', 'a', 'a', 'c']. + * + * @param {!Array} arr The array to get a indexed copy from. + * @param {!Array} index_arr An array of indexes to get from arr. + * @return {!Array} A new array of elements from arr in index_arr order. + * @template T + */ +goog.array.copyByIndex = function(arr, index_arr) { + var result = []; + goog.array.forEach(index_arr, function(index) { + result.push(arr[index]); + }); + return result; +}; diff --git a/lib/goog/asserts/asserts.js b/lib/goog/asserts/asserts.js index 905cd58..95513d1 100644 --- a/lib/goog/asserts/asserts.js +++ b/lib/goog/asserts/asserts.js @@ -31,12 +31,14 @@ * The compiler will leave in foo() (because its return value is used), * but it will remove bar() because it assumes it does not have side-effects. * + * @author agrieve@google.com (Andrew Grieve) */ goog.provide('goog.asserts'); goog.provide('goog.asserts.AssertionError'); goog.require('goog.debug.Error'); +goog.require('goog.dom.NodeType'); goog.require('goog.string'); @@ -50,9 +52,10 @@ goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); /** * Error object for failed assertions. * @param {string} messagePattern The pattern that was used to form message. - * @param {!Array.<*>} messageArgs The items to substitute into the pattern. + * @param {!Array<*>} messageArgs The items to substitute into the pattern. * @constructor * @extends {goog.debug.Error} + * @final */ goog.asserts.AssertionError = function(messagePattern, messageArgs) { messageArgs.unshift(messagePattern); @@ -75,13 +78,27 @@ goog.inherits(goog.asserts.AssertionError, goog.debug.Error); goog.asserts.AssertionError.prototype.name = 'AssertionError'; +/** + * The default error handler. + * @param {!goog.asserts.AssertionError} e The exception to be handled. + */ +goog.asserts.DEFAULT_ERROR_HANDLER = function(e) { throw e; }; + + +/** + * The handler responsible for throwing or logging assertion errors. + * @private {function(!goog.asserts.AssertionError)} + */ +goog.asserts.errorHandler_ = goog.asserts.DEFAULT_ERROR_HANDLER; + + /** * Throws an exception with the given message and "Assertion failed" prefixed * onto it. * @param {string} defaultMessage The message to use if givenMessage is empty. - * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage. + * @param {Array<*>} defaultArgs The substitution arguments for defaultMessage. * @param {string|undefined} givenMessage Message supplied by the caller. - * @param {Array.<*>} givenArgs The substitution arguments for givenMessage. + * @param {Array<*>} givenArgs The substitution arguments for givenMessage. * @throws {goog.asserts.AssertionError} When the value is not a number. * @private */ @@ -99,17 +116,32 @@ goog.asserts.doAssertFailure_ = // a stack trace is added to var message above. With this, a stack trace is // not added until this line (it causes the extra garbage to be added after // the assertion message instead of in the middle of it). - throw new goog.asserts.AssertionError('' + message, args || []); + var e = new goog.asserts.AssertionError('' + message, args || []); + goog.asserts.errorHandler_(e); +}; + + +/** + * Sets a custom error handler that can be used to customize the behavior of + * assertion failures, for example by turning all assertion failures into log + * messages. + * @param {function(!goog.asserts.AssertionError)} errorHandler + */ +goog.asserts.setErrorHandler = function(errorHandler) { + if (goog.asserts.ENABLE_ASSERTS) { + goog.asserts.errorHandler_ = errorHandler; + } }; /** * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is * true. - * @param {*} condition The condition to check. + * @template T + * @param {T} condition The condition to check. * @param {string=} opt_message Error message in case of failure. * @param {...*} var_args The items to substitute into the failure message. - * @return {*} The value of the condition. + * @return {T} The value of the condition. * @throws {goog.asserts.AssertionError} When the condition evaluates to false. */ goog.asserts.assert = function(condition, opt_message, var_args) { @@ -141,9 +173,9 @@ goog.asserts.assert = function(condition, opt_message, var_args) { */ goog.asserts.fail = function(opt_message, var_args) { if (goog.asserts.ENABLE_ASSERTS) { - throw new goog.asserts.AssertionError( + goog.asserts.errorHandler_(new goog.asserts.AssertionError( 'Failure' + (opt_message ? ': ' + opt_message : ''), - Array.prototype.slice.call(arguments, 1)); + Array.prototype.slice.call(arguments, 1))); } }; @@ -226,7 +258,7 @@ goog.asserts.assertObject = function(value, opt_message, var_args) { * @param {*} value The value to check. * @param {string=} opt_message Error message in case of failure. * @param {...*} var_args The items to substitute into the failure message. - * @return {!Array} The value, guaranteed to be a non-null array. + * @return {!Array} The value, guaranteed to be a non-null array. * @throws {goog.asserts.AssertionError} When the value is not an array. */ goog.asserts.assertArray = function(value, opt_message, var_args) { @@ -235,7 +267,7 @@ goog.asserts.assertArray = function(value, opt_message, var_args) { [goog.typeOf(value), value], opt_message, Array.prototype.slice.call(arguments, 2)); } - return /** @type {!Array} */ (value); + return /** @type {!Array} */ (value); }; @@ -258,6 +290,26 @@ goog.asserts.assertBoolean = function(value, opt_message, var_args) { }; +/** + * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. + * @param {*} value The value to check. + * @param {string=} opt_message Error message in case of failure. + * @param {...*} var_args The items to substitute into the failure message. + * @return {!Element} The value, likely to be a DOM Element when asserts are + * enabled. + * @throws {goog.asserts.AssertionError} When the value is not an Element. + */ +goog.asserts.assertElement = function(value, opt_message, var_args) { + if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) || + value.nodeType != goog.dom.NodeType.ELEMENT)) { + goog.asserts.doAssertFailure_('Expected Element but got %s: %s.', + [goog.typeOf(value), value], opt_message, + Array.prototype.slice.call(arguments, 2)); + } + return /** @type {!Element} */ (value); +}; + + /** * Checks if the value is an instance of the user-defined type if * goog.asserts.ENABLE_ASSERTS is true. @@ -270,14 +322,44 @@ goog.asserts.assertBoolean = function(value, opt_message, var_args) { * @param {...*} var_args The items to substitute into the failure message. * @throws {goog.asserts.AssertionError} When the value is not an instance of * type. - * @return {!T} + * @return {T} * @template T */ goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) { - goog.asserts.doAssertFailure_('instanceof check failed.', null, + goog.asserts.doAssertFailure_('Expected instanceof %s but got %s.', + [goog.asserts.getType_(type), goog.asserts.getType_(value)], opt_message, Array.prototype.slice.call(arguments, 3)); } return value; }; + +/** + * Checks that no enumerable keys are present in Object.prototype. Such keys + * would break most code that use {@code for (var ... in ...)} loops. + */ +goog.asserts.assertObjectPrototypeIsIntact = function() { + for (var key in Object.prototype) { + goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); + } +}; + + +/** + * Returns the type of a value. If a constructor is passed, and a suitable + * string cannot be found, 'unknown type name' will be returned. + * @param {*} value A constructor, object, or primitive. + * @return {string} The best display name for the value, or 'unknown type name'. + * @private + */ +goog.asserts.getType_ = function(value) { + if (value instanceof Function) { + return value.displayName || value.name || 'unknown type name'; + } else if (value instanceof Object) { + return value.constructor.displayName || value.constructor.name || + Object.prototype.toString.call(value); + } else { + return value === null ? 'null' : typeof value; + } +}; diff --git a/lib/goog/async/freelist.js b/lib/goog/async/freelist.js new file mode 100644 index 0000000..d0331f2 --- /dev/null +++ b/lib/goog/async/freelist.js @@ -0,0 +1,88 @@ +// Copyright 2015 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Simple freelist. + * + * An anterative to goog.structs.SimplePool, it imposes the requirement that the + * objects in the list contain a "next" property that can be used to maintain + * the pool. + */ + +goog.provide('goog.async.FreeList'); + + +/** + * @template ITEM + */ +goog.async.FreeList = goog.defineClass(null, { + /** + * @param {function():ITEM} create + * @param {function(ITEM):void} reset + * @param {number} limit + */ + constructor: function(create, reset, limit) { + /** @const {number} */ + this.limit_ = limit; + /** @const {function()} */ + this.create_ = create; + /** @const {function(ITEM):void} */ + this.reset_ = reset; + + /** @type {number} */ + this.occupants_ = 0; + /** @type {ITEM} */ + this.head_ = null; + }, + + /** + * @return {ITEM} + */ + get: function() { + var item; + if (this.occupants_ > 0) { + this.occupants_--; + item = this.head_; + this.head_ = item.next; + item.next = null; + } else { + item = this.create_(); + } + return item; + }, + + /** + * @param {ITEM} item An item available for possible future reuse. + */ + put: function(item) { + this.reset_(item); + if (this.occupants_ < this.limit_) { + this.occupants_++; + item.next = this.head_; + this.head_ = item; + } + }, + + /** + * Visible for testing. + * @package + * @return {number} + */ + occupants: function() { + return this.occupants_; + } +}); + + + diff --git a/lib/goog/async/nexttick.js b/lib/goog/async/nexttick.js new file mode 100644 index 0000000..bdbad6b --- /dev/null +++ b/lib/goog/async/nexttick.js @@ -0,0 +1,241 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Provides a function to schedule running a function as soon + * as possible after the current JS execution stops and yields to the event + * loop. + * + */ + +goog.provide('goog.async.nextTick'); +goog.provide('goog.async.throwException'); + +goog.require('goog.debug.entryPointRegistry'); +goog.require('goog.dom.TagName'); +goog.require('goog.functions'); +goog.require('goog.labs.userAgent.browser'); +goog.require('goog.labs.userAgent.engine'); + + +/** + * Throw an item without interrupting the current execution context. For + * example, if processing a group of items in a loop, sometimes it is useful + * to report an error while still allowing the rest of the batch to be + * processed. + * @param {*} exception + */ +goog.async.throwException = function(exception) { + // Each throw needs to be in its own context. + goog.global.setTimeout(function() { throw exception; }, 0); +}; + + +/** + * Fires the provided callbacks as soon as possible after the current JS + * execution context. setTimeout(…, 0) takes at least 4ms when called from + * within another setTimeout(…, 0) for legacy reasons. + * + * This will not schedule the callback as a microtask (i.e. a task that can + * preempt user input or networking callbacks). It is meant to emulate what + * setTimeout(_, 0) would do if it were not throttled. If you desire microtask + * behavior, use {@see goog.Promise} instead. + * + * @param {function(this:SCOPE)} callback Callback function to fire as soon as + * possible. + * @param {SCOPE=} opt_context Object in whose scope to call the listener. + * @param {boolean=} opt_useSetImmediate Avoid the IE workaround that + * ensures correctness at the cost of speed. See comments for details. + * @template SCOPE + */ +goog.async.nextTick = function(callback, opt_context, opt_useSetImmediate) { + var cb = callback; + if (opt_context) { + cb = goog.bind(callback, opt_context); + } + cb = goog.async.nextTick.wrapCallback_(cb); + // window.setImmediate was introduced and currently only supported by IE10+, + // but due to a bug in the implementation it is not guaranteed that + // setImmediate is faster than setTimeout nor that setImmediate N is before + // setImmediate N+1. That is why we do not use the native version if + // available. We do, however, call setImmediate if it is a normal function + // because that indicates that it has been replaced by goog.testing.MockClock + // which we do want to support. + // See + // http://connect.microsoft.com/IE/feedback/details/801823/setimmediate-and-messagechannel-are-broken-in-ie10 + // + // Note we do allow callers to also request setImmediate if they are willing + // to accept the possible tradeoffs of incorrectness in exchange for speed. + // The IE fallback of readystate change is much slower. + if (goog.isFunction(goog.global.setImmediate) && + // Opt in. + (opt_useSetImmediate || + // or it isn't a browser or the environment is weird + !goog.global.Window || !goog.global.Window.prototype || + // or something redefined setImmediate in which case we (YOLO) decide + // to use it (This is so that we use the mockClock setImmediate. sigh). + goog.global.Window.prototype.setImmediate != goog.global.setImmediate)) { + goog.global.setImmediate(cb); + return; + } + + // Look for and cache the custom fallback version of setImmediate. + if (!goog.async.nextTick.setImmediate_) { + goog.async.nextTick.setImmediate_ = + goog.async.nextTick.getSetImmediateEmulator_(); + } + goog.async.nextTick.setImmediate_(cb); +}; + + +/** + * Cache for the setImmediate implementation. + * @type {function(function())} + * @private + */ +goog.async.nextTick.setImmediate_; + + +/** + * Determines the best possible implementation to run a function as soon as + * the JS event loop is idle. + * @return {function(function())} The "setImmediate" implementation. + * @private + */ +goog.async.nextTick.getSetImmediateEmulator_ = function() { + // Create a private message channel and use it to postMessage empty messages + // to ourselves. + var Channel = goog.global['MessageChannel']; + // If MessageChannel is not available and we are in a browser, implement + // an iframe based polyfill in browsers that have postMessage and + // document.addEventListener. The latter excludes IE8 because it has a + // synchronous postMessage implementation. + if (typeof Channel === 'undefined' && typeof window !== 'undefined' && + window.postMessage && window.addEventListener && + // Presto (The old pre-blink Opera engine) has problems with iframes + // and contentWindow. + !goog.labs.userAgent.engine.isPresto()) { + /** @constructor */ + Channel = function() { + // Make an empty, invisible iframe. + var iframe = document.createElement(goog.dom.TagName.IFRAME); + iframe.style.display = 'none'; + iframe.src = ''; + document.documentElement.appendChild(iframe); + var win = iframe.contentWindow; + var doc = win.document; + doc.open(); + doc.write(''); + doc.close(); + // Do not post anything sensitive over this channel, as the workaround for + // pages with file: origin could allow that information to be modified or + // intercepted. + var message = 'callImmediate' + Math.random(); + // The same origin policy rejects attempts to postMessage from file: urls + // unless the origin is '*'. + // TODO(b/16335441): Use '*' origin for data: and other similar protocols. + var origin = win.location.protocol == 'file:' ? + '*' : win.location.protocol + '//' + win.location.host; + var onmessage = goog.bind(function(e) { + // Validate origin and message to make sure that this message was + // intended for us. If the origin is set to '*' (see above) only the + // message needs to match since, for example, '*' != 'file://'. Allowing + // the wildcard is ok, as we are not concerned with security here. + if ((origin != '*' && e.origin != origin) || e.data != message) { + return; + } + this['port1'].onmessage(); + }, this); + win.addEventListener('message', onmessage, false); + this['port1'] = {}; + this['port2'] = { + postMessage: function() { + win.postMessage(message, origin); + } + }; + }; + } + if (typeof Channel !== 'undefined' && + (!goog.labs.userAgent.browser.isIE())) { + // Exclude all of IE due to + // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ + // which allows starving postMessage with a busy setTimeout loop. + // This currently affects IE10 and IE11 which would otherwise be able + // to use the postMessage based fallbacks. + var channel = new Channel(); + // Use a fifo linked list to call callbacks in the right order. + var head = {}; + var tail = head; + channel['port1'].onmessage = function() { + if (goog.isDef(head.next)) { + head = head.next; + var cb = head.cb; + head.cb = null; + cb(); + } + }; + return function(cb) { + tail.next = { + cb: cb + }; + tail = tail.next; + channel['port2'].postMessage(0); + }; + } + // Implementation for IE6+: Script elements fire an asynchronous + // onreadystatechange event when inserted into the DOM. + if (typeof document !== 'undefined' && 'onreadystatechange' in + document.createElement(goog.dom.TagName.SCRIPT)) { + return function(cb) { + var script = document.createElement(goog.dom.TagName.SCRIPT); + script.onreadystatechange = function() { + // Clean up and call the callback. + script.onreadystatechange = null; + script.parentNode.removeChild(script); + script = null; + cb(); + cb = null; + }; + document.documentElement.appendChild(script); + }; + } + // Fall back to setTimeout with 0. In browsers this creates a delay of 5ms + // or more. + return function(cb) { + goog.global.setTimeout(cb, 0); + }; +}; + + +/** + * Helper function that is overrided to protect callbacks with entry point + * monitor if the application monitors entry points. + * @param {function()} callback Callback function to fire as soon as possible. + * @return {function()} The wrapped callback. + * @private + */ +goog.async.nextTick.wrapCallback_ = goog.functions.identity; + + +// Register the callback function as an entry point, so that it can be +// monitored for exception handling, etc. This has to be done in this file +// since it requires special code to handle all browsers. +goog.debug.entryPointRegistry.register( + /** + * @param {function(!Function): !Function} transformer The transforming + * function. + */ + function(transformer) { + goog.async.nextTick.wrapCallback_ = transformer; + }); diff --git a/lib/goog/async/run.js b/lib/goog/async/run.js new file mode 100644 index 0000000..d818683 --- /dev/null +++ b/lib/goog/async/run.js @@ -0,0 +1,139 @@ +// Copyright 2013 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.async.run'); + +goog.require('goog.async.WorkQueue'); +goog.require('goog.async.nextTick'); +goog.require('goog.async.throwException'); +goog.require('goog.testing.watchers'); + + +/** + * Fires the provided callback just before the current callstack unwinds, or as + * soon as possible after the current JS execution context. + * @param {function(this:THIS)} callback + * @param {THIS=} opt_context Object to use as the "this value" when calling + * the provided function. + * @template THIS + */ +goog.async.run = function(callback, opt_context) { + if (!goog.async.run.schedule_) { + goog.async.run.initializeRunner_(); + } + if (!goog.async.run.workQueueScheduled_) { + // Nothing is currently scheduled, schedule it now. + goog.async.run.schedule_(); + goog.async.run.workQueueScheduled_ = true; + } + + goog.async.run.workQueue_.add(callback, opt_context); +}; + + +/** + * Initializes the function to use to process the work queue. + * @private + */ +goog.async.run.initializeRunner_ = function() { + // If native Promises are available in the browser, just schedule the callback + // on a fulfilled promise, which is specified to be async, but as fast as + // possible. + if (goog.global.Promise && goog.global.Promise.resolve) { + var promise = goog.global.Promise.resolve(); + goog.async.run.schedule_ = function() { + promise.then(goog.async.run.processWorkQueue); + }; + } else { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + }; + } +}; + + +/** + * Forces goog.async.run to use nextTick instead of Promise. + * + * This should only be done in unit tests. It's useful because MockClock + * replaces nextTick, but not the browser Promise implementation, so it allows + * Promise-based code to be tested with MockClock. + * + * However, we also want to run promises if the MockClock is no longer in + * control so we schedule a backup "setTimeout" to the unmocked timeout if + * provided. + * + * @param {function(function())=} opt_realSetTimeout + */ +goog.async.run.forceNextTick = function(opt_realSetTimeout) { + goog.async.run.schedule_ = function() { + goog.async.nextTick(goog.async.run.processWorkQueue); + if (opt_realSetTimeout) { + opt_realSetTimeout(goog.async.run.processWorkQueue); + } + }; +}; + + +/** + * The function used to schedule work asynchronousely. + * @private {function()} + */ +goog.async.run.schedule_; + + +/** @private {boolean} */ +goog.async.run.workQueueScheduled_ = false; + + +/** @private {!goog.async.WorkQueue} */ +goog.async.run.workQueue_ = new goog.async.WorkQueue(); + + +if (goog.DEBUG) { + /** + * Reset the work queue. + * @private + */ + goog.async.run.resetQueue_ = function() { + goog.async.run.workQueueScheduled_ = false; + goog.async.run.workQueue_ = new goog.async.WorkQueue(); + }; + + // If there is a clock implemenation in use for testing + // and it is reset, reset the queue. + goog.testing.watchers.watchClockReset(goog.async.run.resetQueue_); +} + + +/** + * Run any pending goog.async.run work items. This function is not intended + * for general use, but for use by entry point handlers to run items ahead of + * goog.async.nextTick. + */ +goog.async.run.processWorkQueue = function() { + // NOTE: additional work queue items may be added while processing. + var item = null; + while (item = goog.async.run.workQueue_.remove()) { + try { + item.fn.call(item.scope); + } catch (e) { + goog.async.throwException(e); + } + goog.async.run.workQueue_.returnUnused(item); + } + + // There are no more work items, allow processing to be scheduled again. + goog.async.run.workQueueScheduled_ = false; +}; diff --git a/lib/goog/async/workqueue.js b/lib/goog/async/workqueue.js new file mode 100644 index 0000000..2d86c89 --- /dev/null +++ b/lib/goog/async/workqueue.js @@ -0,0 +1,139 @@ +// Copyright 2015 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('goog.async.WorkItem'); +goog.provide('goog.async.WorkQueue'); + +goog.require('goog.asserts'); +goog.require('goog.async.FreeList'); + + +// TODO(johnlenz): generalize the WorkQueue if this is used by more +// than goog.async.run. + + + +/** + * A low GC workqueue. The key elements of this design: + * - avoids the need for goog.bind or equivalent by carrying scope + * - avoids the need for array reallocation by using a linked list + * - minimizes work entry objects allocation by recycling objects + * @constructor + * @final + * @struct + */ +goog.async.WorkQueue = function() { + this.workHead_ = null; + this.workTail_ = null; +}; + + +/** @define {number} The maximum number of entries to keep for recycling. */ +goog.define('goog.async.WorkQueue.DEFAULT_MAX_UNUSED', 100); + + +/** @const @private {goog.async.FreeList} */ +goog.async.WorkQueue.freelist_ = new goog.async.FreeList( + function() {return new goog.async.WorkItem(); }, + function(item) {item.reset()}, + goog.async.WorkQueue.DEFAULT_MAX_UNUSED); + + +/** + * @param {function()} fn + * @param {Object|null|undefined} scope + */ +goog.async.WorkQueue.prototype.add = function(fn, scope) { + var item = this.getUnusedItem_(); + item.set(fn, scope); + + if (this.workTail_) { + this.workTail_.next = item; + this.workTail_ = item; + } else { + goog.asserts.assert(!this.workHead_); + this.workHead_ = item; + this.workTail_ = item; + } +}; + + +/** + * @return {goog.async.WorkItem} + */ +goog.async.WorkQueue.prototype.remove = function() { + var item = null; + + if (this.workHead_) { + item = this.workHead_; + this.workHead_ = this.workHead_.next; + if (!this.workHead_) { + this.workTail_ = null; + } + item.next = null; + } + return item; +}; + + +/** + * @param {goog.async.WorkItem} item + */ +goog.async.WorkQueue.prototype.returnUnused = function(item) { + goog.async.WorkQueue.freelist_.put(item); +}; + + +/** + * @return {goog.async.WorkItem} + * @private + */ +goog.async.WorkQueue.prototype.getUnusedItem_ = function() { + return goog.async.WorkQueue.freelist_.get(); +}; + + + +/** + * @constructor + * @final + * @struct + */ +goog.async.WorkItem = function() { + /** @type {?function()} */ + this.fn = null; + /** @type {Object|null|undefined} */ + this.scope = null; + /** @type {?goog.async.WorkItem} */ + this.next = null; +}; + + +/** + * @param {function()} fn + * @param {Object|null|undefined} scope + */ +goog.async.WorkItem.prototype.set = function(fn, scope) { + this.fn = fn; + this.scope = scope; + this.next = null; +}; + + +/** Reset the work item so they don't prevent GC before reuse */ +goog.async.WorkItem.prototype.reset = function() { + this.fn = null; + this.scope = null; + this.next = null; +}; diff --git a/lib/goog/base.js b/lib/goog/base.js index 39bd73a..bb232f8 100644 --- a/lib/goog/base.js +++ b/lib/goog/base.js @@ -19,6 +19,7 @@ * global CLOSURE_NO_DEPS is set to true. This allows projects to * include their own deps file(s) from different locations. * + * @author arv@google.com (Erik Arvidsson) * * @provideGoog */ @@ -32,9 +33,9 @@ var COMPILED = false; /** - * Base namespace for the Closure library. Checks to see goog is - * already defined in the current scope before assigning to prevent - * clobbering if base.js is loaded more than once. + * Base namespace for the Closure library. Checks to see goog is already + * defined in the current scope before assigning to prevent clobbering if + * base.js is loaded more than once. * * @const */ @@ -50,26 +51,61 @@ goog.global = this; /** * A hook for overriding the define values in uncompiled mode. * - * In uncompiled mode, {@code CLOSURE_DEFINES} may be defined before loading - * base.js. If a key is defined in {@code CLOSURE_DEFINES}, {@code goog.define} - * will use the value instead of the default value. This allows flags to be - * overwritten without compilation (this is normally accomplished with the - * compiler's "define" flag). + * In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before + * loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES}, + * {@code goog.define} will use the value instead of the default value. This + * allows flags to be overwritten without compilation (this is normally + * accomplished with the compiler's "define" flag). * * Example: *
        - *   var CLOSURE_DEFINES = {'goog.DEBUG', false};
        + *   var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
          * 
        * - * @type {Object.|undefined} + * @type {Object|undefined} + */ +goog.global.CLOSURE_UNCOMPILED_DEFINES; + + +/** + * A hook for overriding the define values in uncompiled or compiled mode, + * like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In + * uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence. + * + * Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or + * string literals or the compiler will emit an error. + * + * While any @define value may be set, only those set with goog.define will be + * effective for uncompiled code. + * + * Example: + *
        + *   var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
        + * 
        + * + * @type {Object|undefined} */ goog.global.CLOSURE_DEFINES; /** - * Builds an object structure for the provided namespace path, - * ensuring that names that already exist are not overwritten. For - * example: + * Returns true if the specified value is not undefined. + * WARNING: Do not use this to test if an object has a property. Use the in + * operator instead. + * + * @param {?} val Variable to test. + * @return {boolean} Whether variable is defined. + */ +goog.isDef = function(val) { + // void 0 always evaluates to undefined and hence we do not need to depend on + // the definition of the global variable named 'undefined'. + return val !== void 0; +}; + + +/** + * Builds an object structure for the provided namespace path, ensuring that + * names that already exist are not overwritten. For example: * "a.b.c" -> a = {};a.b={};a.b.c={}; * Used by goog.provide and goog.exportSymbol. * @param {string} name name of the object that this file defines. @@ -96,7 +132,7 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { // Parentheses added to eliminate strict JS warning in Firefox. for (var part; parts.length && (part = parts.shift());) { - if (!parts.length && opt_object !== undefined) { + if (!parts.length && goog.isDef(opt_object)) { // last part and we have an object; use it cur[part] = opt_object; } else if (cur[part]) { @@ -109,10 +145,11 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { /** - * Defines a named value. In uncompiled mode, the value is retreived from - * CLOSURE_DEFINES if the object is defined and has the property specified, - * and otherwise used the defined defaultValue. When compiled, the default - * can be overridden using compiler command-line options. + * Defines a named value. In uncompiled mode, the value is retrieved from + * CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and + * has the property specified, and otherwise used the defined defaultValue. + * When compiled the default can be overridden using the compiler + * options or the value set in the CLOSURE_DEFINES object. * * @param {string} name The distinguished name to provide. * @param {string|number|boolean} defaultValue @@ -120,8 +157,13 @@ goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) { goog.define = function(name, defaultValue) { var value = defaultValue; if (!COMPILED) { - if (goog.global.CLOSURE_DEFINES && Object.prototype.hasOwnProperty.call( - goog.global.CLOSURE_DEFINES, name)) { + if (goog.global.CLOSURE_UNCOMPILED_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) { + value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name]; + } else if (goog.global.CLOSURE_DEFINES && + Object.prototype.hasOwnProperty.call( + goog.global.CLOSURE_DEFINES, name)) { value = goog.global.CLOSURE_DEFINES[name]; } } @@ -137,7 +179,7 @@ goog.define = function(name, defaultValue) { * because they are generally used for debugging purposes and it is difficult * for the JSCompiler to statically determine whether they are used. */ -goog.DEBUG = true; +goog.define('goog.DEBUG', true); /** @@ -169,7 +211,7 @@ goog.define('goog.LOCALE', 'en'); // default to en * external libraries like Prototype, Datejs, and JQuery and setting this flag * to false forces closure to use its own implementations when possible. * - * If your javascript can be loaded by a third party site and you are wary about + * If your JavaScript can be loaded by a third party site and you are wary about * relying on non-standard implementations, specify * "--define goog.TRUSTED_SITE=false" to the JSCompiler. */ @@ -177,24 +219,73 @@ goog.define('goog.TRUSTED_SITE', true); /** - * Creates object stubs for a namespace. The presence of one or more - * goog.provide() calls indicate that the file defines the given - * objects/namespaces. Build tools also scan for provide/require statements + * @define {boolean} Whether a project is expected to be running in strict mode. + * + * This define can be used to trigger alternate implementations compatible with + * running in EcmaScript Strict mode or warn about unavailable functionality. + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode + * + */ +goog.define('goog.STRICT_MODE_COMPATIBLE', false); + + +/** + * @define {boolean} Whether code that calls {@link goog.setTestOnly} should + * be disallowed in the compilation unit. + */ +goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG); + + +/** + * @define {boolean} Whether to use a Chrome app CSP-compliant method for + * loading scripts via goog.require. @see appendScriptSrcNode_. + */ +goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false); + + +/** + * Defines a namespace in Closure. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * The presence of one or more goog.provide() calls in a file indicates + * that the file defines the given objects/namespaces. + * Provided symbols must not be null or undefined. + * + * In addition, goog.provide() creates the object stubs for a namespace + * (for example, goog.provide("goog.foo.bar") will create the object + * goog.foo.bar if it does not already exist). + * + * Build tools also scan for provide/require/module statements * to discern dependencies, build dependency files (see deps.js), etc. + * * @see goog.require + * @see goog.module * @param {string} name Namespace provided by this file in the form * "goog.package.part". */ goog.provide = function(name) { if (!COMPILED) { - // Ensure that the same namespace isn't provided twice. This is intended - // to teach new developers that 'goog.provide' is effectively a variable - // declaration. And when JSCompiler transforms goog.provide into a real - // variable declaration, the compiled JS should work the same as the raw - // JS--even when the raw JS uses goog.provide incorrectly. + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file if (goog.isProvided_(name)) { throw Error('Namespace "' + name + '" already declared.'); } + } + + goog.constructNamespace_(name); +}; + + +/** + * @param {string} name Namespace provided by this file in the form + * "goog.package.part". + * @param {Object=} opt_obj The object to embed in the namespace. + * @private + */ +goog.constructNamespace_ = function(name, opt_obj) { + if (!COMPILED) { delete goog.implicitNamespaces_[name]; var namespace = name; @@ -206,7 +297,166 @@ goog.provide = function(name) { } } - goog.exportPath_(name); + goog.exportPath_(name, opt_obj); +}; + + +/** + * Module identifier validation regexp. + * Note: This is a conservative check, it is very possible to be more lenient, + * the primary exclusion here is "/" and "\" and a leading ".", these + * restrictions are intended to leave the door open for using goog.require + * with relative file paths rather than module identifiers. + * @private + */ +goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/; + + +/** + * Defines a module in Closure. + * + * Marks that this file must be loaded as a module and claims the namespace. + * + * A namespace may only be defined once in a codebase. It may be defined using + * goog.provide() or goog.module(). + * + * goog.module() has three requirements: + * - goog.module may not be used in the same file as goog.provide. + * - goog.module must be the first statement in the file. + * - only one goog.module is allowed per file. + * + * When a goog.module annotated file is loaded, it is enclosed in + * a strict function closure. This means that: + * - any variables declared in a goog.module file are private to the file + * (not global), though the compiler is expected to inline the module. + * - The code must obey all the rules of "strict" JavaScript. + * - the file will be marked as "use strict" + * + * NOTE: unlike goog.provide, goog.module does not declare any symbols by + * itself. If declared symbols are desired, use + * goog.module.declareLegacyNamespace(). + * + * + * See the public goog.module proposal: http://goo.gl/Va1hin + * + * @param {string} name Namespace provided by this file in the form + * "goog.package.part", is expected but not required. + */ +goog.module = function(name) { + if (!goog.isString(name) || + !name || + name.search(goog.VALID_MODULE_RE_) == -1) { + throw Error('Invalid module identifier'); + } + if (!goog.isInModuleLoader_()) { + throw Error('Module ' + name + ' has been loaded incorrectly.'); + } + if (goog.moduleLoaderState_.moduleName) { + throw Error('goog.module may only be called once per module.'); + } + + // Store the module name for the loader. + goog.moduleLoaderState_.moduleName = name; + if (!COMPILED) { + // Ensure that the same namespace isn't provided twice. + // A goog.module/goog.provide maps a goog.require to a specific file + if (goog.isProvided_(name)) { + throw Error('Namespace "' + name + '" already declared.'); + } + delete goog.implicitNamespaces_[name]; + } +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * + * Note: This is not an alternative to goog.require, it does not + * indicate a hard dependency, instead it is used to indicate + * an optional dependency or to access the exports of a module + * that has already been loaded. + * @suppress {missingProvide} + */ +goog.module.get = function(name) { + return goog.module.getInternal_(name); +}; + + +/** + * @param {string} name The module identifier. + * @return {?} The module exports for an already loaded module or null. + * @private + */ +goog.module.getInternal_ = function(name) { + if (!COMPILED) { + if (goog.isProvided_(name)) { + // goog.require only return a value with-in goog.module files. + return name in goog.loadedModules_ ? + goog.loadedModules_[name] : + goog.getObjectByName(name); + } else { + return null; + } + } +}; + + +/** + * @private {?{ + * moduleName: (string|undefined), + * declareTestMethods: boolean + * }} + */ +goog.moduleLoaderState_ = null; + + +/** + * @private + * @return {boolean} Whether a goog.module is currently being initialized. + */ +goog.isInModuleLoader_ = function() { + return goog.moduleLoaderState_ != null; +}; + + +/** + * Indicate that a module's exports that are known test methods should + * be copied to the global object. This makes the test methods visible to + * test runners that inspect the global object. + * + * TODO(johnlenz): Make the test framework aware of goog.module so + * that this isn't necessary. Alternately combine this with goog.setTestOnly + * to minimize boiler plate. + * @suppress {missingProvide} + * @deprecated This approach does not translate to ES6 module syntax, instead + * use goog.testing.testSuite to declare the test methods. + */ +goog.module.declareTestMethods = function() { + if (!goog.isInModuleLoader_()) { + throw new Error('goog.module.declareTestMethods must be called from ' + + 'within a goog.module'); + } + goog.moduleLoaderState_.declareTestMethods = true; +}; + + +/** + * Provide the module's exports as a globally accessible object under the + * module's declared name. This is intended to ease migration to goog.module + * for files that have existing usages. + * @suppress {missingProvide} + */ +goog.module.declareLegacyNamespace = function() { + if (!COMPILED && !goog.isInModuleLoader_()) { + throw new Error('goog.module.declareLegacyNamespace must be called from ' + + 'within a goog.module'); + } + if (!COMPILED && !goog.moduleLoaderState_.moduleName) { + throw Error('goog.module must be called prior to ' + + 'goog.module.declareLegacyNamespace.'); + } + goog.moduleLoaderState_.declareLegacyNamespace = true; }; @@ -214,22 +464,41 @@ goog.provide = function(name) { * Marks that the current file should only be used for testing, and never for * live code in production. * - * In the case of unit tests, the message may optionally be an exact - * namespace for the test (e.g. 'goog.stringTest'). The linter will then - * ignore the extra provide (if not explicitly defined in the code). + * In the case of unit tests, the message may optionally be an exact namespace + * for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra + * provide (if not explicitly defined in the code). * * @param {string=} opt_message Optional message to add to the error that's * raised when used in production code. */ goog.setTestOnly = function(opt_message) { - if (COMPILED && !goog.DEBUG) { + if (goog.DISALLOW_TEST_ONLY_CODE) { opt_message = opt_message || ''; throw Error('Importing test-only code into non-debug environment' + - opt_message ? ': ' + opt_message : '.'); + (opt_message ? ': ' + opt_message : '.')); } }; +/** + * Forward declares a symbol. This is an indication to the compiler that the + * symbol may be used in the source yet is not required and may not be provided + * in compilation. + * + * The most common usage of forward declaration is code that takes a type as a + * function parameter but does not need to require it. By forward declaring + * instead of requiring, no hard dependency is made, and (if not required + * elsewhere) the namespace may never be required and thus, not be pulled + * into the JavaScript binary. If it is required elsewhere, it will be type + * checked as normal. + * + * + * @param {string} name The namespace to forward declare in the form of + * "goog.package.part". + */ +goog.forwardDeclare = function(name) {}; + + if (!COMPILED) { /** @@ -240,25 +509,33 @@ if (!COMPILED) { * @private */ goog.isProvided_ = function(name) { - return !goog.implicitNamespaces_[name] && !!goog.getObjectByName(name); + return (name in goog.loadedModules_) || + (!goog.implicitNamespaces_[name] && + goog.isDefAndNotNull(goog.getObjectByName(name))); }; /** * Namespaces implicitly defined by goog.provide. For example, - * goog.provide('goog.events.Event') implicitly declares - * that 'goog' and 'goog.events' must be namespaces. + * goog.provide('goog.events.Event') implicitly declares that 'goog' and + * 'goog.events' must be namespaces. * - * @type {Object} + * @type {!Object} * @private */ - goog.implicitNamespaces_ = {}; + goog.implicitNamespaces_ = {'goog.module': true}; + + // NOTE: We add goog.module as an implicit namespace as goog.module is defined + // here and because the existing module package has not been moved yet out of + // the goog.module namespace. This satisifies both the debug loader and + // ahead-of-time dependency management. } /** - * Returns an object based on its fully qualified external name. If you are - * using a compilation pass that renames property names beware that using this - * function will not find renamed properties. + * Returns an object based on its fully qualified external name. The object + * is not found if null or undefined. If you are using a compilation pass that + * renames property names beware that using this function will not find renamed + * properties. * * @param {string} name The fully qualified name. * @param {Object=} opt_obj The object within which to look; default is @@ -282,7 +559,7 @@ goog.getObjectByName = function(name, opt_obj) { /** * Globalizes a whole namespace, such as goog or goog.lang. * - * @param {Object} obj The namespace to globalize. + * @param {!Object} obj The namespace to globalize. * @param {Object=} opt_global The object to add the properties to. * @deprecated Properties may be explicitly exported to the global scope, but * this should no longer be done in bulk. @@ -298,22 +575,21 @@ goog.globalize = function(obj, opt_global) { /** * Adds a dependency from a file to the files it requires. * @param {string} relPath The path to the js file. - * @param {Array} provides An array of strings with the names of the objects - * this file provides. - * @param {Array} requires An array of strings with the names of the objects - * this file requires. - */ -goog.addDependency = function(relPath, provides, requires) { + * @param {!Array} provides An array of strings with + * the names of the objects this file provides. + * @param {!Array} requires An array of strings with + * the names of the objects this file requires. + * @param {boolean=} opt_isModule Whether this dependency must be loaded as + * a module as declared by goog.module. + */ +goog.addDependency = function(relPath, provides, requires, opt_isModule) { if (goog.DEPENDENCIES_ENABLED) { var provide, require; var path = relPath.replace(/\\/g, '/'); var deps = goog.dependencies_; for (var i = 0; provide = provides[i]; i++) { deps.nameToPath[provide] = path; - if (!(path in deps.pathToNames)) { - deps.pathToNames[path] = {}; - } - deps.pathToNames[path][provide] = true; + deps.pathIsModule[path] = !!opt_isModule; } for (var j = 0; require = requires[j]; j++) { if (!(path in deps.requires)) { @@ -327,17 +603,17 @@ goog.addDependency = function(relPath, provides, requires) { -// NOTE(nnaze): The debug DOM loader was included in base.js as an orignal -// way to do "debug-mode" development. The dependency system can sometimes -// be confusing, as can the debug DOM loader's asyncronous nature. +// NOTE(nnaze): The debug DOM loader was included in base.js as an original way +// to do "debug-mode" development. The dependency system can sometimes be +// confusing, as can the debug DOM loader's asynchronous nature. // -// With the DOM loader, a call to goog.require() is not blocking -- the -// script will not load until some point after the current script. If a -// namespace is needed at runtime, it needs to be defined in a previous -// script, or loaded via require() with its registered dependencies. +// With the DOM loader, a call to goog.require() is not blocking -- the script +// will not load until some point after the current script. If a namespace is +// needed at runtime, it needs to be defined in a previous script, or loaded via +// require() with its registered dependencies. // User-defined namespaces may need their own deps file. See http://go/js_deps, // http://go/genjsdeps, or, externally, DepsWriter. -// http://code.google.com/closure/library/docs/depswriter.html +// https://developers.google.com/closure/library/docs/depswriter // // Because of legacy clients, the DOM loader can't be easily removed from // base.js. Work is being done to make it disableable or replaceable for @@ -359,25 +635,40 @@ goog.define('goog.ENABLE_DEBUG_LOADER', true); /** - * Implements a system for the dynamic resolution of dependencies - * that works in parallel with the BUILD system. Note that all calls - * to goog.require will be stripped by the JSCompiler when the - * --closure_pass option is used. + * @param {string} msg + * @private + */ +goog.logToConsole_ = function(msg) { + if (goog.global.console) { + goog.global.console['error'](msg); + } +}; + + +/** + * Implements a system for the dynamic resolution of dependencies that works in + * parallel with the BUILD system. Note that all calls to goog.require will be + * stripped by the JSCompiler when the --closure_pass option is used. * @see goog.provide - * @param {string} name Namespace to include (as was given in goog.provide()) - * in the form "goog.package.part". + * @param {string} name Namespace to include (as was given in goog.provide()) in + * the form "goog.package.part". + * @return {?} If called within a goog.module file, the associated namespace or + * module otherwise null. */ goog.require = function(name) { - // if the object already exists we do not need do do anything - // TODO(arv): If we start to support require based on file name this has - // to change - // TODO(arv): If we allow goog.foo.* this has to change - // TODO(arv): If we implement dynamic load after page load we should probably - // not remove this code for the compiled output + // If the object already exists we do not need do do anything. if (!COMPILED) { + if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) { + goog.maybeProcessDeferredDep_(name); + } + if (goog.isProvided_(name)) { - return; + if (goog.isInModuleLoader_()) { + return goog.module.getInternal_(name); + } else { + return null; + } } if (goog.ENABLE_DEBUG_LOADER) { @@ -385,24 +676,20 @@ goog.require = function(name) { if (path) { goog.included_[path] = true; goog.writeScripts_(); - return; + return null; } } var errorMessage = 'goog.require could not find: ' + name; - if (goog.global.console) { - goog.global.console['error'](errorMessage); - } - - - throw Error(errorMessage); + goog.logToConsole_(errorMessage); + throw Error(errorMessage); } }; /** - * Path for included scripts + * Path for included scripts. * @type {string} */ goog.basePath = ''; @@ -416,8 +703,7 @@ goog.global.CLOSURE_BASE_PATH; /** - * Whether to write out Closure's deps file. By default, - * the deps are written. + * Whether to write out Closure's deps file. By default, the deps are written. * @type {boolean|undefined} */ goog.global.CLOSURE_NO_DEPS; @@ -431,6 +717,7 @@ goog.global.CLOSURE_NO_DEPS; * * The function is passed the script source, which is a relative URI. It should * return true if the script was imported, false otherwise. + * @type {(function(string): boolean)|undefined} */ goog.global.CLOSURE_IMPORT_SCRIPT; @@ -442,35 +729,19 @@ goog.global.CLOSURE_IMPORT_SCRIPT; goog.nullFunction = function() {}; -/** - * The identity function. Returns its first argument. - * - * @param {*=} opt_returnValue The single value that will be returned. - * @param {...*} var_args Optional trailing arguments. These are ignored. - * @return {?} The first argument. We can't know the type -- just pass it along - * without type. - * @deprecated Use goog.functions.identity instead. - */ -goog.identityFunction = function(opt_returnValue, var_args) { - return opt_returnValue; -}; - /** * When defining a class Foo with an abstract method bar(), you can do: - * * Foo.prototype.bar = goog.abstractMethod * - * Now if a subclass of Foo fails to override bar(), an error - * will be thrown when bar() is invoked. + * Now if a subclass of Foo fails to override bar(), an error will be thrown + * when bar() is invoked. * - * Note: This does not take the name of the function to override as - * an argument because that would make it more difficult to obfuscate - * our JavaScript code. + * Note: This does not take the name of the function to override as an argument + * because that would make it more difficult to obfuscate our JavaScript code. * * @type {!Function} - * @throws {Error} when invoked to indicate the method should be - * overridden. + * @throws {Error} when invoked to indicate the method should be overridden. */ goog.abstractMethod = function() { throw Error('unimplemented abstract method'); @@ -478,8 +749,8 @@ goog.abstractMethod = function() { /** - * Adds a {@code getInstance} static method that always return the same instance - * object. + * Adds a {@code getInstance} static method that always returns the same + * instance object. * @param {!Function} ctor The constructor for the class to add the static * method to. */ @@ -501,12 +772,37 @@ goog.addSingletonGetter = function(ctor) { * All singleton classes that have been instantiated, for testing. Don't read * it directly, use the {@code goog.testing.singleton} module. The compiler * removes this variable if unused. - * @type {!Array.} + * @type {!Array} * @private */ goog.instantiatedSingletons_ = []; +/** + * @define {boolean} Whether to load goog.modules using {@code eval} when using + * the debug loader. This provides a better debugging experience as the + * source is unmodified and can be edited using Chrome Workspaces or similar. + * However in some environments the use of {@code eval} is banned + * so we provide an alternative. + */ +goog.define('goog.LOAD_MODULE_USING_EVAL', true); + + +/** + * @define {boolean} Whether the exports of goog.modules should be sealed when + * possible. + */ +goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG); + + +/** + * The registry of initialized modules: + * the module identifier to module exports map. + * @private @const {!Object} + */ +goog.loadedModules_ = {}; + + /** * True if goog.dependencies_ is available. * @const {boolean} @@ -516,28 +812,39 @@ goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER; if (goog.DEPENDENCIES_ENABLED) { /** - * Object used to keep track of urls that have already been added. This - * record allows the prevention of circular dependencies. - * @type {Object} - * @private + * Object used to keep track of urls that have already been added. This record + * allows the prevention of circular dependencies. + * @private {!Object} */ goog.included_ = {}; /** * This object is used to keep track of dependencies and other data that is - * used for loading scripts + * used for loading scripts. * @private - * @type {Object} + * @type {{ + * pathIsModule: !Object, + * nameToPath: !Object, + * requires: !Object>, + * visited: !Object, + * written: !Object, + * deferred: !Object + * }} */ goog.dependencies_ = { - pathToNames: {}, // 1 to many + pathIsModule: {}, // 1 to 1 + nameToPath: {}, // 1 to 1 + requires: {}, // 1 to many - // used when resolving dependencies to prevent us from - // visiting the file twice + + // Used when resolving dependencies to prevent us from visiting file twice. visited: {}, - written: {} // used to keep track of script files we have written + + written: {}, // Used to keep track of script files we have written. + + deferred: {} // Used to track deferred module evaluations in old IEs }; @@ -554,7 +861,7 @@ if (goog.DEPENDENCIES_ENABLED) { /** - * Tries to detect the base path of the base.js script that bootstraps Closure + * Tries to detect the base path of base.js script that bootstraps Closure. * @private */ goog.findBasePath_ = function() { @@ -565,11 +872,12 @@ if (goog.DEPENDENCIES_ENABLED) { return; } var doc = goog.global.document; - var scripts = doc.getElementsByTagName('script'); + var scripts = doc.getElementsByTagName('SCRIPT'); // Search backwards since the current script is in almost all cases the one // that has base.js. for (var i = scripts.length - 1; i >= 0; --i) { - var src = scripts[i].src; + var script = /** @type {!HTMLScriptElement} */ (scripts[i]); + var src = script.src; var qmark = src.lastIndexOf('?'); var l = qmark == -1 ? src.length : qmark; if (src.substr(l - 7, 7) == 'base.js') { @@ -584,33 +892,303 @@ if (goog.DEPENDENCIES_ENABLED) { * Imports a script if, and only if, that script hasn't already been imported. * (Must be called at execution time) * @param {string} src Script source. + * @param {string=} opt_sourceText The optionally source text to evaluate * @private */ - goog.importScript_ = function(src) { + goog.importScript_ = function(src, opt_sourceText) { var importScript = goog.global.CLOSURE_IMPORT_SCRIPT || goog.writeScriptTag_; - if (!goog.dependencies_.written[src] && importScript(src)) { + if (importScript(src, opt_sourceText)) { goog.dependencies_.written[src] = true; } }; + /** @const @private {boolean} */ + goog.IS_OLD_IE_ = !goog.global.atob && goog.global.document && + goog.global.document.all; + + + /** + * Given a URL initiate retrieval and execution of the module. + * @param {string} src Script source URL. + * @private + */ + goog.importModule_ = function(src) { + // In an attempt to keep browsers from timing out loading scripts using + // synchronous XHRs, put each load in its own script block. + var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");'; + + if (goog.importScript_('', bootstrap)) { + goog.dependencies_.written[src] = true; + } + }; + + + /** @private {!Array} */ + goog.queuedModules_ = []; + + + /** + * Return an appropriate module text. Suitable to insert into + * a script tag (that is unescaped). + * @param {string} srcUrl + * @param {string} scriptText + * @return {string} + * @private + */ + goog.wrapModule_ = function(srcUrl, scriptText) { + if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) { + return '' + + 'goog.loadModule(function(exports) {' + + '"use strict";' + + scriptText + + '\n' + // terminate any trailing single line comment. + ';return exports' + + '});' + + '\n//# sourceURL=' + srcUrl + '\n'; + } else { + return '' + + 'goog.loadModule(' + + goog.global.JSON.stringify( + scriptText + '\n//# sourceURL=' + srcUrl + '\n') + + ');'; + } + }; + + // On IE9 and earlier, it is necessary to handle + // deferred module loads. In later browsers, the + // code to be evaluated is simply inserted as a script + // block in the correct order. To eval deferred + // code at the right time, we piggy back on goog.require to call + // goog.maybeProcessDeferredDep_. + // + // The goog.requires are used both to bootstrap + // the loading process (when no deps are available) and + // declare that they should be available. + // + // Here we eval the sources, if all the deps are available + // either already eval'd or goog.require'd. This will + // be the case when all the dependencies have already + // been loaded, and the dependent module is loaded. + // + // But this alone isn't sufficient because it is also + // necessary to handle the case where there is no root + // that is not deferred. For that there we register for an event + // and trigger goog.loadQueuedModules_ handle any remaining deferred + // evaluations. + + /** + * Handle any remaining deferred goog.module evals. + * @private + */ + goog.loadQueuedModules_ = function() { + var count = goog.queuedModules_.length; + if (count > 0) { + var queue = goog.queuedModules_; + goog.queuedModules_ = []; + for (var i = 0; i < count; i++) { + var path = queue[i]; + goog.maybeProcessDeferredPath_(path); + } + } + }; + + + /** + * Eval the named module if its dependencies are + * available. + * @param {string} name The module to load. + * @private + */ + goog.maybeProcessDeferredDep_ = function(name) { + if (goog.isDeferredModule_(name) && + goog.allDepsAreAvailable_(name)) { + var path = goog.getPathFromDeps_(name); + goog.maybeProcessDeferredPath_(goog.basePath + path); + } + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose evaluation has been deferred. + * @private + */ + goog.isDeferredModule_ = function(name) { + var path = goog.getPathFromDeps_(name); + if (path && goog.dependencies_.pathIsModule[path]) { + var abspath = goog.basePath + path; + return (abspath) in goog.dependencies_.deferred; + } + return false; + }; + + /** + * @param {string} name The module to check. + * @return {boolean} Whether the name represents a + * module whose declared dependencies have all been loaded + * (eval'd or a deferred module load) + * @private + */ + goog.allDepsAreAvailable_ = function(name) { + var path = goog.getPathFromDeps_(name); + if (path && (path in goog.dependencies_.requires)) { + for (var requireName in goog.dependencies_.requires[path]) { + if (!goog.isProvided_(requireName) && + !goog.isDeferredModule_(requireName)) { + return false; + } + } + } + return true; + }; + + + /** + * @param {string} abspath + * @private + */ + goog.maybeProcessDeferredPath_ = function(abspath) { + if (abspath in goog.dependencies_.deferred) { + var src = goog.dependencies_.deferred[abspath]; + delete goog.dependencies_.deferred[abspath]; + goog.globalEval(src); + } + }; + + + /** + * @param {function(?):?|string} moduleDef The module definition. + */ + goog.loadModule = function(moduleDef) { + // NOTE: we allow function definitions to be either in the from + // of a string to eval (which keeps the original source intact) or + // in a eval forbidden environment (CSP) we allow a function definition + // which in its body must call {@code goog.module}, and return the exports + // of the module. + var previousState = goog.moduleLoaderState_; + try { + goog.moduleLoaderState_ = { + moduleName: undefined, declareTestMethods: false}; + var exports; + if (goog.isFunction(moduleDef)) { + exports = moduleDef.call(goog.global, {}); + } else if (goog.isString(moduleDef)) { + exports = goog.loadModuleFromSource_.call(goog.global, moduleDef); + } else { + throw Error('Invalid module definition'); + } + + var moduleName = goog.moduleLoaderState_.moduleName; + if (!goog.isString(moduleName) || !moduleName) { + throw Error('Invalid module name \"' + moduleName + '\"'); + } + + // Don't seal legacy namespaces as they may be uses as a parent of + // another namespace + if (goog.moduleLoaderState_.declareLegacyNamespace) { + goog.constructNamespace_(moduleName, exports); + } else if (goog.SEAL_MODULE_EXPORTS && Object.seal) { + Object.seal(exports); + } + + goog.loadedModules_[moduleName] = exports; + if (goog.moduleLoaderState_.declareTestMethods) { + for (var entry in exports) { + if (entry.indexOf('test', 0) === 0 || + entry == 'tearDown' || + entry == 'setUp' || + entry == 'setUpPage' || + entry == 'tearDownPage') { + goog.global[entry] = exports[entry]; + } + } + } + } finally { + goog.moduleLoaderState_ = previousState; + } + }; + + + /** + * @param {string} source + * @return {!Object} + * @private + */ + goog.loadModuleFromSource_ = function(source) { + // NOTE: we avoid declaring parameters or local variables here to avoid + // masking globals or leaking values into the module definition. + 'use strict'; + var exports = {}; + eval(arguments[0]); + return exports; + }; + + + /** + * Writes a new script pointing to {@code src} directly into the DOM. + * + * NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for + * the fallback mechanism. + * + * @param {string} src The script URL. + * @private + */ + goog.writeScriptSrcNode_ = function(src) { + goog.global.document.write( + ' + + \ No newline at end of file diff --git a/lib/test/data/click_tests/overlapping_elements.html b/lib/test/data/click_tests/overlapping_elements.html new file mode 100644 index 0000000..16c4bd9 --- /dev/null +++ b/lib/test/data/click_tests/overlapping_elements.html @@ -0,0 +1,59 @@ + + + + An element that disappears on click + + + +

        Hello

        +
        +
        +

        Log:

        +

        + + + \ No newline at end of file diff --git a/lib/test/data/coordinates_tests/page_with_fixed_element.html b/lib/test/data/coordinates_tests/page_with_fixed_element.html index b815891..6cbb273 100644 --- a/lib/test/data/coordinates_tests/page_with_fixed_element.html +++ b/lib/test/data/coordinates_tests/page_with_fixed_element.html @@ -5,7 +5,7 @@
        fixed red box
        -
        Placeholder
        +
        Placeholder
        Element at the bottom
        Tex after box
        diff --git a/lib/test/data/firefox/jetpack-sample.xpi b/lib/test/data/firefox/jetpack-sample.xpi new file mode 100644 index 0000000000000000000000000000000000000000..84d6493dd4f56572a488dc06cf841d9eb2b92d11 GIT binary patch literal 7289 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 literal 0 HcmV?d00001 diff --git a/lib/test/data/firefox/sample.xpi b/lib/test/data/firefox/sample.xpi new file mode 100644 index 0000000000000000000000000000000000000000..062f9a1722450df325576503b0722dcdca842141 GIT binary patch literal 1551 zcmWIWW@Zs#U|`^2kSY*#Eoi*G+6lz$0pb~u38{RaY;OWhNCP@i2+4%}vecrS#7e!aVz^mY z{FB@7n9XD;(E9$b=uTB`(+%k_mxkRs)FuAk;f|!Q!a1FrD`KzwzvmgABW@*7!E=B1 z_aZ))nr(aul}@L8Ul2uS|q2=Xrb0qT(mM}rFiSK;2Z2kg^wpKS@k-!?5F#A^*!rZ z#V6+q@9Ozp<@P%&eE*X5xeSb@@A+bnNY;7IjpI5~c1CjYyhSQ|?afv%v$!`i(fWb> zq9wVKY})tTn_Bq=d)CVrng>3uWkv*27%-3=9oUa43~vk9FN% zZ@$9{JTBkoF#TjG+r7474y$XJ(_#M^W($|=cc01nGS4J=nzrlTr)mpRxczGmJes08 z*~R|Llt`U7d#{UMteiLFl>U(y8@KfG$({6m?l${@{k_0x z%elg<^Zx&@I``9gS6=1Ax~7mdBHH<8U-z08UYdS(^KDM2wR@Vcg*FvlHo2@eXIfEz zdZA3R{Q<{@CwlI$G)!_Q1_-}DqH;jDXL|e^jTMQ^_xlY#$jzxZQn*|HZo?^+)g|VQ z-rG~xhR5l;8YOTFdtF}SFo$be^CjlAfCrqn1zjsc4>UJ1v8?|2aB=?BN0T#Vw!3<6 zUw-lU?$ldzpDV6^mMI>^yQp01)un`qGg(%7d&VS7=TCE$*H&n}c=qDX*Q=X~B&uyA z_x2?14e0Jld+%~uswa7eb(yr*_XYB+S0zYpW#4;lkMzSlj!)8?mdgM7$&}jYA868F zB$8k2uz5n8Nn3@W-s1W8C*#bX`RwUc)Nh-jpS4}`R^P80yS?^?&u7_M-n-)?|9}4% z(MgZqy;nP@AK=Z%B*%=aRFME@YG8CRENKKWG0GTLNEw3`TM$EWl@Aa@85jf@-a2-J z4272#K+`~_1y<8=l@-WNJd0!+a>+s1T+Hl+Z0;vu2*6wkGZ&oMfChsy8y*K^rg>z8 gw*hkjN)7-Th$Zz0c(byBf}a%#mjc}q!UEy}0BE8O*Z=?k literal 0 HcmV?d00001 diff --git a/lib/test/data/formPage.html b/lib/test/data/formPage.html index e1197db..7bcfea0 100644 --- a/lib/test/data/formPage.html +++ b/lib/test/data/formPage.html @@ -14,6 +14,7 @@
        +
        diff --git a/lib/test/data/frameWithAnimals.html b/lib/test/data/frameWithAnimals.html new file mode 100644 index 0000000..1e0dc87 --- /dev/null +++ b/lib/test/data/frameWithAnimals.html @@ -0,0 +1,11 @@ + + + This page has iframes + + +

        This is the heading

        + +'),"undefined"==typeof s&&(s=t.renderHtml(e)),e.statusbar&&(a=e.statusbar.renderHtml()),'
        '+o+'
        '+s+"
        "+a+"
        "},fullscreen:function(e){var t=this,r=document.documentElement,i,o=t.classPrefix,a;if(e!=t._fullscreen)if(n.on(window,"resize",function(){var e;if(t._fullscreen)if(i)t._timer||(t._timer=setTimeout(function(){var e=n.getWindowSize();t.moveTo(0,0).resizeTo(e.w,e.h),t._timer=0},50));else{e=(new Date).getTime();var r=n.getWindowSize();t.moveTo(0,0).resizeTo(r.w,r.h),(new Date).getTime()-e>50&&(i=!0)}}),a=t.layoutRect(),t._fullscreen=e,e){t._initial={x:a.x,y:a.y,w:a.w,h:a.h},t._borderBox=t.parseBox("0"),t.getEl("head").style.display="none",a.deltaH-=a.headerH+2,n.addClass(r,o+"fullscreen"),n.addClass(document.body,o+"fullscreen"),t.addClass("fullscreen");var s=n.getWindowSize();t.moveTo(0,0).resizeTo(s.w,s.h)}else t._borderBox=t.parseBox(t.settings.border),t.getEl("head").style.display="",a.deltaH+=a.headerH,n.removeClass(r,o+"fullscreen"),n.removeClass(document.body,o+"fullscreen"),t.removeClass("fullscreen"),t.moveTo(t._initial.x,t._initial.y).resizeTo(t._initial.w,t._initial.h);return t.reflow()},postRender:function(){var e=this,t;setTimeout(function(){e.addClass("in")},0),e._super(),e.statusbar&&e.statusbar.postRender(),e.focus(),this.dragHelper=new r(e._id+"-dragh",{start:function(){t={x:e.layoutRect().x,y:e.layoutRect().y}},drag:function(n){e.moveTo(t.x+n.deltaX,t.y+n.deltaY)}}),e.on("submit",function(t){t.isDefaultPrevented()||e.close()})},submit:function(){return this.fire("submit",{data:this.toJSON()})},remove:function(){var e=this,t=e.classPrefix;e.dragHelper.destroy(),e._super(),e.statusbar&&this.statusbar.remove(),e._fullscreen&&(n.removeClass(document.documentElement,t+"fullscreen"),n.removeClass(document.body,t+"fullscreen"))},getContentWindow:function(){var e=this.getEl().getElementsByTagName("iframe")[0];return e?e.contentWindow:null}});return i}),r(nt,[tt],function(e){var t=e.extend({init:function(e){e={border:1,padding:20,layout:"flex",pack:"center",align:"center",containerCls:"panel",autoScroll:!0,buttons:{type:"button",text:"Ok",action:"ok"},items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200}},this._super(e)},Statics:{OK:1,OK_CANCEL:2,YES_NO:3,YES_NO_CANCEL:4,msgBox:function(n){var r,i=n.callback||function(){};switch(n.buttons){case t.OK_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}},{type:"button",text:"Cancel",onClick:function(e){e.control.parents()[1].close(),i(!1)}}];break;case t.YES_NO:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}];break;case t.YES_NO_CANCEL:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close()}}];break;default:r=[{type:"button",text:"Ok",subtype:"primary",onClick:function(e){e.control.parents()[1].close(),i(!0)}}]}return new e({padding:20,x:n.x,y:n.y,minWidth:300,minHeight:100,layout:"flex",pack:"center",align:"center",buttons:r,title:n.title,role:"alertdialog",items:{type:"label",multiline:!0,maxWidth:500,maxHeight:200,text:n.text},onPostRender:function(){this.aria("describedby",this.items()[0]._id)},onClose:n.onClose,onCancel:function(){i(!1)}}).renderTo(document.body).reflow()},alert:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,t.msgBox(e)},confirm:function(e,n){return"string"==typeof e&&(e={text:e}),e.callback=n,e.buttons=t.OK_CANCEL,t.msgBox(e)}}});return t}),r(rt,[tt,nt],function(e,t){return function(n){function r(){return o.length?o[o.length-1]:void 0}var i=this,o=[];i.windows=o,i.open=function(t,r){var i;return n.editorManager.activeEditor=n,t.title=t.title||" ",t.url=t.url||t.file,t.url&&(t.width=parseInt(t.width||320,10),t.height=parseInt(t.height||240,10)),t.body&&(t.items={defaults:t.defaults,type:t.bodyType||"form",items:t.body}),t.url||t.buttons||(t.buttons=[{text:"Ok",subtype:"primary",onclick:function(){i.find("form")[0].submit()}},{text:"Cancel",onclick:function(){i.close()}}]),i=new e(t),o.push(i),i.on("close",function(){for(var e=o.length;e--;)o[e]===i&&o.splice(e,1);n.focus()}),t.data&&i.on("postRender",function(){this.find("*").each(function(e){var n=e.name();n in t.data&&e.value(t.data[n])})}),i.features=t||{},i.params=r||{},n.nodeChanged(),i.renderTo().reflow()},i.alert=function(e,r,i){t.alert(e,function(){r?r.call(i||this):n.focus()})},i.confirm=function(e,n,r){t.confirm(e,function(e){n.call(r||this,e)})},i.close=function(){r()&&r().close()},i.getParams=function(){return r()?r().params:null},i.setParams=function(e){r()&&(r().params=e)},i.getWindows=function(){return o}}}),r(it,[R,B,x,m,g,p],function(e,t,n,r,i,o){return function(a){function s(e,t){try{a.getDoc().execCommand(e,!1,t)}catch(n){}}function l(){var e=a.getDoc().documentMode;return e?e:6}function c(e){return e.isDefaultPrevented()}function u(){function t(e){var t=new i(function(){});o.each(a.getBody().getElementsByTagName("*"),function(e){"SPAN"==e.tagName&&e.setAttribute("mce-data-marked",1),!e.hasAttribute("data-mce-style")&&e.hasAttribute("style")&&a.dom.setAttrib(e,"style",e.getAttribute("style"))}),t.observe(a.getDoc(),{childList:!0,attributes:!0,subtree:!0,attributeFilter:["style"]}),a.getDoc().execCommand(e?"ForwardDelete":"Delete",!1,null);var n=a.selection.getRng(),r=n.startContainer.parentNode;o.each(t.takeRecords(),function(e){if(q.isChildOf(e.target,a.getBody())){if("style"==e.attributeName){var t=e.target.getAttribute("data-mce-style");t?e.target.setAttribute("style",t):e.target.removeAttribute("style")}o.each(e.addedNodes,function(e){if("SPAN"==e.nodeName&&!e.getAttribute("mce-data-marked")){var t,i;e==r&&(t=n.startOffset,i=e.firstChild),q.remove(e,!0),i&&(n.setStart(i,t),n.setEnd(i,t),a.selection.setRng(n))}})}}),t.disconnect(),o.each(a.dom.select("span[mce-data-marked]"),function(e){e.removeAttribute("mce-data-marked")})}var n=a.getDoc(),r="data:text/mce-internal,",i=window.MutationObserver,s,l;i||(s=!0,i=function(){function e(e){var t=e.relatedNode||e.target;n.push({target:t,addedNodes:[t]})}function t(e){var t=e.relatedNode||e.target;n.push({target:t,attributeName:e.attrName})}var n=[],r;this.observe=function(n){r=n,r.addEventListener("DOMSubtreeModified",e,!1),r.addEventListener("DOMNodeInsertedIntoDocument",e,!1),r.addEventListener("DOMNodeInserted",e,!1),r.addEventListener("DOMAttrModified",t,!1)},this.disconnect=function(){r.removeEventListener("DOMSubtreeModified",e,!1),r.removeEventListener("DOMNodeInsertedIntoDocument",e,!1),r.removeEventListener("DOMNodeInserted",e,!1),r.removeEventListener("DOMAttrModified",t,!1)},this.takeRecords=function(){return n}}),a.on("keydown",function(n){var r=n.keyCode==U,i=e.metaKeyPressed(n);if(!c(n)&&(r||n.keyCode==V)){var o=a.selection.getRng(),s=o.startContainer,l=o.startOffset;if(!i&&o.collapsed&&3==s.nodeType&&(r?l0))return;n.preventDefault(),i&&a.selection.getSel().modify("extend",r?"forward":"backward","word"),t(r)}}),a.on("keypress",function(n){c(n)||$.isCollapsed()||!n.charCode||e.metaKeyPressed(n)||(n.preventDefault(),t(!0),a.selection.setContent(String.fromCharCode(n.charCode)))}),a.addCommand("Delete",function(){t()}),a.addCommand("ForwardDelete",function(){t(!0)}),s||(a.on("dragstart",function(e){var t;a.selection.isCollapsed()&&"IMG"==e.target.tagName&&$.select(e.target),l=$.getRng(),t=a.selection.getContent(),t.length>0&&e.dataTransfer.setData("URL","data:text/mce-internal,"+escape(t))}),a.on("drop",function(e){if(!c(e)){var i=e.dataTransfer.getData("URL");if(!i||-1==i.indexOf(r)||!n.caretRangeFromPoint)return;i=unescape(i.substr(r.length)),n.caretRangeFromPoint&&(e.preventDefault(),window.setTimeout(function(){var r=n.caretRangeFromPoint(e.x,e.y);l&&($.setRng(l),l=null),t(),$.setRng(r),a.insertContent(i)},0))}}),a.on("cut",function(e){!c(e)&&e.clipboardData&&(e.preventDefault(),e.clipboardData.clearData(),e.clipboardData.setData("text/html",a.selection.getContent()),e.clipboardData.setData("text/plain",a.selection.getContent({format:"text"})),t(!0))}))}function d(){function e(e){var t=q.create("body"),n=e.cloneContents();return t.appendChild(n),$.serializer.serialize(t,{format:"html"})}function n(n){if(!n.setStart){if(n.item)return!1;var r=n.duplicate();return r.moveToElementText(a.getBody()),t.compareRanges(n,r)}var i=e(n),o=q.createRng();o.selectNode(a.getBody());var s=e(o);return i===s}a.on("keydown",function(e){var t=e.keyCode,r,i;if(!c(e)&&(t==U||t==V)){if(r=a.selection.isCollapsed(),i=a.getBody(),r&&!q.isEmpty(i))return;if(!r&&!n(a.selection.getRng()))return;e.preventDefault(),a.setContent(""),i.firstChild&&q.isBlock(i.firstChild)?a.selection.setCursorLocation(i.firstChild,0):a.selection.setCursorLocation(i,0),a.nodeChanged()}})}function f(){a.on("keydown",function(t){!c(t)&&65==t.keyCode&&e.metaKeyPressed(t)&&(t.preventDefault(),a.execCommand("SelectAll"))})}function p(){a.settings.content_editable||(q.bind(a.getDoc(),"focusin",function(){$.setRng($.getRng())}),q.bind(a.getDoc(),"mousedown",function(e){e.target==a.getDoc().documentElement&&(a.getBody().focus(),$.setRng($.getRng()))}))}function h(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===V&&$.isCollapsed()&&0===$.getRng(!0).startOffset){var t=$.getNode(),n=t.previousSibling;if("HR"==t.nodeName)return q.remove(t),void e.preventDefault();n&&n.nodeName&&"hr"===n.nodeName.toLowerCase()&&(q.remove(n),e.preventDefault())}})}function m(){window.Range.prototype.getClientRects||a.on("mousedown",function(e){if(!c(e)&&"HTML"===e.target.nodeName){var t=a.getBody();t.blur(),setTimeout(function(){t.focus()},0)}})}function g(){a.on("click",function(e){e=e.target,/^(IMG|HR)$/.test(e.nodeName)&&$.getSel().setBaseAndExtent(e,0,e,1),"A"==e.nodeName&&q.hasClass(e,"mce-item-anchor")&&$.select(e),a.nodeChanged()})}function v(){function e(){var e=q.getAttribs($.getStart().cloneNode(!1));return function(){var t=$.getStart();t!==a.getBody()&&(q.setAttrib(t,"style",null),W(e,function(e){t.setAttributeNode(e.cloneNode(!0))}))}}function t(){return!$.isCollapsed()&&q.getParent($.getStart(),q.isBlock)!=q.getParent($.getEnd(),q.isBlock)}a.on("keypress",function(n){var r;return c(n)||8!=n.keyCode&&46!=n.keyCode||!t()?void 0:(r=e(),a.getDoc().execCommand("delete",!1,null),r(),n.preventDefault(),!1)}),q.bind(a.getDoc(),"cut",function(n){var r;!c(n)&&t()&&(r=e(),setTimeout(function(){r()},0))})}function y(){var e,n;a.on("selectionchange",function(){n&&(clearTimeout(n),n=0),n=window.setTimeout(function(){if(!a.removed){var n=$.getRng();e&&t.compareRanges(n,e)||(a.nodeChanged(),e=n)}},50)})}function b(){document.body.setAttribute("role","application")}function C(){a.on("keydown",function(e){if(!c(e)&&e.keyCode===V&&$.isCollapsed()&&0===$.getRng(!0).startOffset){var t=$.getNode().previousSibling;if(t&&t.nodeName&&"table"===t.nodeName.toLowerCase())return e.preventDefault(),!1}})}function x(){l()>7||(s("RespectVisibilityInDesign",!0),a.contentStyles.push(".mceHideBrInPre pre br {display: none}"),q.addClass(a.getBody(),"mceHideBrInPre"),K.addNodeFilter("pre",function(e){for(var t=e.length,r,i,o,a;t--;)for(r=e[t].getAll("br"),i=r.length;i--;)o=r[i],a=o.prev,a&&3===a.type&&"\n"!=a.value.charAt(a.value-1)?a.value+="\n":o.parent.insert(new n("#text",3),o,!0).value="\n"}),G.addNodeFilter("pre",function(e){for(var t=e.length,n,r,i,o;t--;)for(n=e[t].getAll("br"),r=n.length;r--;)i=n[r],o=i.prev,o&&3==o.type&&(o.value=o.value.replace(/\r?\n$/,""))}))}function w(){q.bind(a.getBody(),"mouseup",function(){var e,t=$.getNode();"IMG"==t.nodeName&&((e=q.getStyle(t,"width"))&&(q.setAttrib(t,"width",e.replace(/[^0-9%]+/g,"")),q.setStyle(t,"width","")),(e=q.getStyle(t,"height"))&&(q.setAttrib(t,"height",e.replace(/[^0-9%]+/g,"")),q.setStyle(t,"height","")))})}function _(){a.on("keydown",function(t){var n,r,i,o,s;if(!c(t)&&t.keyCode==e.BACKSPACE&&(n=$.getRng(),r=n.startContainer,i=n.startOffset,o=q.getRoot(),s=r,n.collapsed&&0===i)){for(;s&&s.parentNode&&s.parentNode.firstChild==s&&s.parentNode!=o;)s=s.parentNode;"BLOCKQUOTE"===s.tagName&&(a.formatter.toggle("blockquote",null,s),n=q.createRng(),n.setStart(r,0),n.setEnd(r,0),$.setRng(n))}})}function N(){function e(){a._refreshContentEditable(),s("StyleWithCSS",!1),s("enableInlineTableEditing",!1),j.object_resizing||s("enableObjectResizing",!1)}j.readonly||a.on("BeforeExecCommand MouseDown",e)}function E(){function e(){W(q.select("a"),function(e){var t=e.parentNode,n=q.getRoot();if(t.lastChild===e){for(;t&&!q.isBlock(t);){if(t.parentNode.lastChild!==t||t===n)return;t=t.parentNode}q.add(t,"br",{"data-mce-bogus":1})}})}a.on("SetContent ExecCommand",function(t){("setcontent"==t.type||"mceInsertLink"===t.command)&&e()})}function S(){j.forced_root_block&&a.on("init",function(){s("DefaultParagraphSeparator",j.forced_root_block)})}function k(){a.on("Undo Redo SetContent",function(e){e.initial||a.execCommand("mceRepaint")})}function T(){a.on("keydown",function(e){var t;c(e)||e.keyCode!=V||(t=a.getDoc().selection.createRange(),t&&t.item&&(e.preventDefault(),a.undoManager.beforeChange(),q.remove(t.item(0)),a.undoManager.add()))})}function R(){var e;l()>=10&&(e="",W("p div h1 h2 h3 h4 h5 h6".split(" "),function(t,n){e+=(n>0?",":"")+t+":empty"}),a.contentStyles.push(e+"{padding-right: 1px !important}"))}function A(){l()<9&&(K.addNodeFilter("noscript",function(e){for(var t=e.length,n,r;t--;)n=e[t],r=n.firstChild,r&&n.attr("data-mce-innertext",r.value)}),G.addNodeFilter("noscript",function(e){for(var t=e.length,i,o,a;t--;)i=e[t],o=e[t].firstChild,o?o.value=r.decode(o.value):(a=i.attributes.map["data-mce-innertext"],a&&(i.attr("data-mce-innertext",null),o=new n("#text",3),o.value=a,o.raw=!0,i.append(o)))}))}function B(){function e(e,t){var n=i.createTextRange();try{n.moveToPoint(e,t)}catch(r){n=null}return n}function t(t){var r;t.button?(r=e(t.x,t.y),r&&(r.compareEndPoints("StartToStart",a)>0?r.setEndPoint("StartToStart",a):r.setEndPoint("EndToEnd",a),r.select())):n()}function n(){var e=r.selection.createRange();a&&!e.item&&0===e.compareEndPoints("StartToEnd",e)&&a.select(),q.unbind(r,"mouseup",n),q.unbind(r,"mousemove",t),a=o=0}var r=q.doc,i=r.body,o,a,s;r.documentElement.unselectable=!0,q.bind(r,"mousedown contextmenu",function(i){if("HTML"===i.target.nodeName){if(o&&n(),s=r.documentElement,s.scrollHeight>s.clientHeight)return;o=1,a=e(i.x,i.y),a&&(q.bind(r,"mouseup",n),q.bind(r,"mousemove",t),q.getRoot().focus(),a.select())}})}function D(){a.on("keyup focusin mouseup",function(t){65==t.keyCode&&e.metaKeyPressed(t)||$.normalize()},!0)}function L(){a.contentStyles.push("img:-moz-broken {-moz-force-broken-image-icon:1;min-width:24px;min-height:24px}")}function M(){a.inline||a.on("keydown",function(){document.activeElement==document.body&&a.getWin().focus()})}function H(){a.inline||(a.contentStyles.push("body {min-height: 150px}"),a.on("click",function(e){"HTML"==e.target.nodeName&&(a.getBody().focus(),a.selection.normalize(),a.nodeChanged())}))}function P(){i.mac&&a.on("keydown",function(t){!e.metaKeyPressed(t)||37!=t.keyCode&&39!=t.keyCode||(t.preventDefault(),a.selection.getSel().modify("move",37==t.keyCode?"backward":"forward","word"))})}function O(){s("AutoUrlDetect",!1)}function I(){a.inline||a.on("focus blur beforegetcontent",function(){var e=a.dom.create("br");a.getBody().appendChild(e),e.parentNode.removeChild(e)},!0)}function F(){a.on("click",function(e){var t=e.target;do if("A"===t.tagName)return void e.preventDefault();while(t=t.parentNode)}),a.contentStyles.push(".mce-content-body {-webkit-touch-callout: none}")}function z(){a.on("init",function(){a.dom.bind(a.getBody(),"submit",function(e){e.preventDefault()})})}var W=o.each,V=e.BACKSPACE,U=e.DELETE,q=a.dom,$=a.selection,j=a.settings,K=a.parser,G=a.serializer,Y=i.gecko,X=i.ie,J=i.webkit;C(),_(),d(),D(),J&&(u(),p(),g(),S(),z(),i.iOS?(y(),M(),H(),F()):f()),X&&i.ie<11&&(h(),b(),x(),w(),T(),R(),A(),B()),i.ie>=11&&(H(),I()),i.ie&&(f(),O()),Y&&(h(),m(),v(),N(),E(),k(),L(),P())}}),r(ot,[W],function(e){function t(t){return t._eventDispatcher||(t._eventDispatcher=new e({scope:t,toggleEvent:function(n,r){e.isNative(n)&&t.toggleNativeEvent&&t.toggleNativeEvent(n,r)}})),t._eventDispatcher}return{fire:function(e,n,r){var i=this;if(i.removed&&"remove"!==e)return n;if(n=t(i).fire(e,n,r),r!==!1&&i.parent)for(var o=i.parent();o&&!n.isPropagationStopped();)o.fire(e,n,!1),o=o.parent();return n},on:function(e,n,r){return t(this).on(e,n,r)},off:function(e,n){return t(this).off(e,n)},hasEventListeners:function(e){return t(this).has(e)}}}),r(at,[ot,y,p],function(e,t,n){function r(e,t){return"selectionchange"==t?e.getDoc():!e.inline&&/^mouse|click|contextmenu|drop/.test(t)?e.getDoc():e.getBody()}function i(e,t){var n=e.settings.event_root,i=e.editorManager,a=i.eventRootElm||r(e,t);if(n){if(i.rootEvents||(i.rootEvents={},i.on("RemoveEditor",function(){i.activeEditor||(o.unbind(a),delete i.rootEvents)})),i.rootEvents[t])return;a==e.getBody()&&(a=o.select(n)[0],i.eventRootElm=a),i.rootEvents[t]=!0,o.bind(a,t,function(e){for(var n=e.target,r=i.editors,a=r.length;a--;){var s=r[a].getBody();(s===n||o.isChildOf(n,s))&&(r[a].hidden||r[a].fire(t,e))}})}else e.dom.bind(a,t,function(n){e.hidden||e.fire(t,n)})}var o=t.DOM,a={bindPendingEventDelegates:function(){var e=this;n.each(e._pendingNativeEvents,function(t){i(e,t)})},toggleNativeEvent:function(e,t){var n=this;n.settings.readonly||"focus"!=e&&"blur"!=e&&(t?n.initialized?i(n,e):n._pendingNativeEvents?n._pendingNativeEvents.push(e):n._pendingNativeEvents=[e]:n.initialized&&n.dom.unbind(r(n,e),e))}};return a=n.extend({},e,a)}),r(st,[p,g],function(e,t){var n=e.each,r=e.explode,i={f9:120,f10:121,f11:122};return function(o){var a=this,s={};o.on("keyup keypress keydown",function(e){(e.altKey||e.ctrlKey||e.metaKey)&&n(s,function(n){var r=t.mac?e.metaKey:e.ctrlKey;if(n.ctrl==r&&n.alt==e.altKey&&n.shift==e.shiftKey)return e.keyCode==n.keyCode||e.charCode&&e.charCode==n.charCode?(e.preventDefault(),"keydown"==e.type&&n.func.call(n.scope),!0):void 0})}),a.add=function(t,a,l,c){var u;return u=l,"string"==typeof l?l=function(){o.execCommand(u,!1,null)}:e.isArray(u)&&(l=function(){o.execCommand(u[0],u[1],u[2])}),n(r(t.toLowerCase()),function(e){var t={func:l,scope:c||o,desc:o.translate(a),alt:!1,ctrl:!1,shift:!1};n(r(e,"+"),function(e){switch(e){case"alt":case"ctrl":case"shift":t[e]=!0;break;default:t.charCode=e.charCodeAt(0),t.keyCode=i[e]||e.toUpperCase().charCodeAt(0)}}),s[(t.ctrl?"ctrl":"")+","+(t.alt?"alt":"")+","+(t.shift?"shift":"")+","+t.keyCode]=t}),!0}}}),r(lt,[y,C,x,k,S,D,M,H,P,O,I,F,b,l,rt,w,N,it,g,p,at,st],function(e,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w){function _(e,t,r){var i=this,o,a;o=i.documentBaseUrl=r.documentBaseURL,a=r.baseURI,i.settings=t=k({id:e,theme:"modern",delta_width:0,delta_height:0,popup_css:"",plugins:"",document_base_url:o,add_form_submit_trigger:!0,submit_patch:!0,add_unload_trigger:!0,convert_urls:!0,relative_urls:!0,remove_script_host:!0,object_resizing:!0,doctype:"",visual:!0,font_size_style_values:"xx-small,x-small,small,medium,large,x-large,xx-large",font_size_legacy_values:"xx-small,small,medium,large,x-large,xx-large,300%",forced_root_block:"p",hidden_input:!0,padd_empty_editor:!0,render_ui:!0,indentation:"30px",inline_styles:!0,convert_fonts_to_spans:!0,indent:"simple",indent_before:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",indent_after:"p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist",validate:!0,entity_encoding:"named",url_converter:i.convertURL,url_converter_scope:i,ie7_compat:!0},t),n.language=t.language||"en",n.languageLoad=t.language_load,n.baseURL=r.baseURL,i.id=t.id=e,i.isNotDirty=!0,i.plugins={},i.documentBaseURI=new f(t.document_base_url||o,{base_uri:a}),i.baseURI=a,i.contentCSS=[],i.contentStyles=[],i.shortcuts=new w(i),i.execCommands={},i.queryStateCommands={},i.queryValueCommands={},i.loadedCSS={},i.suffix=r.suffix,i.editorManager=r,i.inline=t.inline,r.fire("SetupEditor",i),i.execCallback("setup",i)}var N=e.DOM,E=n.ThemeManager,S=n.PluginManager,k=C.extend,T=C.each,R=C.explode,A=C.inArray,B=C.trim,D=C.resolve,L=h.Event,M=b.gecko,H=b.ie;return _.prototype={render:function(){function e(){N.unbind(window,"ready",e),n.render()}function t(){var e=p.ScriptLoader;if(r.language&&"en"!=r.language&&!r.language_url&&(r.language_url=n.editorManager.baseURL+"/langs/"+r.language+".js"),r.language_url&&e.add(r.language_url),r.theme&&"function"!=typeof r.theme&&"-"!=r.theme.charAt(0)&&!E.urls[r.theme]){var t=r.theme_url;t=t?n.documentBaseURI.toAbsolute(t):"themes/"+r.theme+"/theme"+o+".js",E.load(r.theme,t)}C.isArray(r.plugins)&&(r.plugins=r.plugins.join(" ")),T(r.external_plugins,function(e,t){S.load(t,e),r.plugins+=" "+t}),T(r.plugins.split(/[ ,]/),function(e){if(e=B(e),e&&!S.urls[e])if("-"==e.charAt(0)){e=e.substr(1,e.length);var t=S.dependencies(e);T(t,function(e){var t={prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"};e=S.createUrl(t,e),S.load(e.resource,e)})}else S.load(e,{prefix:"plugins/",resource:e,suffix:"/plugin"+o+".js"})}),e.loadQueue(function(){n.removed||n.init()})}var n=this,r=n.settings,i=n.id,o=n.suffix;if(!L.domLoaded)return void N.bind(window,"ready",e);if(n.getElement()&&b.contentEditable){r.inline?n.inline=!0:(n.orgVisibility=n.getElement().style.visibility,n.getElement().style.visibility="hidden");var a=n.getElement().form||N.getParent(i,"form");a&&(n.formElement=a,r.hidden_input&&!/TEXTAREA|INPUT/i.test(n.getElement().nodeName)&&(N.insertAfter(N.create("input",{type:"hidden",name:i}),i),n.hasHiddenInput=!0),n.formEventDelegate=function(e){n.fire(e.type,e)},N.bind(a,"submit reset",n.formEventDelegate),n.on("reset",function(){n.setContent(n.startContent,{format:"raw"})}),!r.submit_patch||a.submit.nodeType||a.submit.length||a._mceOldSubmit||(a._mceOldSubmit=a.submit,a.submit=function(){return n.editorManager.triggerSave(),n.isNotDirty=!0,a._mceOldSubmit(a)})),n.windowManager=new m(n),"xml"==r.encoding&&n.on("GetContent",function(e){e.save&&(e.content=N.encode(e.content))}),r.add_form_submit_trigger&&n.on("submit",function(){n.initialized&&n.save()}),r.add_unload_trigger&&(n._beforeUnload=function(){!n.initialized||n.destroyed||n.isHidden()||n.save({format:"raw",no_events:!0,set_dirty:!1})},n.editorManager.on("BeforeUnload",n._beforeUnload)),t()}},init:function(){function e(n){var r=S.get(n),i,o;i=S.urls[n]||t.documentBaseUrl.replace(/\/$/,""),n=B(n),r&&-1===A(m,n)&&(T(S.dependencies(n),function(t){e(t)}),o=new r(t,i),t.plugins[n]=o,o.init&&(o.init(t,i),m.push(n)))}var t=this,n=t.settings,r=t.getElement(),i,o,a,s,l,c,u,d,f,p,h,m=[];if(t.rtl=this.editorManager.i18n.rtl,t.editorManager.add(t),n.aria_label=n.aria_label||N.getAttrib(r,"aria-label",t.getLang("aria.rich_text_area")),n.theme&&("function"!=typeof n.theme?(n.theme=n.theme.replace(/-/,""),c=E.get(n.theme),t.theme=new c(t,E.urls[n.theme]),t.theme.init&&t.theme.init(t,E.urls[n.theme]||t.documentBaseUrl.replace(/\/$/,""))):t.theme=n.theme),T(n.plugins.replace(/\-/g,"").split(/[ ,]/),e),n.render_ui&&t.theme&&(t.orgDisplay=r.style.display,"function"!=typeof n.theme?(i=n.width||r.style.width||r.offsetWidth,o=n.height||r.style.height||r.offsetHeight,a=n.min_height||100,p=/^[0-9\.]+(|px)$/i,p.test(""+i)&&(i=Math.max(parseInt(i,10),100)),p.test(""+o)&&(o=Math.max(parseInt(o,10),a)),l=t.theme.renderUI({targetNode:r,width:i,height:o,deltaWidth:n.delta_width,deltaHeight:n.delta_height}),n.content_editable||(N.setStyles(l.sizeContainer||l.editorContainer,{wi2dth:i,h2eight:o}),o=(l.iframeHeight||o)+("number"==typeof o?l.deltaHeight||0:""),a>o&&(o=a))):(l=n.theme(t,r),l.editorContainer.nodeType&&(l.editorContainer=l.editorContainer.id=l.editorContainer.id||t.id+"_parent"),l.iframeContainer.nodeType&&(l.iframeContainer=l.iframeContainer.id=l.iframeContainer.id||t.id+"_iframecontainer"),o=l.iframeHeight||r.offsetHeight),t.editorContainer=l.editorContainer),n.content_css&&T(R(n.content_css),function(e){t.contentCSS.push(t.documentBaseURI.toAbsolute(e))}),n.content_style&&t.contentStyles.push(n.content_style),n.content_editable)return r=s=l=null,t.initContentBody();for(t.iframeHTML=n.doctype+"",n.document_base_url!=t.documentBaseUrl&&(t.iframeHTML+=''),!b.caretAfter&&n.ie7_compat&&(t.iframeHTML+=''),t.iframeHTML+='',h=0;h',t.loadedCSS[g]=!0}d=n.body_id||"tinymce",-1!=d.indexOf("=")&&(d=t.getParam("body_id","","hash"),d=d[t.id]||d),f=n.body_class||"",-1!=f.indexOf("=")&&(f=t.getParam("body_class","","hash"),f=f[t.id]||""),t.iframeHTML+='
        ";var v='javascript:(function(){document.open();document.domain="'+document.domain+'";var ed = window.parent.tinymce.get("'+t.id+'");document.write(ed.iframeHTML);document.close();ed.initContentBody(true);})()';if(document.domain!=location.hostname&&(u=v),s=N.add(l.iframeContainer,"iframe",{id:t.id+"_ifr",src:u||'javascript:""',frameBorder:"0",allowTransparency:"true",title:t.editorManager.translate("Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"),style:{width:"100%",height:o,display:"block"}}),H)try{t.getDoc()}catch(y){s.src=u=v}t.contentAreaContainer=l.iframeContainer,l.editorContainer&&(N.get(l.editorContainer).style.display=t.orgDisplay),N.get(t.id).style.display="none",N.setAttrib(t.id,"aria-hidden",!0),u||t.initContentBody(),r=s=l=null},initContentBody:function(t){var n=this,o=n.settings,f=N.get(n.id),p=n.getDoc(),h,m;o.inline||(n.getElement().style.visibility=n.orgVisibility),t||o.content_editable||(p.open(),p.write(n.iframeHTML),p.close()),o.content_editable&&(n.on("remove",function(){var e=this.getBody();N.removeClass(e,"mce-content-body"),N.removeClass(e,"mce-edit-focus"),N.setAttrib(e,"contentEditable",null)}),N.addClass(f,"mce-content-body"),n.contentDocument=p=o.content_document||document,n.contentWindow=o.content_window||window,n.bodyElement=f,o.content_document=o.content_window=null,o.root_name=f.nodeName.toLowerCase()),h=n.getBody(),h.disabled=!0,o.readonly||(n.inline&&"static"==N.getStyle(h,"position",!0)&&(h.style.position="relative"),h.contentEditable=n.getParam("content_editable_state",!0)),h.disabled=!1,n.schema=new g(o),n.dom=new e(p,{keep_values:!0,url_converter:n.convertURL,url_converter_scope:n,hex_colors:o.force_hex_style_colors,class_filter:o.class_filter,update_styles:!0,root_element:o.content_editable?n.id:null,collect:o.content_editable,schema:n.schema,onSetAttrib:function(e){n.fire("SetAttrib",e) +}}),n.parser=new v(o,n.schema),n.parser.addAttributeFilter("src,href,style,tabindex",function(e,t){for(var r=e.length,i,o=n.dom,a,s;r--;)i=e[r],a=i.attr(t),s="data-mce-"+t,i.attributes.map[s]||("style"===t?i.attr(s,o.serializeStyle(o.parseStyle(a),i.name)):"tabindex"===t?(i.attr(s,a),i.attr(t,null)):i.attr(s,n.convertURL(a,t,i.name)))}),n.parser.addNodeFilter("script",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr("type","mce-"+(n.attr("type")||"text/javascript"))}),n.parser.addNodeFilter("#cdata",function(e){for(var t=e.length,n;t--;)n=e[t],n.type=8,n.name="#comment",n.value="[CDATA["+n.value+"]]"}),n.parser.addNodeFilter("p,h1,h2,h3,h4,h5,h6,div",function(e){for(var t=e.length,i,o=n.schema.getNonEmptyElements();t--;)i=e[t],i.isEmpty(o)&&(i.empty().append(new r("br",1)).shortEnded=!0)}),n.serializer=new i(o,n),n.selection=new a(n.dom,n.getWin(),n.serializer,n),n.formatter=new s(n),n.undoManager=new l(n),n.forceBlocks=new u(n),n.enterKey=new c(n),n.editorCommands=new d(n),n.fire("PreInit"),o.browser_spellcheck||o.gecko_spellcheck||(p.body.spellcheck=!1,N.setAttrib(h,"spellcheck","false")),n.fire("PostRender"),n.quirks=y(n),o.directionality&&(h.dir=o.directionality),o.nowrap&&(h.style.whiteSpace="nowrap"),o.protect&&n.on("BeforeSetContent",function(e){T(o.protect,function(t){e.content=e.content.replace(t,function(e){return""})})}),n.on("SetContent",function(){n.addVisual(n.getBody())}),o.padd_empty_editor&&n.on("PostProcess",function(e){e.content=e.content.replace(/^(]*>( | |\s|\u00a0|)<\/p>[\r\n]*|
        [\r\n]*)$/,"")}),n.load({initial:!0,format:"html"}),n.startContent=n.getContent({format:"raw"}),n.initialized=!0,n.bindPendingEventDelegates(),n.fire("init"),n.focus(!0),n.nodeChanged({initial:!0}),n.execCallback("init_instance_callback",n),n.contentStyles.length>0&&(m="",T(n.contentStyles,function(e){m+=e+"\r\n"}),n.dom.addStyle(m)),T(n.contentCSS,function(e){n.loadedCSS[e]||(n.dom.loadCSS(e),n.loadedCSS[e]=!0)}),o.auto_focus&&setTimeout(function(){var e=n.editorManager.get(o.auto_focus);e.selection.select(e.getBody(),1),e.selection.collapse(1),e.getBody().focus(),e.getWin().focus()},100),f=p=h=null},focus:function(e){var t,n=this,r=n.selection,i=n.settings.content_editable,o,a,s=n.getDoc(),l;if(!e){if(o=r.getRng(),o.item&&(a=o.item(0)),n._refreshContentEditable(),i||(b.opera||n.getBody().focus(),n.getWin().focus()),M||i){if(l=n.getBody(),l.setActive)try{l.setActive()}catch(c){l.focus()}else l.focus();i&&r.normalize()}a&&a.ownerDocument==s&&(o=s.body.createControlRange(),o.addElement(a),o.select())}n.editorManager.activeEditor!=n&&((t=n.editorManager.activeEditor)&&t.fire("deactivate",{relatedTarget:n}),n.fire("activate",{relatedTarget:t})),n.editorManager.activeEditor=n},execCallback:function(e){var t=this,n=t.settings[e],r;if(n)return t.callbackLookup&&(r=t.callbackLookup[e])&&(n=r.func,r=r.scope),"string"==typeof n&&(r=n.replace(/\.\w+$/,""),r=r?D(r):0,n=D(n),t.callbackLookup=t.callbackLookup||{},t.callbackLookup[e]={func:n,scope:r}),n.apply(r||t,Array.prototype.slice.call(arguments,1))},translate:function(e){var t=this.settings.language||"en",n=this.editorManager.i18n;return e?n.data[t+"."+e]||e.replace(/\{\#([^\}]+)\}/g,function(e,r){return n.data[t+"."+r]||"{#"+r+"}"}):""},getLang:function(e,n){return this.editorManager.i18n.data[(this.settings.language||"en")+"."+e]||(n!==t?n:"{#"+e+"}")},getParam:function(e,t,n){var r=e in this.settings?this.settings[e]:t,i;return"hash"===n?(i={},"string"==typeof r?T(r.split(r.indexOf("=")>0?/[;,](?![^=;,]*(?:[;,]|$))/:","),function(e){e=e.split("="),i[B(e[0])]=B(e.length>1?e[1]:e)}):i=r,i):r},nodeChanged:function(){var e=this,t=e.selection,n,r,i;!e.initialized||e.settings.disable_nodechange||e.settings.readonly||(i=e.getBody(),n=t.getStart()||i,n=H&&n.ownerDocument!=e.getDoc()?e.getBody():n,"IMG"==n.nodeName&&t.isCollapsed()&&(n=n.parentNode),r=[],e.dom.getParent(n,function(e){return e===i?!0:void r.push(e)}),e.fire("NodeChange",{element:n,parents:r}))},addButton:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),t.text||t.icon||(t.icon=e),n.buttons=n.buttons||{},t.tooltip=t.tooltip||t.title,n.buttons[e]=t},addMenuItem:function(e,t){var n=this;t.cmd&&(t.onclick=function(){n.execCommand(t.cmd)}),n.menuItems=n.menuItems||{},n.menuItems[e]=t},addCommand:function(e,t,n){this.execCommands[e]={func:t,scope:n||this}},addQueryStateHandler:function(e,t,n){this.queryStateCommands[e]={func:t,scope:n||this}},addQueryValueHandler:function(e,t,n){this.queryValueCommands[e]={func:t,scope:n||this}},addShortcut:function(e,t,n,r){this.shortcuts.add(e,t,n,r)},execCommand:function(e,t,n,r){var i=this,o=0,a;return/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint)$/.test(e)||r&&r.skip_focus||i.focus(),r=k({},r),r=i.fire("BeforeExecCommand",{command:e,ui:t,value:n}),r.isDefaultPrevented()?!1:(a=i.execCommands[e])&&a.func.call(a.scope,t,n)!==!0?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(T(i.plugins,function(r){return r.execCommand&&r.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),o=!0,!1):void 0}),o?o:i.theme&&i.theme.execCommand&&i.theme.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):i.editorCommands.execCommand(e,t,n)?(i.fire("ExecCommand",{command:e,ui:t,value:n}),!0):(i.getDoc().execCommand(e,t,n),void i.fire("ExecCommand",{command:e,ui:t,value:n})))},queryCommandState:function(e){var t=this,n,r;if(!t._isHidden()){if((n=t.queryStateCommands[e])&&(r=n.func.call(n.scope),r!==!0))return r;if(r=t.editorCommands.queryCommandState(e),-1!==r)return r;try{return t.getDoc().queryCommandState(e)}catch(i){}}},queryCommandValue:function(e){var n=this,r,i;if(!n._isHidden()){if((r=n.queryValueCommands[e])&&(i=r.func.call(r.scope),i!==!0))return i;if(i=n.editorCommands.queryCommandValue(e),i!==t)return i;try{return n.getDoc().queryCommandValue(e)}catch(o){}}},show:function(){var e=this;e.hidden&&(e.hidden=!1,e.inline?e.getBody().contentEditable=!0:(N.show(e.getContainer()),N.hide(e.id)),e.load(),e.fire("show"))},hide:function(){var e=this,t=e.getDoc();e.hidden||(e.hidden=!0,H&&t&&!e.inline&&t.execCommand("SelectAll"),e.save(),e.inline?(e.getBody().contentEditable=!1,e==e.editorManager.focusedEditor&&(e.editorManager.focusedEditor=null)):(N.hide(e.getContainer()),N.setStyle(e.id,"display",e.orgDisplay)),e.fire("hide"))},isHidden:function(){return!!this.hidden},setProgressState:function(e,t){this.fire("ProgressState",{state:e,time:t})},load:function(e){var n=this,r=n.getElement(),i;return r?(e=e||{},e.load=!0,i=n.setContent(r.value!==t?r.value:r.innerHTML,e),e.element=r,e.no_events||n.fire("LoadContent",e),e.element=r=null,i):void 0},save:function(e){var t=this,n=t.getElement(),r,i;if(n&&t.initialized)return e=e||{},e.save=!0,e.element=n,r=e.content=t.getContent(e),e.no_events||t.fire("SaveContent",e),r=e.content,/TEXTAREA|INPUT/i.test(n.nodeName)?n.value=r:(t.inline||(n.innerHTML=r),(i=N.getParent(t.id,"form"))&&T(i.elements,function(e){return e.name==t.id?(e.value=r,!1):void 0})),e.element=n=null,e.set_dirty!==!1&&(t.isNotDirty=!0),r},setContent:function(e,t){var n=this,r=n.getBody(),i;return t=t||{},t.format=t.format||"html",t.set=!0,t.content=e,t.no_events||n.fire("BeforeSetContent",t),e=t.content,0===e.length||/^\s+$/.test(e)?(i=n.settings.forced_root_block,i&&n.schema.isValidChild(r.nodeName.toLowerCase(),i.toLowerCase())?(e=H&&11>H?"":'
        ',e=n.dom.createHTML(i,n.settings.forced_root_block_attrs,e)):H||(e='
        '),r.innerHTML=e,n.fire("SetContent",t)):("raw"!==t.format&&(e=new o({},n.schema).serialize(n.parser.parse(e,{isRootContent:!0}))),t.content=B(e),n.dom.setHTML(r,t.content),t.no_events||n.fire("SetContent",t)),t.content},getContent:function(e){var t=this,n,r=t.getBody();return e=e||{},e.format=e.format||"html",e.get=!0,e.getInner=!0,e.no_events||t.fire("BeforeGetContent",e),n="raw"==e.format?r.innerHTML:"text"==e.format?r.innerText||r.textContent:t.serializer.serialize(r,e),e.content="text"!=e.format?B(n):n,e.no_events||t.fire("GetContent",e),e.content},insertContent:function(e){this.execCommand("mceInsertContent",!1,e)},isDirty:function(){return!this.isNotDirty},getContainer:function(){var e=this;return e.container||(e.container=N.get(e.editorContainer||e.id+"_parent")),e.container},getContentAreaContainer:function(){return this.contentAreaContainer},getElement:function(){return N.get(this.settings.content_element||this.id)},getWin:function(){var e=this,t;return e.contentWindow||(t=N.get(e.id+"_ifr"),t&&(e.contentWindow=t.contentWindow)),e.contentWindow},getDoc:function(){var e=this,t;return e.contentDocument||(t=e.getWin(),t&&(e.contentDocument=t.document)),e.contentDocument},getBody:function(){return this.bodyElement||this.getDoc().body},convertURL:function(e,t,n){var r=this,i=r.settings;return i.urlconverter_callback?r.execCallback("urlconverter_callback",e,n,!0,t):!i.convert_urls||n&&"LINK"==n.nodeName||0===e.indexOf("file:")||0===e.length?e:i.relative_urls?r.documentBaseURI.toRelative(e):e=r.documentBaseURI.toAbsolute(e,i.remove_script_host)},addVisual:function(e){var n=this,r=n.settings,i=n.dom,o;e=e||n.getBody(),n.hasVisual===t&&(n.hasVisual=r.visual),T(i.select("table,a",e),function(e){var t;switch(e.nodeName){case"TABLE":return o=r.visual_table_class||"mce-item-table",t=i.getAttrib(e,"border"),void(t&&"0"!=t||(n.hasVisual?i.addClass(e,o):i.removeClass(e,o)));case"A":return void(i.getAttrib(e,"href",!1)||(t=i.getAttrib(e,"name")||e.id,o=r.visual_anchor_class||"mce-item-anchor",t&&(n.hasVisual?i.addClass(e,o):i.removeClass(e,o))))}}),n.fire("VisualAid",{element:e,hasVisual:n.hasVisual})},remove:function(){var e=this;if(!e.removed){e.removed=1,e.save(),e.hasHiddenInput&&N.remove(e.getElement().nextSibling),e.inline||(H&&10>H&&e.getDoc().execCommand("SelectAll",!1,null),N.setStyle(e.id,"display",e.orgDisplay),e.getBody().onload=null,L.unbind(e.getWin()),L.unbind(e.getDoc()));var t=e.getContainer();L.unbind(e.getBody()),L.unbind(t),e.fire("remove"),e.editorManager.remove(e),N.remove(t),e.destroy()}},destroy:function(e){var t=this,n;if(!t.destroyed){if(!e&&!t.removed)return void t.remove();e&&M&&(L.unbind(t.getDoc()),L.unbind(t.getWin()),L.unbind(t.getBody())),e||(t.editorManager.off("beforeunload",t._beforeUnload),t.theme&&t.theme.destroy&&t.theme.destroy(),t.selection.destroy(),t.dom.destroy()),n=t.formElement,n&&(n._mceOldSubmit&&(n.submit=n._mceOldSubmit,n._mceOldSubmit=null),N.unbind(n,"submit reset",t.formEventDelegate)),t.contentAreaContainer=t.formElement=t.container=t.editorContainer=null,t.settings.content_element=t.bodyElement=t.contentDocument=t.contentWindow=null,t.selection&&(t.selection=t.selection.win=t.selection.dom=t.selection.dom.doc=null),t.destroyed=1}},_refreshContentEditable:function(){var e=this,t,n;e._isHidden()&&(t=e.getBody(),n=t.parentNode,n.removeChild(t),n.appendChild(t),t.focus())},_isHidden:function(){var e;return M?(e=this.selection.getSel(),!e||!e.rangeCount||0===e.rangeCount):0}},k(_.prototype,x),_}),r(ct,[],function(){var e={};return{rtl:!1,add:function(t,n){for(var r in n)e[r]=n[r];this.rtl=this.rtl||"rtl"===e._dir},translate:function(t){if("undefined"==typeof t)return t;if("string"!=typeof t&&t.raw)return t.raw;if(t.push){var n=t.slice(1);t=(e[t[0]]||t[0]).replace(/\{([^\}]+)\}/g,function(e,t){return n[t]})}return e[t]||t},data:e}}),r(ut,[y,g],function(e,t){function n(e){function s(){try{return document.activeElement}catch(e){return document.body}}function l(e,t){if(t&&t.startContainer){if(!e.isChildOf(t.startContainer,e.getRoot())||!e.isChildOf(t.endContainer,e.getRoot()))return;return{startContainer:t.startContainer,startOffset:t.startOffset,endContainer:t.endContainer,endOffset:t.endOffset}}return t}function c(e,t){var n;return t.startContainer?(n=e.getDoc().createRange(),n.setStart(t.startContainer,t.startOffset),n.setEnd(t.endContainer,t.endOffset)):n=t,n}function u(e){return!!a.getParent(e,n.isEditorUIElement)}function d(n){var d=n.editor;d.on("init",function(){(d.inline||t.ie)&&(d.on("nodechange keyup",function(){var e=document.activeElement;e&&e.id==d.id+"_ifr"&&(e=d.getBody()),d.dom.isChildOf(e,d.getBody())&&(d.lastRng=d.selection.getRng())}),t.webkit&&!r&&(r=function(){var t=e.activeEditor;if(t&&t.selection){var n=t.selection.getRng();n&&!n.collapsed&&(d.lastRng=n)}},a.bind(document,"selectionchange",r)))}),d.on("setcontent",function(){d.lastRng=null}),d.on("mousedown",function(){d.selection.lastFocusBookmark=null}),d.on("focusin",function(){var t=e.focusedEditor;d.selection.lastFocusBookmark&&(d.selection.setRng(c(d,d.selection.lastFocusBookmark)),d.selection.lastFocusBookmark=null),t!=d&&(t&&t.fire("blur",{focusedEditor:d}),e.activeEditor=d,e.focusedEditor=d,d.fire("focus",{blurredEditor:t}),d.focus(!0)),d.lastRng=null}),d.on("focusout",function(){window.setTimeout(function(){var t=e.focusedEditor;u(s())||t!=d||(d.fire("blur",{focusedEditor:null}),e.focusedEditor=null,d.selection&&(d.selection.lastFocusBookmark=null))},0)}),i||(i=function(t){var n=e.activeEditor;n&&t.target.ownerDocument==document&&(n.selection&&(n.selection.lastFocusBookmark=l(n.dom,n.lastRng)),u(t.target)||e.focusedEditor!=n||(n.fire("blur",{focusedEditor:null}),e.focusedEditor=null))},a.bind(document,"focusin",i)),d.inline&&!o&&(o=function(t){var n=e.activeEditor;if(n.inline&&!n.dom.isChildOf(t.target,n.getBody())){var r=n.selection.getRng();r.collapsed||(n.lastRng=r)}},a.bind(document,"mouseup",o))}function f(t){e.focusedEditor==t.editor&&(e.focusedEditor=null),e.activeEditor||(a.unbind(document,"selectionchange",r),a.unbind(document,"focusin",i),a.unbind(document,"mouseup",o),r=i=o=null)}e.on("AddEditor",d),e.on("RemoveEditor",f)}var r,i,o,a=e.DOM;return n.isEditorUIElement=function(e){return-1!==e.className.toString().indexOf("mce-")},n}),r(dt,[lt,y,F,g,p,ot,ct,ut],function(e,t,n,r,i,o,a,s){function l(e){var t=g.editors,n;delete t[e.id];for(var r=0;r0&&f(d(c),function(n){u.get(n)?(m=new e(n,t,s),l.push(m),m.render()):f(document.forms,function(e){f(e.elements,function(e){e.name===n&&(n="mce_editor_"+h++,u.setAttrib(e,"id",n),r(n,t))})})});break;case"textareas":case"specific_textareas":f(u.select("textarea"),function(e){t.editor_deselector&&o(e,t.editor_deselector)||(!t.editor_selector||o(e,t.editor_selector))&&r(n(e),t)})}t.oninit&&(c=g=0,f(l,function(e){g++,e.initialized?c++:e.on("init",function(){c++,c==g&&i(t,"oninit")}),c==g&&i(t,"oninit")}))}var s=this,l=[],m;s.settings=t,u.bind(window,"ready",a)},get:function(e){return arguments.length?e in this.editors?this.editors[e]:null:this.editors},add:function(e){var t=this,n=t.editors;return n[e.id]=e,n.push(e),t.activeEditor=e,t.fire("AddEditor",{editor:e}),m||(m=function(){t.fire("BeforeUnload")},u.bind(window,"beforeunload",m)),e},createEditor:function(t,n){return this.add(new e(t,n,this))},remove:function(e){var t=this,n,r=t.editors,i;{if(e)return"string"==typeof e?(e=e.selector||e,void f(u.select(e),function(e){t.remove(r[e.id])})):(i=e,r[i.id]?(l(i)&&t.fire("RemoveEditor",{editor:i}),r.length||u.unbind(window,"beforeunload",m),i.remove(),i):null);for(n=r.length-1;n>=0;n--)t.remove(r[n])}},execCommand:function(t,n,r){var i=this,o=i.get(r);switch(t){case"mceAddEditor":return i.get(r)||new e(r,i.settings,i).render(),!0;case"mceRemoveEditor":return o&&o.remove(),!0;case"mceToggleEditor":return o?(o.isHidden()?o.show():o.hide(),!0):(i.execCommand("mceAddEditor",0,r),!0)}return i.activeEditor?i.activeEditor.execCommand(t,n,r):!1},triggerSave:function(){f(this.editors,function(e){e.save()})},addI18n:function(e,t){a.add(e,t)},translate:function(e){return a.translate(e)}},p(g,o),g.setup(),window.tinymce=window.tinyMCE=g,g}),r(ft,[dt,p],function(e,t){var n=t.each,r=t.explode;e.on("AddEditor",function(e){var t=e.editor;t.on("preInit",function(){function e(e,t){n(t,function(t,n){t&&s.setStyle(e,n,t)}),s.rename(e,"span")}function i(e){s=t.dom,l.convert_fonts_to_spans&&n(s.select("font,u,strike",e.node),function(e){o[e.nodeName.toLowerCase()](s,e)})}var o,a,s,l=t.settings;l.inline_styles&&(a=r(l.font_size_legacy_values),o={font:function(t,n){e(n,{backgroundColor:n.style.backgroundColor,color:n.color,fontFamily:n.face,fontSize:a[parseInt(n.size,10)-1]})},u:function(t,n){e(n,{textDecoration:"underline"})},strike:function(t,n){e(n,{textDecoration:"line-through"})}},t.on("PreProcess SetContent",i))})})}),r(pt,[],function(){return{send:function(e){function t(){!e.async||4==n.readyState||r++>1e4?(e.success&&1e4>r&&200==n.status?e.success.call(e.success_scope,""+n.responseText,n,e):e.error&&e.error.call(e.error_scope,r>1e4?"TIMED_OUT":"GENERAL",n,e),n=null):setTimeout(t,10)}var n,r=0;if(e.scope=e.scope||this,e.success_scope=e.success_scope||e.scope,e.error_scope=e.error_scope||e.scope,e.async=e.async===!1?!1:!0,e.data=e.data||"",n=new XMLHttpRequest){if(n.overrideMimeType&&n.overrideMimeType(e.content_type),n.open(e.type||(e.data?"POST":"GET"),e.url,e.async),e.content_type&&n.setRequestHeader("Content-Type",e.content_type),n.setRequestHeader("X-Requested-With","XMLHttpRequest"),n.send(e.data),!e.async)return t();setTimeout(t,10)}}}}),r(ht,[],function(){function e(t,n){var r,i,o,a;if(n=n||'"',null===t)return"null";if(o=typeof t,"string"==o)return i="\bb t\nn\ff\rr\"\"''\\\\",n+t.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g,function(e,t){return'"'===n&&"'"===e?e:(r=i.indexOf(t),r+1?"\\"+i.charAt(r+1):(e=t.charCodeAt().toString(16),"\\u"+"0000".substring(e.length)+e))})+n;if("object"==o){if(t.hasOwnProperty&&"[object Array]"===Object.prototype.toString.call(t)){for(r=0,i="[";r0?",":"")+e(t[r],n);return i+"]"}i="{";for(a in t)t.hasOwnProperty(a)&&(i+="function"!=typeof t[a]?(i.length>1?","+n:n)+a+n+":"+e(t[a],n):"");return i+"}"}return""+t}return{serialize:e,parse:function(e){try{return window[String.fromCharCode(101)+"val"]("("+e+")")}catch(t){}}}}),r(mt,[ht,pt,p],function(e,t,n){function r(e){this.settings=i({},e),this.count=0}var i=n.extend;return r.sendRPC=function(e){return(new r).send(e)},r.prototype={send:function(n){var r=n.error,o=n.success;n=i(this.settings,n),n.success=function(t,i){t=e.parse(t),"undefined"==typeof t&&(t={error:"JSON Parse error."}),t.error?r.call(n.error_scope||n.scope,t.error,i):o.call(n.success_scope||n.scope,t.result)},n.error=function(e,t){r&&r.call(n.error_scope||n.scope,e,t)},n.data=e.serialize({id:n.id||"c"+this.count++,method:n.method,params:n.params}),n.content_type="application/json",t.send(n)}},r}),r(gt,[y],function(e){return{callbacks:{},count:0,send:function(n){var r=this,i=e.DOM,o=n.count!==t?n.count:r.count,a="tinymce_jsonp_"+o;r.callbacks[o]=function(e){i.remove(a),delete r.callbacks[o],n.callback(e)},i.add(i.doc.body,"script",{id:a,src:n.url,type:"text/javascript"}),r.count++}}}),r(vt,[],function(){function e(){s=[];for(var e in a)s.push(e);i.length=s.length}function n(){function n(e){var n,r;return r=e!==t?u+e:i.indexOf(",",u),-1===r||r>i.length?null:(n=i.substring(u,r),u=r+1,n)}var r,i,s,u=0;if(a={},c){o.load(l),i=o.getAttribute(l)||"";do{var d=n();if(null===d)break;if(r=n(parseInt(d,32)||0),null!==r){if(d=n(),null===d)break;s=n(parseInt(d,32)||0),r&&(a[r]=s)}}while(null!==r);e()}}function r(){var t,n="";if(c){for(var r in a)t=a[r],n+=(n?",":"")+r.length.toString(32)+","+r+","+t.length.toString(32)+","+t;o.setAttribute(l,n);try{o.save(l)}catch(i){}e()}}var i,o,a,s,l,c;try{if(window.localStorage)return localStorage}catch(u){}return l="tinymce",o=document.documentElement,c=!!o.addBehavior,c&&o.addBehavior("#default#userData"),i={key:function(e){return s[e]},getItem:function(e){return e in a?a[e]:null},setItem:function(e,t){a[e]=""+t,r()},removeItem:function(e){delete a[e],r()},clear:function(){a={},r()}},n(),i}),r(yt,[y,l,b,C,p,g],function(e,t,n,r,i,o){var a=window.tinymce;return a.DOM=e.DOM,a.ScriptLoader=n.ScriptLoader,a.PluginManager=r.PluginManager,a.ThemeManager=r.ThemeManager,a.dom=a.dom||{},a.dom.Event=t.Event,i.each(i,function(e,t){a[t]=e}),i.each("isOpera isWebKit isIE isGecko isMac".split(" "),function(e){a[e]=o[e.substr(2).toLowerCase()]}),{}}),r(bt,[z,p],function(e,t){return e.extend({Defaults:{firstControlClass:"first",lastControlClass:"last"},init:function(e){this.settings=t.extend({},this.Defaults,e)},preRender:function(e){e.addClass(this.settings.containerClass,"body")},applyClasses:function(e){var t=this,n=t.settings,r,i,o;r=e.items().filter(":visible"),i=n.firstControlClass,o=n.lastControlClass,r.each(function(e){e.removeClass(i).removeClass(o),n.controlClass&&e.addClass(n.controlClass)}),r.eq(0).addClass(i),r.eq(-1).addClass(o)},renderHtml:function(e){var t=this,n=t.settings,r,i="";return r=e.items(),r.eq(0).addClass(n.firstControlClass),r.eq(-1).addClass(n.lastControlClass),r.each(function(e){n.controlClass&&e.addClass(n.controlClass),i+=e.renderHtml()}),i},recalc:function(){},postRender:function(){}})}),r(Ct,[bt],function(e){return e.extend({Defaults:{containerClass:"abs-layout",controlClass:"abs-layout-item"},recalc:function(e){e.items().filter(":visible").each(function(e){var t=e.settings;e.layoutRect({x:t.x,y:t.y,w:t.w,h:t.h}),e.recalc&&e.recalc()})},renderHtml:function(e){return'
        '+this._super(e)}})}),r(xt,[$,Q],function(e,t){return e.extend({Mixins:[t],Defaults:{classes:"widget tooltip tooltip-n"},text:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().lastChild.innerHTML=t.encode(e)),t):t._value},renderHtml:function(){var e=this,t=e.classPrefix;return'"},repaint:function(){var e=this,t,n;t=e.getEl().style,n=e._layoutRect,t.left=n.x+"px",t.top=n.y+"px",t.zIndex=131070}})}),r(wt,[$,xt],function(e,t){var n,r=e.extend({init:function(e){var t=this;t._super(e),e=t.settings,t.canFocus=!0,e.tooltip&&r.tooltips!==!1&&(t.on("mouseenter",function(n){var r=t.tooltip().moveTo(-65535);if(n.control==t){var i=r.text(e.tooltip).show().testMoveRel(t.getEl(),["bc-tc","bc-tl","bc-tr"]);r.toggleClass("tooltip-n","bc-tc"==i),r.toggleClass("tooltip-nw","bc-tl"==i),r.toggleClass("tooltip-ne","bc-tr"==i),r.moveRel(t.getEl(),i)}else r.hide()}),t.on("mouseleave mousedown click",function(){t.tooltip().hide()})),t.aria("label",e.ariaLabel||e.tooltip)},tooltip:function(){return n||(n=new t({type:"tooltip"}),n.renderTo()),n},active:function(e){var t=this,n;return e!==n&&(t.aria("pressed",e),t.toggleClass("active",e)),t._super(e)},disabled:function(e){var t=this,n;return e!==n&&(t.aria("disabled",e),t.toggleClass("disabled",e)),t._super(e)},postRender:function(){var e=this,t=e.settings;e._rendered=!0,e._super(),e.parent()||!t.width&&!t.height||(e.initLayoutRect(),e.repaint()),t.autofocus&&e.focus()},remove:function(){this._super(),n&&(n.remove(),n=null)}});return r}),r(_t,[wt],function(e){return e.extend({Defaults:{classes:"widget btn",role:"button"},init:function(e){var t=this,n;t.on("click mousedown",function(e){e.preventDefault()}),t._super(e),n=e.size,e.subtype&&t.addClass(e.subtype),n&&t.addClass("btn-"+n)},icon:function(e){var t=this,n=t.classPrefix;if("undefined"==typeof e)return t.settings.icon;if(t.settings.icon=e,e=e?n+"ico "+n+"i-"+t.settings.icon:"",t._rendered){var r=t.getEl().firstChild,i=r.getElementsByTagName("i")[0];e?(i&&i==r.firstChild||(i=document.createElement("i"),r.insertBefore(i,r.firstChild)),i.className=e):i&&r.removeChild(i),t.text(t._text)}return t},repaint:function(){var e=this.getEl().firstChild.style;e.width=e.height="100%",this._super()},text:function(e){var t=this;if(t._rendered){var n=t.getEl().lastChild.lastChild;n&&(n.data=t.translate(e))}return t._super(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon,i;return i=e.settings.image,i?(r="none","string"!=typeof i&&(i=window.getSelection?i[0]:i[1]),i=" style=\"background-image: url('"+i+"')\""):i="",r=e.settings.icon?n+"ico "+n+"i-"+r:"",'
        "}})}),r(Nt,[G],function(e){return e.extend({Defaults:{defaultType:"button",role:"group"},renderHtml:function(){var e=this,t=e._layout;return e.addClass("btn-group"),e.preRender(),t.preRender(e),'
        '+(e.settings.html||"")+t.renderHtml(e)+"
        "}})}),r(Et,[wt],function(e){return e.extend({Defaults:{classes:"checkbox",role:"checkbox",checked:!1},init:function(e){var t=this;t._super(e),t.on("click mousedown",function(e){e.preventDefault()}),t.on("click",function(e){e.preventDefault(),t.disabled()||t.checked(!t.checked())}),t.checked(t.settings.checked)},checked:function(e){var t=this;return"undefined"!=typeof e?(e?t.addClass("checked"):t.removeClass("checked"),t._checked=e,t.aria("checked",e),t):t._checked},value:function(e){return this.checked(e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix;return'
        '+e.encode(e._text)+"
        "}})}),r(St,[_t,et],function(e,t){return e.extend({showPanel:function(){var e=this,n=e.settings;if(e.active(!0),e.panel)e.panel.show();else{var r=n.panel;r.type&&(r={layout:"grid",items:r}),r.role=r.role||"dialog",r.popover=!0,r.autohide=!0,r.ariaRoot=!0,e.panel=new t(r).on("hide",function(){e.active(!1)}).on("cancel",function(t){t.stopPropagation(),e.focus(),e.hidePanel()}).parent(e).renderTo(e.getContainerElm()),e.panel.fire("show"),e.panel.reflow()}e.panel.moveRel(e.getEl(),n.popoverAlign||(e.isRtl()?["bc-tr","bc-tc"]:["bc-tl","bc-tc"]))},hidePanel:function(){var e=this;e.panel&&e.panel.hide()},postRender:function(){var e=this;return e.aria("haspopup",!0),e.on("click",function(t){t.control===e&&(e.panel&&e.panel.visible()?e.hidePanel():(e.showPanel(),e.panel.focus(!!t.aria)))}),e._super()}})}),r(kt,[St,y],function(e,t){var n=t.DOM;return e.extend({init:function(e){this._super(e),this.addClass("colorbutton")},color:function(e){return e?(this._color=e,this.getEl("preview").style.backgroundColor=e,this):this._color},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"",i=e.settings.image?" style=\"background-image: url('"+e.settings.image+"')\"":"";return'
        '},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(r){r.aria&&"down"==r.aria.key||r.control!=e||n.getParent(r.target,"."+e.classPrefix+"open")||(r.stopImmediatePropagation(),t.call(e,r))}),delete e.settings.onclick,e._super()}})}),r(Tt,[wt,j,q],function(e,t,n){return e.extend({init:function(e){var t=this;t._super(e),t.addClass("combobox"),t.subinput=!0,t.ariaTarget="inp",e=t.settings,e.menu=e.menu||e.values,e.menu&&(e.icon="caret"),t.on("click",function(n){for(var r=n.target,i=t.getEl();r&&r!=i;)r.id&&-1!=r.id.indexOf("-open")&&(t.fire("action"),e.menu&&(t.showMenu(),n.aria&&t.menu.items()[0].focus())),r=r.parentNode}),t.on("keydown",function(e){"INPUT"==e.target.nodeName&&13==e.keyCode&&t.parents().reverse().each(function(n){return e.preventDefault(),t.fire("change"),n.hasEventListeners("submit")&&n.toJSON?(n.fire("submit",{data:n.toJSON()}),!1):void 0})}),e.placeholder&&(t.addClass("placeholder"),t.on("focusin",function(){t._hasOnChange||(n.on(t.getEl("inp"),"change",function(){t.fire("change")}),t._hasOnChange=!0),t.hasClass("placeholder")&&(t.getEl("inp").value="",t.removeClass("placeholder"))}),t.on("focusout",function(){0===t.value().length&&(t.getEl("inp").value=e.placeholder,t.addClass("placeholder"))}))},showMenu:function(){var e=this,n=e.settings,r;e.menu||(r=n.menu||[],r.length?r={type:"menu",items:r}:r.type=r.type||"menu",e.menu=t.create(r).parent(e).renderTo(e.getContainerElm()),e.fire("createmenu"),e.menu.reflow(),e.menu.on("cancel",function(t){t.control===e.menu&&e.focus()}),e.menu.on("show hide",function(t){t.control.items().each(function(t){t.active(t.value()==e.value())})}).fire("show"),e.menu.on("select",function(t){e.value(t.control.value())}),e.on("focusin",function(t){"INPUT"==t.target.tagName.toUpperCase()&&e.menu.hide()}),e.aria("expanded",!0)),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),e.menu.moveRel(e.getEl(),e.isRtl()?["br-tr","tr-br"]:["bl-tl","tl-bl"])},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t.removeClass("placeholder"),t._rendered&&(t.getEl("inp").value=e),t):t._rendered?(e=t.getEl("inp").value,e!=t.settings.placeholder?e:""):t._value},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl("inp").disabled=e),t._super(e)},focus:function(){this.getEl("inp").focus()},repaint:function(){var e=this,t=e.getEl(),r=e.getEl("open"),i=e.layoutRect(),o,a;o=r?i.w-n.getSize(r).width-10:i.w-10;var s=document;return s.all&&(!s.documentMode||s.documentMode<=8)&&(a=e.layoutRect().h-2+"px"),n.css(t.firstChild,{width:o,lineHeight:a}),e._super(),e},postRender:function(){var e=this;return n.on(this.getEl("inp"),"change",function(){e.fire("change")}),e._super()},remove:function(){n.off(this.getEl("inp")),this._super()},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.classPrefix,i=n.value||n.placeholder||"",o,a,s="",l="";return"spellcheck"in n&&(l+=' spellcheck="'+n.spellcheck+'"'),n.maxLength&&(l+=' maxlength="'+n.maxLength+'"'),n.size&&(l+=' size="'+n.size+'"'),n.subtype&&(l+=' type="'+n.subtype+'"'),e.disabled()&&(l+=' disabled="disabled"'),o=n.icon,o&&"caret"!=o&&(o=r+"ico "+r+"i-"+n.icon),a=e._text,(o||a)&&(s='
        ",e.addClass("has-open")),'
        "+s+"
        " +}})}),r(Rt,[wt],function(e){return e.extend({init:function(e){var t=this;e.delimiter||(e.delimiter="\xbb"),t._super(e),t.addClass("path"),t.canFocus=!0,t.on("click",function(e){var n,r=e.target;(n=r.getAttribute("data-index"))&&t.fire("select",{value:t.data()[n],index:n})})},focus:function(){var e=this;return e.getEl().firstChild.focus(),e},data:function(e){var t=this;return"undefined"!=typeof e?(t._data=e,t.update(),t):t._data},update:function(){this.innerHtml(this._getPathHtml())},postRender:function(){var e=this;e._super(),e.data(e.settings.data)},renderHtml:function(){var e=this;return'
        '+e._getPathHtml()+"
        "},_getPathHtml:function(){var e=this,t=e._data||[],n,r,i="",o=e.classPrefix;for(n=0,r=t.length;r>n;n++)i+=(n>0?'":"")+'
        '+t[n].name+"
        ";return i||(i='
        \xa0
        '),i}})}),r(At,[Rt,dt],function(e,t){return e.extend({postRender:function(){function e(e){if(1===e.nodeType){if("BR"==e.nodeName||e.getAttribute("data-mce-bogus"))return!0;if("bookmark"===e.getAttribute("data-mce-type"))return!0}return!1}var n=this,r=t.activeEditor;return n.on("select",function(t){var n=[],i,o=r.getBody();for(r.focus(),i=r.selection.getStart();i&&i!=o;)e(i)||n.push(i),i=i.parentNode;r.selection.select(n[n.length-1-t.index]),r.nodeChanged()}),r.on("nodeChange",function(t){for(var i=[],o=t.parents,a=o.length;a--;)if(1==o[a].nodeType&&!e(o[a])){var s=r.fire("ResolveName",{name:o[a].nodeName.toLowerCase(),target:o[a]});i.push({name:s.name})}n.data(i)}),n._super()}})}),r(Bt,[G],function(e){return e.extend({Defaults:{layout:"flex",align:"center",defaults:{flex:1}},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.addClass("formitem"),t.preRender(e),'
        '+(e.settings.title?'
        '+e.settings.title+"
        ":"")+'
        '+(e.settings.html||"")+t.renderHtml(e)+"
        "}})}),r(Dt,[G,Bt],function(e,t){return e.extend({Defaults:{containerCls:"form",layout:"flex",direction:"column",align:"stretch",flex:1,padding:20,labelGap:30,spacing:10,callbacks:{submit:function(){this.submit()}}},preRender:function(){var e=this,n=e.items();n.each(function(n){var r,i=n.settings.label;i&&(r=new t({layout:"flex",autoResize:"overflow",defaults:{flex:1},items:[{type:"label",id:n._id+"-l",text:i,flex:0,forId:n._id,disabled:n.disabled()}]}),r.type="formitem",n.aria("labelledby",n._id+"-l"),"undefined"==typeof n.settings.flex&&(n.settings.flex=1),e.replace(n,r),r.add(n))})},recalcLabels:function(){var e=this,t=0,n=[],r,i;if(e.settings.labelGapCalc!==!1)for(e.items().filter("formitem").each(function(e){var r=e.items()[0],i=r.getEl().clientWidth;t=i>t?i:t,n.push(r)}),i=e.settings.labelGap||0,r=n.length;r--;)n[r].settings.minWidth=t+i},visible:function(e){var t=this._super(e);return e===!0&&this._rendered&&this.recalcLabels(),t},submit:function(){return this.fire("submit",{data:this.toJSON()})},postRender:function(){var e=this;e._super(),e.recalcLabels(),e.fromJSON(e.settings.data)}})}),r(Lt,[Dt],function(e){return e.extend({Defaults:{containerCls:"fieldset",layout:"flex",direction:"column",align:"stretch",flex:1,padding:"25 15 5 15",labelGap:30,spacing:10,border:1},renderHtml:function(){var e=this,t=e._layout,n=e.classPrefix;return e.preRender(),t.preRender(e),'
        '+(e.settings.title?''+e.settings.title+"":"")+'
        '+(e.settings.html||"")+t.renderHtml(e)+"
        "}})}),r(Mt,[Tt],function(e){return e.extend({init:function(e){var t=this,n=tinymce.activeEditor,r;e.spellcheck=!1,r=n.settings.file_browser_callback,r&&(e.icon="browse",e.onaction=function(){r(t.getEl("inp").id,t.getEl("inp").value,e.filetype,window)}),t._super(e)}})}),r(Ht,[Ct],function(e){return e.extend({recalc:function(e){var t=e.layoutRect(),n=e.paddingBox();e.items().filter(":visible").each(function(e){e.layoutRect({x:n.left,y:n.top,w:t.innerW-n.right-n.left,h:t.innerH-n.top-n.bottom}),e.recalc&&e.recalc()})}})}),r(Pt,[Ct],function(e){return e.extend({recalc:function(e){var t,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v=[],y,b,C,x,w,_,N,E,S,k,T,R,A,B,D,L,M,H,P,O,I,F,z=Math.max,W=Math.min;for(r=e.items().filter(":visible"),i=e.layoutRect(),o=e._paddingBox,a=e.settings,f=e.isRtl()?a.direction||"row-reversed":a.direction,s=a.align,l=e.isRtl()?a.pack||"end":a.pack,c=a.spacing||0,("row-reversed"==f||"column-reverse"==f)&&(r=r.set(r.toArray().reverse()),f=f.split("-")[0]),"column"==f?(S="y",N="h",E="minH",k="maxH",R="innerH",T="top",A="deltaH",B="contentH",P="left",M="w",D="x",L="innerW",H="minW",O="right",I="deltaW",F="contentW"):(S="x",N="w",E="minW",k="maxW",R="innerW",T="left",A="deltaW",B="contentW",P="top",M="h",D="y",L="innerH",H="minH",O="bottom",I="deltaH",F="contentH"),d=i[R]-o[T]-o[T],_=u=0,t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),m=p.settings,g=m.flex,d-=n-1>t?c:0,g>0&&(u+=g,h[k]&&v.push(p),h.flex=g),d-=h[E],y=o[P]+h[H]+o[O],y>_&&(_=y);if(x={},x[E]=0>d?i[E]-d+i[A]:i[R]-d+i[A],x[H]=_+i[I],x[B]=i[R]-d,x[F]=_,x.minW=W(x.minW,i.maxW),x.minH=W(x.minH,i.maxH),x.minW=z(x.minW,i.startMinWidth),x.minH=z(x.minH,i.startMinHeight),!i.autoResize||x.minW==i.minW&&x.minH==i.minH){for(C=d/u,t=0,n=v.length;n>t;t++)p=v[t],h=p.layoutRect(),b=h[k],y=h[E]+h.flex*C,y>b?(d-=h[k]-h[E],u-=h.flex,h.flex=0,h.maxFlexSize=b):h.maxFlexSize=0;for(C=d/u,w=o[T],x={},0===u&&("end"==l?w=d+o[T]:"center"==l?(w=Math.round(i[R]/2-(i[R]-d)/2)+o[T],0>w&&(w=o[T])):"justify"==l&&(w=o[T],c=Math.floor(d/(r.length-1)))),x[D]=o[P],t=0,n=r.length;n>t;t++)p=r[t],h=p.layoutRect(),y=h.maxFlexSize||h[E],"center"===s?x[D]=Math.round(i[L]/2-h[M]/2):"stretch"===s?(x[M]=z(h[H]||0,i[L]-o[P]-o[O]),x[D]=o[P]):"end"===s&&(x[D]=i[L]-h[M]-o.top),h.flex>0&&(y+=h.flex*C),x[N]=y,x[S]=w,p.layoutRect(x),p.recalc&&p.recalc(),w+=y+c}else if(x.w=x.minW,x.h=x.minH,e.layoutRect(x),this.recalc(e),null===e._lastRect){var V=e.parent();V&&(V._lastRect=null,V.recalc())}}})}),r(Ot,[bt],function(e){return e.extend({Defaults:{containerClass:"flow-layout",controlClass:"flow-layout-item",endClass:"break"},recalc:function(e){e.items().filter(":visible").each(function(e){e.recalc&&e.recalc()})}})}),r(It,[$,wt,et,p,dt,g],function(e,t,n,r,i,o){function a(e){function t(t,n){return function(){var r=this;e.on("nodeChange",function(i){var o=e.formatter,a=null;s(i.parents,function(e){return s(t,function(t){return n?o.matchNode(e,n,{value:t.value})&&(a=t.value):o.matchNode(e,t.value)&&(a=t.value),a?!1:void 0}),a?!1:void 0}),r.value(a)})}}function r(e){e=e.replace(/;$/,"").split(";");for(var t=e.length;t--;)e[t]=e[t].split("=");return e}function i(){function t(e){var n=[];if(e)return s(e,function(e){var o={text:e.title,icon:e.icon};if(e.items)o.menu=t(e.items);else{var a=e.format||"custom"+r++;e.format||(e.name=a,i.push(e)),o.format=a}n.push(o)}),n}function n(){var n;return n=t(e.settings.style_formats_merge?e.settings.style_formats?o.concat(e.settings.style_formats):o:e.settings.style_formats||o)}var r=0,i=[],o=[{title:"Headings",items:[{title:"Heading 1",format:"h1"},{title:"Heading 2",format:"h2"},{title:"Heading 3",format:"h3"},{title:"Heading 4",format:"h4"},{title:"Heading 5",format:"h5"},{title:"Heading 6",format:"h6"}]},{title:"Inline",items:[{title:"Bold",icon:"bold",format:"bold"},{title:"Italic",icon:"italic",format:"italic"},{title:"Underline",icon:"underline",format:"underline"},{title:"Strikethrough",icon:"strikethrough",format:"strikethrough"},{title:"Superscript",icon:"superscript",format:"superscript"},{title:"Subscript",icon:"subscript",format:"subscript"},{title:"Code",icon:"code",format:"code"}]},{title:"Blocks",items:[{title:"Paragraph",format:"p"},{title:"Blockquote",format:"blockquote"},{title:"Div",format:"div"},{title:"Pre",format:"pre"}]},{title:"Alignment",items:[{title:"Left",icon:"alignleft",format:"alignleft"},{title:"Center",icon:"aligncenter",format:"aligncenter"},{title:"Right",icon:"alignright",format:"alignright"},{title:"Justify",icon:"alignjustify",format:"alignjustify"}]}];return e.on("init",function(){s(i,function(t){e.formatter.register(t.name,t)})}),{type:"menu",items:n(),onPostRender:function(t){e.fire("renderFormatsMenu",{control:t.control})},itemDefaults:{preview:!0,textStyle:function(){return this.settings.format?e.formatter.getCssText(this.settings.format):void 0},onPostRender:function(){var t=this,n=this.settings.format;n&&t.parent().on("show",function(){t.disabled(!e.formatter.canApply(n)),t.active(e.formatter.match(n))})},onclick:function(){this.settings.format&&d(this.settings.format)}}}}function o(){return e.undoManager?e.undoManager.hasUndo():!1}function a(){return e.undoManager?e.undoManager.hasRedo():!1}function l(){var t=this;t.disabled(!o()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!o())})}function c(){var t=this;t.disabled(!a()),e.on("Undo Redo AddUndo TypingUndo",function(){t.disabled(!a())})}function u(){var t=this;e.on("VisualAid",function(e){t.active(e.hasVisual)}),t.active(e.hasVisual)}function d(t){t.control&&(t=t.control.value()),t&&e.execCommand("mceToggleFormat",!1,t)}var f;f=i(),s({bold:"Bold",italic:"Italic",underline:"Underline",strikethrough:"Strikethrough",subscript:"Subscript",superscript:"Superscript"},function(t,n){e.addButton(n,{tooltip:t,onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})},onclick:function(){d(n)}})}),s({outdent:["Decrease indent","Outdent"],indent:["Increase indent","Indent"],cut:["Cut","Cut"],copy:["Copy","Copy"],paste:["Paste","Paste"],help:["Help","mceHelp"],selectall:["Select all","SelectAll"],hr:["Insert horizontal rule","InsertHorizontalRule"],removeformat:["Clear formatting","RemoveFormat"],visualaid:["Visual aids","mceToggleVisualAid"],newdocument:["New document","mceNewDocument"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1]})}),s({blockquote:["Blockquote","mceBlockQuote"],numlist:["Numbered list","InsertOrderedList"],bullist:["Bullet list","InsertUnorderedList"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],alignleft:["Align left","JustifyLeft"],aligncenter:["Align center","JustifyCenter"],alignright:["Align right","JustifyRight"],alignjustify:["Justify","JustifyFull"]},function(t,n){e.addButton(n,{tooltip:t[0],cmd:t[1],onPostRender:function(){var t=this;e.formatter?e.formatter.formatChanged(n,function(e){t.active(e)}):e.on("init",function(){e.formatter.formatChanged(n,function(e){t.active(e)})})}})}),e.addButton("undo",{tooltip:"Undo",onPostRender:l,cmd:"undo"}),e.addButton("redo",{tooltip:"Redo",onPostRender:c,cmd:"redo"}),e.addMenuItem("newdocument",{text:"New document",shortcut:"Ctrl+N",icon:"newdocument",cmd:"mceNewDocument"}),e.addMenuItem("undo",{text:"Undo",icon:"undo",shortcut:"Ctrl+Z",onPostRender:l,cmd:"undo"}),e.addMenuItem("redo",{text:"Redo",icon:"redo",shortcut:"Ctrl+Y",onPostRender:c,cmd:"redo"}),e.addMenuItem("visualaid",{text:"Visual aids",selectable:!0,onPostRender:u,cmd:"mceToggleVisualAid"}),s({cut:["Cut","Cut","Ctrl+X"],copy:["Copy","Copy","Ctrl+C"],paste:["Paste","Paste","Ctrl+V"],selectall:["Select all","SelectAll","Ctrl+A"],bold:["Bold","Bold","Ctrl+B"],italic:["Italic","Italic","Ctrl+I"],underline:["Underline","Underline"],strikethrough:["Strikethrough","Strikethrough"],subscript:["Subscript","Subscript"],superscript:["Superscript","Superscript"],removeformat:["Clear formatting","RemoveFormat"]},function(t,n){e.addMenuItem(n,{text:t[0],icon:n,shortcut:t[2],cmd:t[1]})}),e.on("mousedown",function(){n.hideAll()}),e.addButton("styleselect",{type:"menubutton",text:"Formats",menu:f}),e.addButton("formatselect",function(){var n=[],i=r(e.settings.block_formats||"Paragraph=p;Address=address;Pre=pre;Heading 1=h1;Heading 2=h2;Heading 3=h3;Heading 4=h4;Heading 5=h5;Heading 6=h6");return s(i,function(t){n.push({text:t[0],value:t[1],textStyle:function(){return e.formatter.getCssText(t[1])}})}),{type:"listbox",text:i[0][0],values:n,fixedWidth:!0,onselect:d,onPostRender:t(n)}}),e.addButton("fontselect",function(){var n="Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",i=[],o=r(e.settings.font_formats||n);return s(o,function(e){i.push({text:{raw:e[0]},value:e[1],textStyle:-1==e[1].indexOf("dings")?"font-family:"+e[1]:""})}),{type:"listbox",text:"Font Family",tooltip:"Font Family",values:i,fixedWidth:!0,onPostRender:t(i,"fontname"),onselect:function(t){t.control.settings.value&&e.execCommand("FontName",!1,t.control.settings.value)}}}),e.addButton("fontsizeselect",function(){var n=[],r="8pt 10pt 12pt 14pt 18pt 24pt 36pt",i=e.settings.fontsize_formats||r;return s(i.split(" "),function(e){n.push({text:e,value:e})}),{type:"listbox",text:"Font Sizes",tooltip:"Font Sizes",values:n,fixedWidth:!0,onPostRender:t(n,"fontsize"),onclick:function(t){t.control.settings.value&&e.execCommand("FontSize",!1,t.control.settings.value)}}}),e.addMenuItem("formats",{text:"Formats",menu:f})}var s=r.each;i.on("AddEditor",function(t){t.editor.rtl&&(e.rtl=!0),a(t.editor)}),e.translate=function(e){return i.translate(e)},t.tooltips=!o.iOS}),r(Ft,[Ct],function(e){return e.extend({recalc:function(e){var t=e.settings,n,r,i,o,a,s,l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,N=[],E=[],S,k,T,R;for(t=e.settings,i=e.items().filter(":visible"),o=e.layoutRect(),r=t.columns||Math.ceil(Math.sqrt(i.length)),n=Math.ceil(i.length/r),y=t.spacingH||t.spacing||0,b=t.spacingV||t.spacing||0,C=t.alignH||t.align,x=t.alignV||t.align,g=e._paddingBox,C&&"string"==typeof C&&(C=[C]),x&&"string"==typeof x&&(x=[x]),d=0;r>d;d++)N.push(0);for(f=0;n>f;f++)E.push(0);for(f=0;n>f;f++)for(d=0;r>d&&(u=i[f*r+d],u);d++)c=u.layoutRect(),S=c.minW,k=c.minH,N[d]=S>N[d]?S:N[d],E[f]=k>E[f]?k:E[f];for(T=o.innerW-g.left-g.right,w=0,d=0;r>d;d++)w+=N[d]+(d>0?y:0),T-=(d>0?y:0)+N[d];for(R=o.innerH-g.top-g.bottom,_=0,f=0;n>f;f++)_+=E[f]+(f>0?b:0),R-=(f>0?b:0)+E[f];if(w+=g.left+g.right,_+=g.top+g.bottom,l={},l.minW=w+(o.w-o.innerW),l.minH=_+(o.h-o.innerH),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH,l.minW=Math.min(l.minW,o.maxW),l.minH=Math.min(l.minH,o.maxH),l.minW=Math.max(l.minW,o.startMinWidth),l.minH=Math.max(l.minH,o.startMinHeight),!o.autoResize||l.minW==o.minW&&l.minH==o.minH){o.autoResize&&(l=e.layoutRect(l),l.contentW=l.minW-o.deltaW,l.contentH=l.minH-o.deltaH);var A;A="start"==t.packV?0:R>0?Math.floor(R/n):0;var B=0,D=t.flexWidths;if(D)for(d=0;dd;d++)N[d]+=D?D[d]*L:L;for(h=g.top,f=0;n>f;f++){for(p=g.left,s=E[f]+A,d=0;r>d&&(u=i[f*r+d],u);d++)m=u.settings,c=u.layoutRect(),a=Math.max(N[d],c.startMinWidth),c.x=p,c.y=h,v=m.alignH||(C?C[d]||C[0]:null),"center"==v?c.x=p+a/2-c.w/2:"right"==v?c.x=p+a-c.w:"stretch"==v&&(c.w=a),v=m.alignV||(x?x[d]||x[0]:null),"center"==v?c.y=h+s/2-c.h/2:"bottom"==v?c.y=h+s-c.h:"stretch"==v&&(c.h=s),u.layoutRect(c),p+=a+y,u.recalc&&u.recalc();h+=s+b}}else if(l.w=l.minW,l.h=l.minH,e.layoutRect(l),this.recalc(e),null===e._lastRect){var M=e.parent();M&&(M._lastRect=null,M.recalc())}}})}),r(zt,[wt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("iframe"),e.canFocus=!1,''},src:function(e){this.getEl().src=e},html:function(e,t){var n=this,r=this.getEl().contentWindow.document.body;return r?(r.innerHTML=e,t&&t()):setTimeout(function(){n.html(e)},0),this}})}),r(Wt,[wt,q],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t.addClass("widget"),t.addClass("label"),t.canFocus=!1,e.multiline&&t.addClass("autoscroll"),e.strong&&t.addClass("strong")},initLayoutRect:function(){var e=this,n=e._super();if(e.settings.multiline){var r=t.getSize(e.getEl());r.width>n.maxW&&(n.minW=n.maxW,e.addClass("multiline")),e.getEl().style.width=n.minW+"px",n.startMinH=n.h=n.minH=Math.min(n.maxH,t.getSize(e.getEl()).height)}return n},repaint:function(){var e=this;return e.settings.multiline||(e.getEl().style.lineHeight=e.layoutRect().h+"px"),e._super()},text:function(e){var t=this;return t._rendered&&e&&this.innerHtml(t.encode(e)),t._super(e)},renderHtml:function(){var e=this,t=e.settings.forId;return'"}})}),r(Vt,[G],function(e){return e.extend({Defaults:{role:"toolbar",layout:"flow"},init:function(e){var t=this;t._super(e),t.addClass("toolbar")},postRender:function(){var e=this;return e.items().addClass("toolbar-item"),e._super()}})}),r(Ut,[Vt],function(e){return e.extend({Defaults:{role:"menubar",containerCls:"menubar",ariaRoot:!0,defaults:{type:"menubutton"}}})}),r(qt,[_t,j,Ut],function(e,t,n){function r(e,t){for(;e;){if(t===e)return!0;e=e.parentNode}return!1}var i=e.extend({init:function(e){var t=this;t._renderOpen=!0,t._super(e),t.addClass("menubtn"),e.fixedWidth&&t.addClass("fixed-width"),t.aria("haspopup",!0),t.hasPopup=!0},showMenu:function(){var e=this,n=e.settings,r;return e.menu&&e.menu.visible()?e.hideMenu():(e.menu||(r=n.menu||[],r.length?r={type:"menu",items:r}:r.type=r.type||"menu",e.menu=t.create(r).parent(e).renderTo(),e.fire("createmenu"),e.menu.reflow(),e.menu.on("cancel",function(t){t.control.parent()===e.menu&&(t.stopPropagation(),e.focus(),e.hideMenu())}),e.menu.on("select",function(){e.focus()}),e.menu.on("show hide",function(t){t.control==e.menu&&e.activeMenu("show"==t.type),e.aria("expanded","show"==t.type)}).fire("show")),e.menu.show(),e.menu.layoutRect({w:e.layoutRect().w}),void e.menu.moveRel(e.getEl(),e.isRtl()?["br-tr","tr-br"]:["bl-tl","tl-bl"]))},hideMenu:function(){var e=this;e.menu&&(e.menu.items().each(function(e){e.hideMenu&&e.hideMenu()}),e.menu.hide())},activeMenu:function(e){this.toggleClass("active",e)},renderHtml:function(){var e=this,t=e._id,r=e.classPrefix,i=e.settings.icon?r+"ico "+r+"i-"+e.settings.icon:"";return e.aria("role",e.parent()instanceof n?"menuitem":"button"),'
        '},postRender:function(){var e=this;return e.on("click",function(t){t.control===e&&r(t.target,e.getEl())&&(e.showMenu(),t.aria&&e.menu.items()[0].focus())}),e.on("mouseenter",function(t){var n=t.control,r=e.parent(),o;n&&r&&n instanceof i&&n.parent()==r&&(r.items().filter("MenuButton").each(function(e){e.hideMenu&&e!=n&&(e.menu&&e.menu.visible()&&(o=!0),e.hideMenu())}),o&&(n.focus(),n.showMenu()))}),e._super()},text:function(e){var t=this,n,r;if(t._rendered)for(r=t.getEl("open").getElementsByTagName("span"),n=0;n0&&(o=n[0].text,t._value=n[0].value),e.menu=n}e.text=e.text||o||n[0].text,t._super(e),t.addClass("listbox"),t.on("select",function(n){var r=n.control;a&&(n.lastControl=a),e.multiple?r.active(!r.active()):t.value(n.control.settings.value),a=r})},value:function(e){function t(e,n){e.items().each(function(e){r=e.value()===n,r&&(i=i||e.text()),e.active(r),e.menu&&t(e.menu,n)})}var n=this,r,i,o,a;if("undefined"!=typeof e){if(n.menu)t(n.menu,e);else for(o=n.settings.menu,a=0;a'+("-"!==o?'\xa0":"")+("-"!==o?''+o+"":"")+(l?'
        '+l+"
        ":"")+(r.menu?'
        ':"")+""},postRender:function(){var e=this,t=e.settings,n=t.textStyle;if("function"==typeof n&&(n=n.call(this)),n){var r=e.getEl("text");r&&r.setAttribute("style",n)}return e.on("mouseenter click",function(n){n.control===e&&(t.menu||"click"!==n.type?(e.showMenu(),n.aria&&e.menu.focus(!0)):(e.fire("select"),e.parent().hideAll()))}),e._super(),e},active:function(e){return"undefined"!=typeof e&&this.aria("checked",e),this._super(e)},remove:function(){this._super(),this.menu&&this.menu.remove()}})}),r(Kt,[et,jt,p],function(e,t,n){var r=e.extend({Defaults:{defaultType:"menuitem",border:1,layout:"stack",role:"application",bodyRole:"menu",ariaRoot:!0},init:function(e){var t=this;if(e.autohide=!0,e.constrainToViewport=!0,e.itemDefaults)for(var r=e.items,i=r.length;i--;)r[i]=n.extend({},e.itemDefaults,r[i]);t._super(e),t.addClass("menu")},repaint:function(){return this.toggleClass("menu-align",!0),this._super(),this.getEl().style.height="",this.getEl("body").style.height="",this},cancel:function(){var e=this;e.hideAll(),e.fire("select")},hideAll:function(){var e=this;return this.find("menuitem").exec("hideMenu"),e._super()},preRender:function(){var e=this;return e.items().each(function(t){var n=t.settings;return n.icon||n.selectable?(e._hasIcons=!0,!1):void 0}),e._super()}});return r}),r(Gt,[Et],function(e){return e.extend({Defaults:{classes:"radio",role:"radio"}})}),r(Yt,[wt,Y],function(e,t){return e.extend({renderHtml:function(){var e=this,t=e.classPrefix;return e.addClass("resizehandle"),"both"==e.settings.direction&&e.addClass("resizehandle-both"),e.canFocus=!1,'
        '},postRender:function(){var e=this;e._super(),e.resizeDragHelper=new t(this._id,{start:function(){e.fire("ResizeStart")},drag:function(t){"both"!=e.settings.direction&&(t.deltaX=0),e.fire("Resize",t)},stop:function(){e.fire("ResizeEnd")}})},remove:function(){return this.resizeDragHelper&&this.resizeDragHelper.destroy(),this._super()}})}),r(Xt,[wt],function(e){return e.extend({renderHtml:function(){var e=this;return e.addClass("spacer"),e.canFocus=!1,'
        '}})}),r(Jt,[qt,q],function(e,t){return e.extend({Defaults:{classes:"widget btn splitbtn",role:"button"},repaint:function(){var e=this,n=e.getEl(),r=e.layoutRect(),i,o;return e._super(),i=n.firstChild,o=n.lastChild,t.css(i,{width:r.w-t.getSize(o).width,height:r.h-2}),t.css(o,{height:r.h-2}),e},activeMenu:function(e){var n=this;t.toggleClass(n.getEl().lastChild,n.classPrefix+"active",e)},renderHtml:function(){var e=this,t=e._id,n=e.classPrefix,r=e.settings.icon?n+"ico "+n+"i-"+e.settings.icon:"";return'
        '},postRender:function(){var e=this,t=e.settings.onclick;return e.on("click",function(e){var n=e.target;if(e.control==this)for(;n;){if(e.aria&&"down"!=e.aria.key||"BUTTON"==n.nodeName&&-1==n.className.indexOf("open"))return e.stopImmediatePropagation(),void t.call(this,e);n=n.parentNode}}),delete e.settings.onclick,e._super()}})}),r(Qt,[Ot],function(e){return e.extend({Defaults:{containerClass:"stack-layout",controlClass:"stack-layout-item",endClass:"break"}})}),r(Zt,[J,q],function(e,t){return e.extend({lastIdx:0,Defaults:{layout:"absolute",defaults:{type:"panel"}},activateTab:function(e){var n;this.activeTabId&&(n=this.getEl(this.activeTabId),t.removeClass(n,this.classPrefix+"active"),n.setAttribute("aria-selected","false")),this.activeTabId="t"+e,n=this.getEl("t"+e),n.setAttribute("aria-selected","true"),t.addClass(n,this.classPrefix+"active"),e!=this.lastIdx&&(this.items()[this.lastIdx].hide(),this.lastIdx=e),this.items()[e].show().fire("showtab"),this.reflow()},renderHtml:function(){var e=this,t=e._layout,n="",r=e.classPrefix;return e.preRender(),t.preRender(e),e.items().each(function(t,i){var o=e._id+"-t"+i;t.aria("role","tabpanel"),t.aria("labelledby",o),n+='"}),'
        '+n+'
        '+t.renderHtml(e)+"
        "},postRender:function(){var e=this;e._super(),e.settings.activeTab=e.settings.activeTab||0,e.activateTab(e.settings.activeTab),this.on("click",function(t){var n=t.target.parentNode;if(t.target.parentNode.id==e._id+"-head")for(var r=n.childNodes.length;r--;)n.childNodes[r]==t.target&&e.activateTab(r)})},initLayoutRect:function(){var e=this,n,r,i;r=t.getSize(e.getEl("head")).width,r=0>r?0:r,i=0,e.items().each(function(t,n){r=Math.max(r,t.layoutRect().minW),i=Math.max(i,t.layoutRect().minH),e.settings.activeTab!=n&&t.hide()}),e.items().each(function(e){e.settings.x=0,e.settings.y=0,e.settings.w=r,e.settings.h=i,e.layoutRect({x:0,y:0,w:r,h:i})});var o=t.getSize(e.getEl("head")).height;return e.settings.minWidth=r,e.settings.minHeight=i+o,n=e._super(),n.deltaH+=o,n.innerH=n.h-n.deltaH,n}})}),r(en,[wt,q],function(e,t){return e.extend({init:function(e){var t=this;t._super(e),t._value=e.value||"",t.addClass("textbox"),e.multiline?t.addClass("multiline"):t.on("keydown",function(e){13==e.keyCode&&t.parents().reverse().each(function(t){return e.preventDefault(),t.hasEventListeners("submit")&&t.toJSON?(t.fire("submit",{data:t.toJSON()}),!1):void 0})})},disabled:function(e){var t=this;return t._rendered&&"undefined"!=typeof e&&(t.getEl().disabled=e),t._super(e)},value:function(e){var t=this;return"undefined"!=typeof e?(t._value=e,t._rendered&&(t.getEl().value=e),t):t._rendered?t.getEl().value:t._value},repaint:function(){var e=this,t,n,r,i=0,o=0,a;t=e.getEl().style,n=e._layoutRect,a=e._lastRepaintRect||{};var s=document;return!e.settings.multiline&&s.all&&(!s.documentMode||s.documentMode<=8)&&(t.lineHeight=n.h-o+"px"),r=e._borderBox,i=r.left+r.right+8,o=r.top+r.bottom+(e.settings.multiline?8:0),n.x!==a.x&&(t.left=n.x+"px",a.x=n.x),n.y!==a.y&&(t.top=n.y+"px",a.y=n.y),n.w!==a.w&&(t.width=n.w-i+"px",a.w=n.w),n.h!==a.h&&(t.height=n.h-o+"px",a.h=n.h),e._lastRepaintRect=a,e.fire("repaint",{},!1),e},renderHtml:function(){var e=this,t=e._id,n=e.settings,r=e.encode(e._value,!1),i="";return"spellcheck"in n&&(i+=' spellcheck="'+n.spellcheck+'"'),n.maxLength&&(i+=' maxlength="'+n.maxLength+'"'),n.size&&(i+=' size="'+n.size+'"'),n.subtype&&(i+=' type="'+n.subtype+'"'),e.disabled()&&(i+=' disabled="disabled"'),n.multiline?'":'"},postRender:function(){var e=this;return t.on(e.getEl(),"change",function(t){e.fire("change",t)}),e._super()},remove:function(){t.off(this.getEl()),this._super()}})}),r(tn,[q,$],function(e,t){return function(n,r){var i=this,o,a=t.classPrefix;i.show=function(t){return i.hide(),o=!0,window.setTimeout(function(){o&&n.appendChild(e.createFragment('
        '))},t||0),i},i.hide=function(){var e=n.lastChild;return e&&-1!=e.className.indexOf("throbber")&&e.parentNode.removeChild(e),o=!1,i}}}),a([l,c,u,d,f,p,h,m,g,v,y,b,C,x,w,_,N,E,S,k,T,R,A,B,D,L,M,H,P,O,I,F,z,W,V,U,q,$,j,K,G,Y,X,J,Q,Z,et,tt,nt,rt,it,ot,at,st,lt,ct,ut,dt,ft,pt,ht,mt,gt,vt,yt,bt,Ct,xt,wt,_t,Nt,Et,St,kt,Tt,Rt,At,Bt,Dt,Lt,Mt,Ht,Pt,Ot,It,Ft,zt,Wt,Vt,Ut,qt,$t,jt,Kt,Gt,Yt,Xt,Jt,Qt,Zt,en,tn])}(this);tinymce.PluginManager.add("advlist",function(t){function e(t,e){var n=[];return tinymce.each(e.split(/[ ,]/),function(t){n.push({text:t.replace(/\-/g," ").replace(/\b\w/g,function(t){return t.toUpperCase()}),data:"default"==t?"":t})}),n}function n(e,n){var o,l=t.dom,a=t.selection;o=l.getParent(a.getNode(),"ol,ul"),o&&o.nodeName==e&&n!==!1||t.execCommand("UL"==e?"InsertUnorderedList":"InsertOrderedList"),n=n===!1?i[e]:n,i[e]=n,o=l.getParent(a.getNode(),"ol,ul"),o&&(l.setStyle(o,"listStyleType",n),o.removeAttribute("data-mce-style")),t.focus()}function o(e){var n=t.dom.getStyle(t.dom.getParent(t.selection.getNode(),"ol,ul"),"listStyleType")||"";e.control.items().each(function(t){t.active(t.settings.data===n)})}var l,a,i={};l=e("OL",t.getParam("advlist_number_styles","default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman")),a=e("UL",t.getParam("advlist_bullet_styles","default,circle,disc,square")),t.addButton("numlist",{type:"splitbutton",tooltip:"Numbered list",menu:l,onshow:o,onselect:function(t){n("OL",t.control.settings.data)},onclick:function(){n("OL",!1)}}),t.addButton("bullist",{type:"splitbutton",tooltip:"Bullet list",menu:a,onshow:o,onselect:function(t){n("UL",t.control.settings.data)},onclick:function(){n("UL",!1)}})});tinymce.PluginManager.add("anchor",function(n){function e(){var e=n.selection.getNode();n.windowManager.open({title:"Anchor",body:{type:"textbox",name:"name",size:40,label:"Name",value:e.name||e.id},onsubmit:function(e){n.execCommand("mceInsertContent",!1,n.dom.createHTML("a",{id:e.data.name}))}})}n.addButton("anchor",{icon:"anchor",tooltip:"Anchor",onclick:e,stateSelector:"a:not([href])"}),n.addMenuItem("anchor",{icon:"anchor",text:"Anchor",context:"insert",onclick:e})});tinymce.PluginManager.add("autolink",function(t){function n(t){o(t,-1,"(",!0)}function e(t){o(t,0,"",!0)}function i(t){o(t,-1,"",!1)}function o(t,n,e){function i(t,n){if(0>n&&(n=0),3==t.nodeType){var e=t.data.length;n>e&&(n=e)}return n}function o(t,n){f.setStart(t,i(t,n))}function r(t,n){f.setEnd(t,i(t,n))}var f,d,a,s,c,l,u,g,h;if(f=t.selection.getRng(!0).cloneRange(),f.startOffset<5){if(g=f.endContainer.previousSibling,!g){if(!f.endContainer.firstChild||!f.endContainer.firstChild.nextSibling)return;g=f.endContainer.firstChild.nextSibling}if(h=g.length,o(g,h),r(g,h),f.endOffset<5)return;d=f.endOffset,s=g}else{if(s=f.endContainer,3!=s.nodeType&&s.firstChild){for(;3!=s.nodeType&&s.firstChild;)s=s.firstChild;3==s.nodeType&&(o(s,0),r(s,s.nodeValue.length))}d=1==f.endOffset?2:f.endOffset-1-n}a=d;do o(s,d>=2?d-2:0),r(s,d>=1?d-1:0),d-=1;while(" "!=f.toString()&&""!==f.toString()&&160!=f.toString().charCodeAt(0)&&d-2>=0&&f.toString()!=e);f.toString()==e||160==f.toString().charCodeAt(0)?(o(s,d),r(s,a),d+=1):0===f.startOffset?(o(s,0),r(s,a)):(o(s,d),r(s,a)),l=f.toString(),"."==l.charAt(l.length-1)&&r(s,a-1),l=f.toString(),u=l.match(/^(https?:\/\/|ssh:\/\/|ftp:\/\/|file:\/|www\.|(?:mailto:)?[A-Z0-9._%+\-]+@)(.+)$/i),u&&("www."==u[1]?u[1]="http://www.":/@$/.test(u[1])&&!/^mailto:/.test(u[1])&&(u[1]="mailto:"+u[1]),c=t.selection.getBookmark(),t.selection.setRng(f),t.execCommand("createlink",!1,u[1]+u[2]),t.selection.moveToBookmark(c),t.nodeChanged())}var r;return t.on("keydown",function(n){return 13==n.keyCode?i(t):void 0}),tinymce.Env.ie?void t.on("focus",function(){if(!r){r=!0;try{t.execCommand("AutoUrlDetect",!1,!0)}catch(n){}}}):(t.on("keypress",function(e){return 41==e.keyCode?n(t):void 0}),void t.on("keyup",function(n){return 32==n.keyCode?e(t):void 0}))});tinymce.PluginManager.add("autoresize",function(e){function t(){return e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen()}function i(n){var s,r,g,u,l,m,h,d,f=tinymce.DOM;if(r=e.getDoc()){if(g=r.body,u=r.documentElement,l=o.autoresize_min_height,!g||n&&"setcontent"===n.type&&n.initial||t())return void(g&&u&&(g.style.overflowY="auto",u.style.overflowY="auto"));h=e.dom.getStyle(g,"margin-top",!0),d=e.dom.getStyle(g,"margin-bottom",!0),m=g.offsetHeight+parseInt(h,10)+parseInt(d,10),(isNaN(m)||0>=m)&&(m=tinymce.Env.ie?g.scrollHeight:tinymce.Env.webkit&&0===g.clientHeight?0:g.offsetHeight),m>o.autoresize_min_height&&(l=m),o.autoresize_max_height&&m>o.autoresize_max_height?(l=o.autoresize_max_height,g.style.overflowY="auto",u.style.overflowY="auto"):(g.style.overflowY="hidden",u.style.overflowY="hidden",g.scrollTop=0),l!==a&&(s=l-a,f.setStyle(f.get(e.id+"_ifr"),"height",l+"px"),a=l,tinymce.isWebKit&&0>s&&i(n))}}function n(e,t,o){setTimeout(function(){i({}),e--?n(e,t,o):o&&o()},t)}var o=e.settings,a=0;e.settings.inline||(o.autoresize_min_height=parseInt(e.getParam("autoresize_min_height",e.getElement().offsetHeight),10),o.autoresize_max_height=parseInt(e.getParam("autoresize_max_height",0),10),e.on("init",function(){var t=e.getParam("autoresize_overflow_padding",1);e.dom.setStyles(e.getBody(),{paddingBottom:e.getParam("autoresize_bottom_margin",50),paddingLeft:t,paddingRight:t})}),e.on("nodechange setcontent keyup FullscreenStateChanged",i),e.getParam("autoresize_on_init",!0)&&e.on("init",function(){n(20,100,function(){n(5,1e3)})}),e.addCommand("mceAutoResize",i))});tinymce.PluginManager.add("autosave",function(e){function t(e,t){var n={s:1e3,m:6e4};return e=/^(\d+)([ms]?)$/.exec(""+(e||t)),(e[2]?n[e[2]]:1)*parseInt(e,10)}function n(){var e=parseInt(l.getItem(d+"time"),10)||0;return(new Date).getTime()-e>v.autosave_retention?(a(!1),!1):!0}function a(t){l.removeItem(d+"draft"),l.removeItem(d+"time"),t!==!1&&e.fire("RemoveDraft")}function r(){!c()&&e.isDirty()&&(l.setItem(d+"draft",e.getContent({format:"raw",no_events:!0})),l.setItem(d+"time",(new Date).getTime()),e.fire("StoreDraft"))}function o(){n()&&(e.setContent(l.getItem(d+"draft"),{format:"raw"}),e.fire("RestoreDraft"))}function i(){m||(setInterval(function(){e.removed||r()},v.autosave_interval),m=!0)}function s(){var t=this;t.disabled(!n()),e.on("StoreDraft RestoreDraft RemoveDraft",function(){t.disabled(!n())}),i()}function u(){e.undoManager.beforeChange(),o(),a(),e.undoManager.add()}function f(){var e;return tinymce.each(tinymce.editors,function(t){t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&t.getParam("autosave_ask_before_unload",!0)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))}),e}function c(t){var n=e.settings.forced_root_block;return t=tinymce.trim("undefined"==typeof t?e.getBody().innerHTML:t),""===t||new RegExp("^<"+n+"[^>]*>(( | |[ ]|]*>)+?|)|
        $","i").test(t)}var d,m,v=e.settings,l=tinymce.util.LocalStorage;d=v.autosave_prefix||"tinymce-autosave-{path}{query}-{id}-",d=d.replace(/\{path\}/g,document.location.pathname),d=d.replace(/\{query\}/g,document.location.search),d=d.replace(/\{id\}/g,e.id),v.autosave_interval=t(v.autosave_interval,"30s"),v.autosave_retention=t(v.autosave_retention,"20m"),e.addButton("restoredraft",{title:"Restore last draft",onclick:u,onPostRender:s}),e.addMenuItem("restoredraft",{text:"Restore last draft",onclick:u,onPostRender:s,context:"file"}),e.settings.autosave_restore_when_empty!==!1&&(e.on("init",function(){n()&&c()&&o()}),e.on("saveContent",function(){a()})),window.onbeforeunload=f,this.hasDraft=n,this.storeDraft=r,this.restoreDraft=o,this.removeDraft=a,this.isEmpty=c});!function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(o){var e=this,t=o.getParam("bbcode_dialect","punbb").toLowerCase();o.on("beforeSetContent",function(o){o.content=e["_"+t+"_bbcode2html"](o.content)}),o.on("postProcess",function(o){o.set&&(o.content=e["_"+t+"_bbcode2html"](o.content)),o.get&&(o.content=e["_"+t+"_html2bbcode"](o.content))})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://www.tinymce.com",infourl:"http://www.tinymce.com/wiki.php/Plugin:bbcode"}},_punbb_html2bbcode:function(o){function e(e,t){o=o.replace(e,t)}return o=tinymce.trim(o),e(/(.*?)<\/a>/gi,"[url=$1]$2[/url]"),e(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]"),e(/(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]"),e(/(.*?)<\/span>/gi,"[color=$1]$2[/color]"),e(/(.*?)<\/font>/gi,"[color=$1]$2[/color]"),e(/(.*?)<\/span>/gi,"[size=$1]$2[/size]"),e(/(.*?)<\/font>/gi,"$1"),e(//gi,"[img]$1[/img]"),e(/(.*?)<\/span>/gi,"[code]$1[/code]"),e(/(.*?)<\/span>/gi,"[quote]$1[/quote]"),e(/(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]"),e(/(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]"),e(/(.*?)<\/em>/gi,"[code][i]$1[/i][/code]"),e(/(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]"),e(/(.*?)<\/u>/gi,"[code][u]$1[/u][/code]"),e(/(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]"),e(/<\/(strong|b)>/gi,"[/b]"),e(/<(strong|b)>/gi,"[b]"),e(/<\/(em|i)>/gi,"[/i]"),e(/<(em|i)>/gi,"[i]"),e(/<\/u>/gi,"[/u]"),e(/(.*?)<\/span>/gi,"[u]$1[/u]"),e(//gi,"[u]"),e(/]*>/gi,"[quote]"),e(/<\/blockquote>/gi,"[/quote]"),e(/
        /gi,"\n"),e(//gi,"\n"),e(/
        /gi,"\n"),e(/

        /gi,""),e(/<\/p>/gi,"\n"),e(/ |\u00a0/gi," "),e(/"/gi,'"'),e(/</gi,"<"),e(/>/gi,">"),e(/&/gi,"&"),o},_punbb_bbcode2html:function(o){function e(e,t){o=o.replace(e,t)}return o=tinymce.trim(o),e(/\n/gi,"
        "),e(/\[b\]/gi,""),e(/\[\/b\]/gi,""),e(/\[i\]/gi,""),e(/\[\/i\]/gi,""),e(/\[u\]/gi,""),e(/\[\/u\]/gi,""),e(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,'
        $2'),e(/\[url\](.*?)\[\/url\]/gi,'$1'),e(/\[img\](.*?)\[\/img\]/gi,''),e(/\[color=(.*?)\](.*?)\[\/color\]/gi,'$2'),e(/\[code\](.*?)\[\/code\]/gi,'$1 '),e(/\[quote.*?\](.*?)\[\/quote\]/gi,'$1 '),o}}),tinymce.PluginManager.add("bbcode",tinymce.plugins.BBCodePlugin)}();tinymce.PluginManager.add("charmap",function(e){function a(){function a(e){for(;e;){if("TD"==e.nodeName)return e;e=e.parentNode}}var t,r,o,n;t='';var l=25;for(o=0;10>o;o++){for(t+="",r=0;l>r;r++){var s=i[o*l+r];t+='"}t+=""}t+="";var c={type:"container",html:t,onclick:function(a){var i=a.target;"TD"==i.tagName&&(i=i.firstChild),"DIV"==i.tagName&&(e.execCommand("mceInsertContent",!1,i.firstChild.data),a.ctrlKey||n.close())},onmouseover:function(e){var i=a(e.target);i&&n.find("#preview").text(i.firstChild.firstChild.data)}};n=e.windowManager.open({title:"Special character",spacing:10,padding:10,items:[c,{type:"label",name:"preview",text:" ",style:"font-size: 40px; text-align: center",border:1,minWidth:100,minHeight:80}],buttons:[{text:"Close",onclick:function(){n.close()}}]})}var i=[["160","no-break space"],["38","ampersand"],["34","quotation mark"],["162","cent sign"],["8364","euro sign"],["163","pound sign"],["165","yen sign"],["169","copyright sign"],["174","registered sign"],["8482","trade mark sign"],["8240","per mille sign"],["181","micro sign"],["183","middle dot"],["8226","bullet"],["8230","three dot leader"],["8242","minutes / feet"],["8243","seconds / inches"],["167","section sign"],["182","paragraph sign"],["223","sharp s / ess-zed"],["8249","single left-pointing angle quotation mark"],["8250","single right-pointing angle quotation mark"],["171","left pointing guillemet"],["187","right pointing guillemet"],["8216","left single quotation mark"],["8217","right single quotation mark"],["8220","left double quotation mark"],["8221","right double quotation mark"],["8218","single low-9 quotation mark"],["8222","double low-9 quotation mark"],["60","less-than sign"],["62","greater-than sign"],["8804","less-than or equal to"],["8805","greater-than or equal to"],["8211","en dash"],["8212","em dash"],["175","macron"],["8254","overline"],["164","currency sign"],["166","broken bar"],["168","diaeresis"],["161","inverted exclamation mark"],["191","turned question mark"],["710","circumflex accent"],["732","small tilde"],["176","degree sign"],["8722","minus sign"],["177","plus-minus sign"],["247","division sign"],["8260","fraction slash"],["215","multiplication sign"],["185","superscript one"],["178","superscript two"],["179","superscript three"],["188","fraction one quarter"],["189","fraction one half"],["190","fraction three quarters"],["402","function / florin"],["8747","integral"],["8721","n-ary sumation"],["8734","infinity"],["8730","square root"],["8764","similar to"],["8773","approximately equal to"],["8776","almost equal to"],["8800","not equal to"],["8801","identical to"],["8712","element of"],["8713","not an element of"],["8715","contains as member"],["8719","n-ary product"],["8743","logical and"],["8744","logical or"],["172","not sign"],["8745","intersection"],["8746","union"],["8706","partial differential"],["8704","for all"],["8707","there exists"],["8709","diameter"],["8711","backward difference"],["8727","asterisk operator"],["8733","proportional to"],["8736","angle"],["180","acute accent"],["184","cedilla"],["170","feminine ordinal indicator"],["186","masculine ordinal indicator"],["8224","dagger"],["8225","double dagger"],["192","A - grave"],["193","A - acute"],["194","A - circumflex"],["195","A - tilde"],["196","A - diaeresis"],["197","A - ring above"],["198","ligature AE"],["199","C - cedilla"],["200","E - grave"],["201","E - acute"],["202","E - circumflex"],["203","E - diaeresis"],["204","I - grave"],["205","I - acute"],["206","I - circumflex"],["207","I - diaeresis"],["208","ETH"],["209","N - tilde"],["210","O - grave"],["211","O - acute"],["212","O - circumflex"],["213","O - tilde"],["214","O - diaeresis"],["216","O - slash"],["338","ligature OE"],["352","S - caron"],["217","U - grave"],["218","U - acute"],["219","U - circumflex"],["220","U - diaeresis"],["221","Y - acute"],["376","Y - diaeresis"],["222","THORN"],["224","a - grave"],["225","a - acute"],["226","a - circumflex"],["227","a - tilde"],["228","a - diaeresis"],["229","a - ring above"],["230","ligature ae"],["231","c - cedilla"],["232","e - grave"],["233","e - acute"],["234","e - circumflex"],["235","e - diaeresis"],["236","i - grave"],["237","i - acute"],["238","i - circumflex"],["239","i - diaeresis"],["240","eth"],["241","n - tilde"],["242","o - grave"],["243","o - acute"],["244","o - circumflex"],["245","o - tilde"],["246","o - diaeresis"],["248","o slash"],["339","ligature oe"],["353","s - caron"],["249","u - grave"],["250","u - acute"],["251","u - circumflex"],["252","u - diaeresis"],["253","y - acute"],["254","thorn"],["255","y - diaeresis"],["913","Alpha"],["914","Beta"],["915","Gamma"],["916","Delta"],["917","Epsilon"],["918","Zeta"],["919","Eta"],["920","Theta"],["921","Iota"],["922","Kappa"],["923","Lambda"],["924","Mu"],["925","Nu"],["926","Xi"],["927","Omicron"],["928","Pi"],["929","Rho"],["931","Sigma"],["932","Tau"],["933","Upsilon"],["934","Phi"],["935","Chi"],["936","Psi"],["937","Omega"],["945","alpha"],["946","beta"],["947","gamma"],["948","delta"],["949","epsilon"],["950","zeta"],["951","eta"],["952","theta"],["953","iota"],["954","kappa"],["955","lambda"],["956","mu"],["957","nu"],["958","xi"],["959","omicron"],["960","pi"],["961","rho"],["962","final sigma"],["963","sigma"],["964","tau"],["965","upsilon"],["966","phi"],["967","chi"],["968","psi"],["969","omega"],["8501","alef symbol"],["982","pi symbol"],["8476","real part symbol"],["978","upsilon - hook symbol"],["8472","Weierstrass p"],["8465","imaginary part"],["8592","leftwards arrow"],["8593","upwards arrow"],["8594","rightwards arrow"],["8595","downwards arrow"],["8596","left right arrow"],["8629","carriage return"],["8656","leftwards double arrow"],["8657","upwards double arrow"],["8658","rightwards double arrow"],["8659","downwards double arrow"],["8660","left right double arrow"],["8756","therefore"],["8834","subset of"],["8835","superset of"],["8836","not a subset of"],["8838","subset of or equal to"],["8839","superset of or equal to"],["8853","circled plus"],["8855","circled times"],["8869","perpendicular"],["8901","dot operator"],["8968","left ceiling"],["8969","right ceiling"],["8970","left floor"],["8971","right floor"],["9001","left-pointing angle bracket"],["9002","right-pointing angle bracket"],["9674","lozenge"],["9824","black spade suit"],["9827","black club suit"],["9829","black heart suit"],["9830","black diamond suit"],["8194","en space"],["8195","em space"],["8201","thin space"],["8204","zero width non-joiner"],["8205","zero width joiner"],["8206","left-to-right mark"],["8207","right-to-left mark"],["173","soft hyphen"]];e.addButton("charmap",{icon:"charmap",tooltip:"Special character",onclick:a}),e.addMenuItem("charmap",{icon:"charmap",text:"Special character",onclick:a,context:"insert"})});tinymce.PluginManager.add("code",function(e){function o(){e.windowManager.open({title:"Source code",body:{type:"textbox",name:"code",multiline:!0,minWidth:e.getParam("code_dialog_width",600),minHeight:e.getParam("code_dialog_height",Math.min(tinymce.DOM.getViewPort().h-200,500)),value:e.getContent({source_view:!0}),spellcheck:!1,style:"direction: ltr; text-align: left"},onSubmit:function(o){e.focus(),e.undoManager.transact(function(){e.setContent(o.data.code)}),e.selection.setCursorLocation(),e.nodeChanged()}})}e.addCommand("mceCodeEditor",o),e.addButton("code",{icon:"code",tooltip:"Source code",onclick:o}),e.addMenuItem("code",{icon:"code",text:"Source code",context:"tools",onclick:o})});tinymce.PluginManager.add("contextmenu",function(e){var n,t=e.settings.contextmenu_never_use_native;e.on("contextmenu",function(i){var o;if(!i.ctrlKey||t){if(i.preventDefault(),o=e.settings.contextmenu||"link image inserttable | cell row column deletetable",n)n.show();else{var c=[];tinymce.each(o.split(/[ ,]/),function(n){var t=e.menuItems[n];"|"==n&&(t={text:n}),t&&(t.shortcut="",c.push(t))});for(var a=0;a'}),t+=""}),t+=""}var i=[["cool","cry","embarassed","foot-in-mouth"],["frown","innocent","kiss","laughing"],["money-mouth","sealed","smile","surprised"],["tongue-out","undecided","wink","yell"]];t.addButton("emoticons",{type:"panelbutton",panel:{role:"application",autohide:!0,html:a,onclick:function(e){var a=t.dom.getParent(e.target,"a");a&&(t.insertContent(''+a.getAttribute('),this.hide())}},tooltip:"Emoticons"})});tinymce.PluginManager.add("fullpage",function(e){function t(){var t=n();e.windowManager.open({title:"Document properties",data:t,defaults:{type:"textbox",size:40},body:[{name:"title",label:"Title"},{name:"keywords",label:"Keywords"},{name:"description",label:"Description"},{name:"robots",label:"Robots"},{name:"author",label:"Author"},{name:"docencoding",label:"Encoding"}],onSubmit:function(e){l(tinymce.extend(t,e.data))}})}function n(){function t(e,t){var n=e.attr(t);return n||""}var n,l,a=i(),r={};return r.fontface=e.getParam("fullpage_default_fontface",""),r.fontsize=e.getParam("fullpage_default_fontsize",""),n=a.firstChild,7==n.type&&(r.xml_pi=!0,l=/encoding="([^"]+)"/.exec(n.value),l&&(r.docencoding=l[1])),n=a.getAll("#doctype")[0],n&&(r.doctype=""),n=a.getAll("title")[0],n&&n.firstChild&&(r.title=n.firstChild.value),s(a.getAll("meta"),function(e){var t,n=e.attr("name"),l=e.attr("http-equiv");n?r[n.toLowerCase()]=e.attr("content"):"Content-Type"==l&&(t=/charset\s*=\s*(.*)\s*/gi.exec(e.attr("content")),t&&(r.docencoding=t[1]))}),n=a.getAll("html")[0],n&&(r.langcode=t(n,"lang")||t(n,"xml:lang")),r.stylesheets=[],tinymce.each(a.getAll("link"),function(e){"stylesheet"==e.attr("rel")&&r.stylesheets.push(e.attr("href"))}),n=a.getAll("body")[0],n&&(r.langdir=t(n,"dir"),r.style=t(n,"style"),r.visited_color=t(n,"vlink"),r.link_color=t(n,"link"),r.active_color=t(n,"alink")),r}function l(t){function n(e,t,n){e.attr(t,n?n:void 0)}function l(e){r.firstChild?r.insert(e,r.firstChild):r.append(e)}var a,r,o,c,u,f=e.dom;a=i(),r=a.getAll("head")[0],r||(c=a.getAll("html")[0],r=new m("head",1),c.firstChild?c.insert(r,c.firstChild,!0):c.append(r)),c=a.firstChild,t.xml_pi?(u='version="1.0"',t.docencoding&&(u+=' encoding="'+t.docencoding+'"'),7!=c.type&&(c=new m("xml",7),a.insert(c,a.firstChild,!0)),c.value=u):c&&7==c.type&&c.remove(),c=a.getAll("#doctype")[0],t.doctype?(c||(c=new m("#doctype",10),t.xml_pi?a.insert(c,a.firstChild):l(c)),c.value=t.doctype.substring(9,t.doctype.length-1)):c&&c.remove(),c=null,s(a.getAll("meta"),function(e){"Content-Type"==e.attr("http-equiv")&&(c=e)}),t.docencoding?(c||(c=new m("meta",1),c.attr("http-equiv","Content-Type"),c.shortEnded=!0,l(c)),c.attr("content","text/html; charset="+t.docencoding)):c.remove(),c=a.getAll("title")[0],t.title?(c?c.empty():(c=new m("title",1),l(c)),c.append(new m("#text",3)).value=t.title):c&&c.remove(),s("keywords,description,author,copyright,robots".split(","),function(e){var n,i,r=a.getAll("meta"),o=t[e];for(n=0;n"))}function i(){return new tinymce.html.DomParser({validate:!1,root_name:"#document"}).parse(d)}function a(t){function n(e){return e.replace(/<\/?[A-Z]+/g,function(e){return e.toLowerCase()})}var l,a,o,m,u=t.content,f="",g=e.dom;if(!t.selection&&!("raw"==t.format&&d||t.source_view&&e.getParam("fullpage_hide_in_source_view"))){u=u.replace(/<(\/?)BODY/gi,"<$1body"),l=u.indexOf("",l),d=n(u.substring(0,l+1)),a=u.indexOf("\n"),o=i(),s(o.getAll("style"),function(e){e.firstChild&&(f+=e.firstChild.value)}),m=o.getAll("body")[0],m&&g.setAttribs(e.getBody(),{style:m.attr("style")||"",dir:m.attr("dir")||"",vLink:m.attr("vlink")||"",link:m.attr("link")||"",aLink:m.attr("alink")||""}),g.remove("fullpage_styles");var y=e.getDoc().getElementsByTagName("head")[0];f&&(g.add(y,"style",{id:"fullpage_styles"},f),m=g.get("fullpage_styles"),m.styleSheet&&(m.styleSheet.cssText=f));var h={};tinymce.each(y.getElementsByTagName("link"),function(e){"stylesheet"==e.rel&&e.getAttribute("data-mce-fullpage")&&(h[e.href]=e)}),tinymce.each(o.getAll("link"),function(e){var t=e.attr("href");h[t]||"stylesheet"!=e.attr("rel")||g.add(y,"link",{rel:"stylesheet",text:"text/css",href:t,"data-mce-fullpage":"1"}),delete h[t]}),tinymce.each(h,function(e){e.parentNode.removeChild(e)})}}function r(){var t,n="",l="";return e.getParam("fullpage_default_xml_pi")&&(n+='\n'),n+=e.getParam("fullpage_default_doctype",""),n+="\n\n\n",(t=e.getParam("fullpage_default_title"))&&(n+=""+t+"\n"),(t=e.getParam("fullpage_default_encoding"))&&(n+='\n'),(t=e.getParam("fullpage_default_font_family"))&&(l+="font-family: "+t+";"),(t=e.getParam("fullpage_default_font_size"))&&(l+="font-size: "+t+";"),(t=e.getParam("fullpage_default_text_color"))&&(l+="color: "+t+";"),n+="\n\n"}function o(t){t.selection||t.source_view&&e.getParam("fullpage_hide_in_source_view")||(t.content=tinymce.trim(d)+"\n"+tinymce.trim(t.content)+"\n"+tinymce.trim(c))}var d,c,s=tinymce.each,m=tinymce.html.Node;e.addCommand("mceFullPageProperties",t),e.addButton("fullpage",{title:"Document properties",cmd:"mceFullPageProperties"}),e.addMenuItem("fullpage",{text:"Document properties",cmd:"mceFullPageProperties",context:"file"}),e.on("BeforeSetContent",a),e.on("GetContent",o)});tinymce.PluginManager.add("fullscreen",function(e){function t(){var e,t,n=window,i=document,l=i.body;return l.offsetWidth&&(e=l.offsetWidth,t=l.offsetHeight),n.innerWidth&&n.innerHeight&&(e=n.innerWidth,t=n.innerHeight),{w:e,h:t}}function n(){function n(){d.setStyle(a,"height",t().h-(h.clientHeight-a.clientHeight))}var u,h,a,f,m=document.body,g=document.documentElement;s=!s,h=e.getContainer(),u=h.style,a=e.getContentAreaContainer().firstChild,f=a.style,s?(i=f.width,l=f.height,f.width=f.height="100%",c=u.width,o=u.height,u.width=u.height="",d.addClass(m,"mce-fullscreen"),d.addClass(g,"mce-fullscreen"),d.addClass(h,"mce-fullscreen"),d.bind(window,"resize",n),n(),r=n):(f.width=i,f.height=l,c&&(u.width=c),o&&(u.height=o),d.removeClass(m,"mce-fullscreen"),d.removeClass(g,"mce-fullscreen"),d.removeClass(h,"mce-fullscreen"),d.unbind(window,"resize",r)),e.fire("FullscreenStateChanged",{state:s})}var i,l,r,c,o,s=!1,d=tinymce.DOM;return e.settings.inline?void 0:(e.on("init",function(){e.addShortcut("Ctrl+Alt+F","",n)}),e.on("remove",function(){r&&d.unbind(window,"resize",r)}),e.addCommand("mceFullScreen",n),e.addMenuItem("fullscreen",{text:"Fullscreen",shortcut:"Ctrl+Alt+F",selectable:!0,onClick:n,onPostRender:function(){var t=this;e.on("FullscreenStateChanged",function(e){t.active(e.state)})},context:"view"}),e.addButton("fullscreen",{tooltip:"Fullscreen",shortcut:"Ctrl+Alt+F",onClick:n,onPostRender:function(){var t=this;e.on("FullscreenStateChanged",function(e){t.active(e.state)})}}),{isFullscreen:function(){return s}})});tinymce.PluginManager.add("hr",function(n){n.addCommand("InsertHorizontalRule",function(){n.execCommand("mceInsertContent",!1,"


        ")}),n.addButton("hr",{icon:"hr",tooltip:"Horizontal line",cmd:"InsertHorizontalRule"}),n.addMenuItem("hr",{icon:"hr",text:"Horizontal line",cmd:"InsertHorizontalRule",context:"insert"})});tinymce.PluginManager.add("image",function(e){function t(e,t){function i(e,i){n.parentNode&&n.parentNode.removeChild(n),t({width:e,height:i})}var n=document.createElement("img");n.onload=function(){i(n.clientWidth,n.clientHeight)},n.onerror=function(){i()};var a=n.style;a.visibility="hidden",a.position="fixed",a.bottom=a.left=0,a.width=a.height="auto",document.body.appendChild(n),n.src=e}function i(t){return tinymce.each(t,function(t){t.textStyle=function(){return e.formatter.getCssText({inline:"img",classes:[t.value]})}}),t}function n(t){return function(){var i=e.settings.image_list;"string"==typeof i?tinymce.util.XHR.send({url:i,success:function(e){t(tinymce.util.JSON.parse(e))}}):"function"==typeof i?i(t):t(i)}}function a(n){function a(t,i,n){var a,l=[];return tinymce.each(e.settings[t]||n,function(e){var t={text:e.text||e.title,value:e.value};l.push(t),(f[i]===e.value||!a&&e.selected)&&(a=t)}),a&&!f[i]&&(f[i]=a.value,a.selected=!0),l}function l(){var t=[{text:"None",value:""}];return tinymce.each(n,function(i){t.push({text:i.text||i.title,value:e.convertURL(i.value||i.url,"src"),menu:i.menu})}),t}function o(){var e,t,i,n;e=u.find("#width")[0],t=u.find("#height")[0],i=e.value(),n=t.value(),u.find("#constrain")[0].checked()&&g&&h&&i&&n&&(g!=i?(n=Math.round(i/g*n),t.value(n)):(i=Math.round(n/h*i),e.value(i))),g=i,h=n}function s(){function t(t){function i(){t.onload=t.onerror=null,e.selection.select(t),e.nodeChanged()}t.onload=function(){f.width||f.height||y.setAttribs(t,{width:t.clientWidth,height:t.clientHeight}),i()},t.onerror=i}d(),o(),f=tinymce.extend(f,u.toJSON()),f.alt||(f.alt=""),""===f.width&&(f.width=null),""===f.height&&(f.height=null),f.style||(f.style=null),f={src:f.src,alt:f.alt,width:f.width,height:f.height,style:f.style,"class":f["class"]},f["class"]||delete f["class"],e.undoManager.transact(function(){return f.src?(v?y.setAttribs(v,f):(f.id="__mcenew",e.focus(),e.selection.setContent(y.createHTML("img",f)),v=y.get("__mcenew"),y.setAttrib(v,"id",null)),void t(v)):void(v&&(y.remove(v),e.focus(),e.nodeChanged()))})}function r(e){return e&&(e=e.replace(/px$/,"")),e}function c(){m&&m.value(e.convertURL(this.value(),"src")),t(this.value(),function(e){e.width&&e.height&&(g=e.width,h=e.height,u.find("#width").value(g),u.find("#height").value(h))})}function d(){function t(e){return e.length>0&&/^[0-9]+$/.test(e)&&(e+="px"),e}if(e.settings.image_advtab){var i=u.toJSON(),n=y.parseStyle(i.style);delete n.margin,n["margin-top"]=n["margin-bottom"]=t(i.vspace),n["margin-left"]=n["margin-right"]=t(i.hspace),n["border-width"]=t(i.border),u.find("#style").value(y.serializeStyle(y.parseStyle(y.serializeStyle(n))))}}var u,g,h,m,p,f={},y=e.dom,v=e.selection.getNode();g=y.getAttrib(v,"width"),h=y.getAttrib(v,"height"),"IMG"!=v.nodeName||v.getAttribute("data-mce-object")||v.getAttribute("data-mce-placeholder")?v=null:f={src:y.getAttrib(v,"src"),alt:y.getAttrib(v,"alt"),"class":y.getAttrib(v,"class"),width:g,height:h},n&&(m={type:"listbox",label:"Image list",values:l(),value:f.src&&e.convertURL(f.src,"src"),onselect:function(e){var t=u.find("#alt");(!t.value()||e.lastControl&&t.value()==e.lastControl.text())&&t.value(e.control.text()),u.find("#src").value(e.control.value())},onPostRender:function(){m=this}}),e.settings.image_class_list&&(p={name:"class",type:"listbox",label:"Class",values:i(a("image_class_list","class"))});var b=[{name:"src",type:"filepicker",filetype:"image",label:"Source",autofocus:!0,onchange:c},m];e.settings.image_description!==!1&&b.push({name:"alt",type:"textbox",label:"Image description"}),e.settings.image_dimensions!==!1&&b.push({type:"container",label:"Dimensions",layout:"flex",direction:"row",align:"center",spacing:5,items:[{name:"width",type:"textbox",maxLength:5,size:3,onchange:o,ariaLabel:"Width"},{type:"label",text:"x"},{name:"height",type:"textbox",maxLength:5,size:3,onchange:o,ariaLabel:"Height"},{name:"constrain",type:"checkbox",checked:!0,text:"Constrain proportions"}]}),b.push(p),e.settings.image_advtab?(v&&(f.hspace=r(v.style.marginLeft||v.style.marginRight),f.vspace=r(v.style.marginTop||v.style.marginBottom),f.border=r(v.style.borderWidth),f.style=e.dom.serializeStyle(e.dom.parseStyle(e.dom.getAttrib(v,"style")))),u=e.windowManager.open({title:"Insert/edit image",data:f,bodyType:"tabpanel",body:[{title:"General",type:"form",items:b},{title:"Advanced",type:"form",pack:"start",items:[{label:"Style",name:"style",type:"textbox"},{type:"form",layout:"grid",packV:"start",columns:2,padding:0,alignH:["left","right"],defaults:{type:"textbox",maxWidth:50,onchange:d},items:[{label:"Vertical space",name:"vspace"},{label:"Horizontal space",name:"hspace"},{label:"Border",name:"border"}]}]}],onSubmit:s})):u=e.windowManager.open({title:"Insert/edit image",data:f,body:b,onSubmit:s})}e.addButton("image",{icon:"image",tooltip:"Insert/edit image",onclick:n(a),stateSelector:"img:not([data-mce-object],[data-mce-placeholder])"}),e.addMenuItem("image",{icon:"image",text:"Insert image",onclick:n(a),context:"insert",prependToContext:!0})});tinymce.PluginManager.add("importcss",function(t){function e(t){return"string"==typeof t?function(e){return-1!==e.indexOf(t)}:t instanceof RegExp?function(e){return t.test(e)}:t}function n(e,n){function i(t,e){var c,o=t.href;if(o&&n(o,e)){s(t.imports,function(t){i(t,!0)});try{c=t.cssRules||t.rules}catch(a){}s(c,function(t){t.styleSheet?i(t.styleSheet,!0):t.selectorText&&s(t.selectorText.split(","),function(t){r.push(tinymce.trim(t))})})}}var r=[],c={};s(t.contentCSS,function(t){c[t]=!0}),n||(n=function(t,e){return e||c[t]});try{s(e.styleSheets,function(t){i(t)})}catch(o){}return r}function i(e){var n,i=/^(?:([a-z0-9\-_]+))?(\.[a-z0-9_\-\.]+)$/i.exec(e);if(i){var r=i[1],s=i[2].substr(1).split(".").join(" "),c=tinymce.makeMap("a,img");return i[1]?(n={title:e},t.schema.getTextBlockElements()[r]?n.block=r:t.schema.getBlockElements()[r]||c[r.toLowerCase()]?n.selector=r:n.inline=r):i[2]&&(n={inline:"span",title:e.substr(1),classes:s}),t.settings.importcss_merge_classes!==!1?n.classes=s:n.attributes={"class":s},n}}var r=this,s=tinymce.each;t.on("renderFormatsMenu",function(c){var o=t.settings,a={},l=o.importcss_selector_converter||i,f=e(o.importcss_selector_filter),m=c.control;t.settings.importcss_append||m.items().remove();var u=[];tinymce.each(o.importcss_groups,function(t){t=tinymce.extend({},t),t.filter=e(t.filter),u.push(t)}),s(n(c.doc||t.getDoc(),e(o.importcss_file_filter)),function(e){if(-1===e.indexOf(".mce-")&&!a[e]&&(!f||f(e))){var n,i=l.call(r,e);if(i){var s=i.name||tinymce.DOM.uniqueId();if(u)for(var c=0;c'+n+"";var i=e.dom.getParent(e.selection.getStart(),"time");if(i)return void e.dom.setOuterHTML(i,n)}e.insertContent(n)}var n,r,i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),d="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),c="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),u=[];e.addCommand("mceInsertDate",function(){a(e.getParam("insertdatetime_dateformat",e.translate("%Y-%m-%d")))}),e.addCommand("mceInsertTime",function(){a(e.getParam("insertdatetime_timeformat",e.translate("%H:%M:%S")))}),e.addButton("insertdatetime",{type:"splitbutton",title:"Insert date/time",onclick:function(){a(n||r)},menu:u}),tinymce.each(e.settings.insertdatetime_formats||["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"],function(e){r||(r=e),u.push({text:t(e),onclick:function(){n=e,a(e)}})}),e.addMenuItem("insertdatetime",{icon:"date",text:"Insert date/time",menu:u,context:"insert"})});!function(e){e.on("AddEditor",function(e){e.editor.settings.inline_styles=!1}),e.PluginManager.add("legacyoutput",function(t){t.on("init",function(){var i="p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img",n=e.explode(t.settings.font_size_style_values),l=t.schema;t.formatter.register({alignleft:{selector:i,attributes:{align:"left"}},aligncenter:{selector:i,attributes:{align:"center"}},alignright:{selector:i,attributes:{align:"right"}},alignjustify:{selector:i,attributes:{align:"justify"}},bold:[{inline:"b",remove:"all"},{inline:"strong",remove:"all"},{inline:"span",styles:{fontWeight:"bold"}}],italic:[{inline:"i",remove:"all"},{inline:"em",remove:"all"},{inline:"span",styles:{fontStyle:"italic"}}],underline:[{inline:"u",remove:"all"},{inline:"span",styles:{textDecoration:"underline"},exact:!0}],strikethrough:[{inline:"strike",remove:"all"},{inline:"span",styles:{textDecoration:"line-through"},exact:!0}],fontname:{inline:"font",attributes:{face:"%value"}},fontsize:{inline:"font",attributes:{size:function(t){return e.inArray(n,t.value)+1}}},forecolor:{inline:"font",attributes:{color:"%value"}},hilitecolor:{inline:"font",styles:{backgroundColor:"%value"}}}),e.each("b,i,u,strike".split(","),function(e){l.addValidElements(e+"[*]")}),l.getElementRule("font")||l.addValidElements("font[face|size|color|style]"),e.each(i.split(","),function(e){var t=l.getElementRule(e);t&&(t.attributes.align||(t.attributes.align={},t.attributesOrder.push("align")))})})})}(tinymce);tinymce.PluginManager.add("link",function(t){function e(e){return function(){var n=t.settings.link_list;"string"==typeof n?tinymce.util.XHR.send({url:n,success:function(t){e(tinymce.util.JSON.parse(t))}}):"function"==typeof n?n(e):e(n)}}function n(e){function n(t){var e=d.find("#text");(!e.value()||t.lastControl&&e.value()==t.lastControl.text())&&e.value(t.control.text()),d.find("#href").value(t.control.value())}function l(){var n=[{text:"None",value:""}];return tinymce.each(e,function(e){n.push({text:e.text||e.title,value:t.convertURL(e.value||e.url,"href"),menu:e.menu})}),n}function i(e){return tinymce.each(e,function(e){e.textStyle=function(){return t.formatter.getCssText({inline:"a",classes:[e.value]})}}),e}function a(e,n,l){var i,a=[];return tinymce.each(t.settings[e]||l,function(t){var e={text:t.text||t.title,value:t.value};a.push(e),(b[n]===t.value||!i&&t.selected)&&(i=e)}),i&&!b[n]&&(b[n]=i.value,i.selected=!0),a}function r(e){var l=[];return tinymce.each(t.dom.select("a:not([href])"),function(t){var n=t.name||t.id;n&&l.push({text:n,value:"#"+n,selected:-1!=e.indexOf("#"+n)})}),l.length?(l.unshift({text:"None",value:""}),{name:"anchor",type:"listbox",label:"Anchors",values:l,onselect:n}):void 0}function o(){h&&h.value(t.convertURL(this.value(),"href")),!f&&0===b.text.length&&x&&this.parent().parent().find("#text")[0].value(this.value())}function s(t){var e=k.getContent();if(/]+>[^<]+<\/a>$/.test(e)||-1==e.indexOf("href=")))return!1;if(t){var n,l=t.childNodes;if(0===l.length)return!1;for(n=l.length-1;n>=0;n--)if(3!=l[n].nodeType)return!1}return!0}var u,c,f,d,x,v,h,g,m,p,y,b={},k=t.selection,w=t.dom;u=k.getNode(),c=w.getParent(u,"a[href]"),x=s(),b.text=f=c?c.innerText||c.textContent:k.getContent({format:"text"}),b.href=c?w.getAttrib(c,"href"):"",b.target=c?w.getAttrib(c,"target"):t.settings.default_link_target||null,b.rel=c?w.getAttrib(c,"rel"):null,b["class"]=c?w.getAttrib(c,"class"):null,b.title=c?w.getAttrib(c,"title"):"",x&&(v={name:"text",type:"textbox",size:40,label:"Text to display",onchange:function(){b.text=this.value()}}),e&&(h={type:"listbox",label:"Link list",values:l(),onselect:n,value:t.convertURL(b.href,"href"),onPostRender:function(){h=this}}),t.settings.target_list!==!1&&(m={name:"target",type:"listbox",label:"Target",values:a("target_list","target",[{text:"None",value:""},{text:"New window",value:"_blank"}])}),t.settings.rel_list&&(g={name:"rel",type:"listbox",label:"Rel",values:a("rel_list","rel",[{text:"None",value:""}])}),t.settings.link_class_list&&(p={name:"class",type:"listbox",label:"Class",values:i(a("link_class_list","class"))}),t.settings.link_title!==!1&&(y={name:"title",type:"textbox",label:"Title",value:b.title}),d=t.windowManager.open({title:"Insert link",data:b,body:[{name:"href",type:"filepicker",filetype:"file",size:40,autofocus:!0,label:"Url",onchange:o,onkeyup:o},v,y,r(b.href),h,g,m,p],onSubmit:function(e){function n(e,n){var l=t.selection.getRng();window.setTimeout(function(){t.windowManager.confirm(e,function(e){t.selection.setRng(l),n(e)})},0)}function l(){var e={href:i,target:b.target?b.target:null,rel:b.rel?b.rel:null,"class":b["class"]?b["class"]:null,title:b.title?b.title:null};c?(t.focus(),x&&b.text!=f&&("innerText"in c?c.innerText=b.text:c.textContent=b.text),w.setAttribs(c,e),k.select(c),t.undoManager.add()):x?t.insertContent(w.createHTML("a",e,w.encode(b.text))):t.execCommand("mceInsertLink",!1,e)}var i;return b=tinymce.extend(b,e.data),(i=b.href)?i.indexOf("@")>0&&-1==i.indexOf("//")&&-1==i.indexOf("mailto:")?void n("The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",function(t){t&&(i="mailto:"+i),l()}):/^\s*www\./i.test(i)?void n("The URL you entered seems to be an external link. Do you want to add the required http:// prefix?",function(t){t&&(i="http://"+i),l()}):void l():void t.execCommand("unlink")}})}t.addButton("link",{icon:"link",tooltip:"Insert/edit link",shortcut:"Ctrl+K",onclick:e(n),stateSelector:"a[href]"}),t.addButton("unlink",{icon:"unlink",tooltip:"Remove link",cmd:"unlink",stateSelector:"a[href]"}),t.addShortcut("Ctrl+K","",e(n)),this.showDialog=n,t.addMenuItem("link",{icon:"link",text:"Insert link",shortcut:"Ctrl+K",onclick:e(n),stateSelector:"a[href]",context:"insert",prependToContext:!0})});tinymce.PluginManager.add("lists",function(e){function t(e){return e&&/^(OL|UL)$/.test(e.nodeName)}function n(e){return e.parentNode.firstChild==e}function r(e){return e.parentNode.lastChild==e}function o(t){return t&&!!e.schema.getTextBlockElements()[t.nodeName]}function i(e){return e&&"SPAN"===e.nodeName&&"bookmark"===e.getAttribute("data-mce-type")}var a=this;e.on("init",function(){function d(e){function t(t){var r,o,i;o=e[t?"startContainer":"endContainer"],i=e[t?"startOffset":"endOffset"],1==o.nodeType&&(r=b.create("span",{"data-mce-type":"bookmark"}),o.hasChildNodes()?(i=Math.min(i,o.childNodes.length-1),t?o.insertBefore(r,o.childNodes[i]):b.insertAfter(r,o.childNodes[i])):o.appendChild(r),o=r,i=0),n[t?"startContainer":"endContainer"]=o,n[t?"startOffset":"endOffset"]=i}var n={};return t(!0),e.collapsed||t(),n}function s(e){function t(t){function n(e){for(var t=e.parentNode.firstChild,n=0;t;){if(t==e)return n;(1!=t.nodeType||"bookmark"!=t.getAttribute("data-mce-type"))&&n++,t=t.nextSibling}return-1}var r,o,i;r=i=e[t?"startContainer":"endContainer"],o=e[t?"startOffset":"endOffset"],r&&(1==r.nodeType&&(o=n(r),r=r.parentNode,b.remove(i)),e[t?"startContainer":"endContainer"]=r,e[t?"startOffset":"endOffset"]=o)}t(!0),t();var n=b.createRng();n.setStart(e.startContainer,e.startOffset),e.endContainer&&n.setEnd(e.endContainer,e.endOffset),k.setRng(n)}function f(t,n){var r,o,i,a=b.createFragment(),d=e.schema.getBlockElements();if(e.settings.forced_root_block&&(n=n||e.settings.forced_root_block),n&&(o=b.create(n),o.tagName===e.settings.forced_root_block&&b.setAttribs(o,e.settings.forced_root_block_attrs),a.appendChild(o)),t)for(;r=t.firstChild;){var s=r.nodeName;i||"SPAN"==s&&"bookmark"==r.getAttribute("data-mce-type")||(i=!0),d[s]?(a.appendChild(r),o=null):n?(o||(o=b.create(n),a.appendChild(o)),o.appendChild(r)):a.appendChild(r)}return e.settings.forced_root_block?i||tinymce.Env.ie&&!(tinymce.Env.ie>10)||o.appendChild(b.create("br",{"data-mce-bogus":"1"})):a.appendChild(b.create("br")),a}function l(){return tinymce.grep(k.getSelectedBlocks(),function(e){return"LI"==e.nodeName})}function c(e,t,n){var r,o,i=b.select('span[data-mce-type="bookmark"]',e);n=n||f(t),r=b.createRng(),r.setStartAfter(t),r.setEndAfter(e),o=r.extractContents(),b.isEmpty(o)||b.insertAfter(o,e),b.insertAfter(n,e),b.isEmpty(t.parentNode)&&(tinymce.each(i,function(e){t.parentNode.parentNode.insertBefore(e,t.parentNode)}),b.remove(t.parentNode)),b.remove(t)}function p(e){var n,r;if(n=e.nextSibling,n&&t(n)&&n.nodeName==e.nodeName){for(;r=n.firstChild;)e.appendChild(r);b.remove(n)}if(n=e.previousSibling,n&&t(n)&&n.nodeName==e.nodeName){for(;r=n.firstChild;)e.insertBefore(r,e.firstChild);b.remove(n)}}function u(e){tinymce.each(tinymce.grep(b.select("ol,ul",e)),function(e){var n,r=e.parentNode;"LI"==r.nodeName&&r.firstChild==e&&(n=r.previousSibling,n&&"LI"==n.nodeName&&(n.appendChild(e),b.isEmpty(r)&&b.remove(r))),t(r)&&(n=r.previousSibling,n&&"LI"==n.nodeName&&n.appendChild(e))})}function m(e){function o(e){b.isEmpty(e)&&b.remove(e)}var i,a=e.parentNode,d=a.parentNode;return n(e)&&r(e)?("LI"==d.nodeName?(b.insertAfter(e,d),o(d),b.remove(a)):t(d)?b.remove(a,!0):(d.insertBefore(f(e),a),b.remove(a)),!0):n(e)?("LI"==d.nodeName?(b.insertAfter(e,d),e.appendChild(a),o(d)):t(d)?d.insertBefore(e,a):(d.insertBefore(f(e),a),b.remove(e)),!0):r(e)?("LI"==d.nodeName?b.insertAfter(e,d):t(d)?b.insertAfter(e,a):(b.insertAfter(f(e),a),b.remove(e)),!0):("LI"==d.nodeName?(a=d,i=f(e,"LI")):i=t(d)?f(e,"LI"):f(e),c(a,e,i),u(a.parentNode),!0)}function h(e){function n(n,r){var o;if(t(n)){for(;o=e.lastChild.firstChild;)r.appendChild(o);b.remove(n)}}var r,o;return r=e.previousSibling,r&&t(r)?(r.appendChild(e),!0):r&&"LI"==r.nodeName&&t(r.lastChild)?(r.lastChild.appendChild(e),n(e.lastChild,r.lastChild),!0):(r=e.nextSibling,r&&t(r)?(r.insertBefore(e,r.firstChild),!0):r&&"LI"==r.nodeName&&t(e.lastChild)?!1:(r=e.previousSibling,r&&"LI"==r.nodeName?(o=b.create(e.parentNode.nodeName),r.appendChild(o),o.appendChild(e),n(e.lastChild,o),!0):!1))}function v(){var t=l();if(t.length){for(var n=d(k.getRng(!0)),r=0;r0))return n;for(var o=new tinymce.dom.TreeWalker(e.startContainer);n=o[t?"next":"prev"]();)if(3==n.nodeType&&n.data.length>0)return n}function r(e,n){var r,o,i=e.parentNode;for(t(n.lastChild)&&(o=n.lastChild),r=n.lastChild,r&&"BR"==r.nodeName&&e.hasChildNodes()&&b.remove(r);r=e.firstChild;)n.appendChild(r);o&&n.appendChild(o),b.remove(e),b.isEmpty(i)&&b.remove(i)}if(k.isCollapsed()){var o=b.getParent(k.getStart(),"LI");if(o){var i=k.getRng(!0),a=b.getParent(n(i,e),"LI");if(a&&a!=o){var f=d(i);return e?r(a,o):r(o,a),s(f),!0}if(!a&&!e&&N(o.parentNode.nodeName))return!0}}},e.addCommand("Indent",function(){return v()?void 0:!0}),e.addCommand("Outdent",function(){return C()?void 0:!0}),e.addCommand("InsertUnorderedList",function(){y("UL")}),e.addCommand("InsertOrderedList",function(){y("OL")}),e.on("keydown",function(t){9==t.keyCode&&e.dom.getParent(e.selection.getStart(),"LI")&&(t.preventDefault(),t.shiftKey?C():v())})}),e.addButton("indent",{icon:"indent",title:"Increase indent",cmd:"Indent",onPostRender:function(){var t=this;e.on("nodechange",function(){for(var r=e.selection.getSelectedBlocks(),o=!1,i=0,a=r.length;!o&&a>i;i++){var d=r[i].nodeName;o="LI"==d&&n(r[i])||"UL"==d||"OL"==d}t.disabled(o)})}}),e.on("keydown",function(e){e.keyCode==tinymce.util.VK.BACKSPACE?a.backspaceDelete()&&e.preventDefault():e.keyCode==tinymce.util.VK.DELETE&&a.backspaceDelete(!0)&&e.preventDefault()})});tinymce.PluginManager.add("media",function(e,t){function i(e){return-1!=e.indexOf(".mp3")?"audio/mpeg":-1!=e.indexOf(".wav")?"audio/wav":-1!=e.indexOf(".mp4")?"video/mp4":-1!=e.indexOf(".webm")?"video/webm":-1!=e.indexOf(".ogg")?"video/ogg":-1!=e.indexOf(".swf")?"application/x-shockwave-flash":""}function r(t){var i=e.settings.media_scripts;if(i)for(var r=0;r':"application/x-shockwave-flash"==o.source1mime?(a+='',o.poster&&(a+=''),a+=""):-1!=o.source1mime.indexOf("audio")?e.settings.audio_template_callback?a=e.settings.audio_template_callback(o):a+='":"script"==o.type?a+='':a=e.settings.video_template_callback?e.settings.video_template_callback(o):'"}return a}function s(e){var t={};return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!0,special:"script,noscript",start:function(e,i){if(t.source1||"param"!=e||(t.source1=i.map.movie),("iframe"==e||"object"==e||"embed"==e||"video"==e||"audio"==e)&&(t.type||(t.type=e),t=tinymce.extend(i.map,t)),"script"==e){var o=r(i.map.src);if(!o)return;t={type:"script",source1:i.map.src,width:o.width,height:o.height}}"source"==e&&(t.source1?t.source2||(t.source2=i.map.src):t.source1=i.map.src),"img"!=e||t.poster||(t.poster=i.map.src)}}).parse(e),t.source1=t.source1||t.src||t.data,t.source2=t.source2||"",t.poster=t.poster||"",t}function n(t){return t.getAttribute("data-mce-object")?s(e.serializer.serialize(t,{selection:!0})):{}}function m(e,t,i){function r(e,t){var i,r,o,a;for(i in t)if(o=""+t[i],e.map[i])for(r=e.length;r--;)a=e[r],a.name==i&&(o?(e.map[i]=o,a.value=o):(delete e.map[i],e.splice(r,1)));else o&&(e.push({name:i,value:o}),e.map[i]=o)}var o,a=new tinymce.html.Writer,c=0;return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!0,special:"script,noscript",comment:function(e){a.comment(e)},cdata:function(e){a.cdata(e)},text:function(e,t){a.text(e,t)},start:function(e,s,n){switch(e){case"video":case"object":case"embed":case"img":case"iframe":r(s,{width:t.width,height:t.height})}if(i)switch(e){case"video":r(s,{poster:t.poster,src:""}),t.source2&&r(s,{src:""});break;case"iframe":r(s,{src:t.source1});break;case"source":if(c++,2>=c&&(r(s,{src:t["source"+c],type:t["source"+c+"mime"]}),!t["source"+c]))return;break;case"img":if(!t.poster)return;o=!0}a.start(e,s,n)},end:function(e){if("video"==e&&i)for(var s=1;2>=s;s++)if(t["source"+s]){var n=[];n.map={},s>c&&(r(n,{src:t["source"+s],type:t["source"+s+"mime"]}),a.start("source",n,!0))}if(t.poster&&"object"==e&&i&&!o){var m=[];m.map={},r(m,{src:t.poster,width:t.width,height:t.height}),a.start("img",m,!0)}a.end(e)}},new tinymce.html.Schema({})).parse(e),a.getContent()}var u=[{regex:/youtu\.be\/([\w\-.]+)/,type:"iframe",w:425,h:350,url:"//www.youtube.com/embed/$1"},{regex:/youtube\.com(.+)v=([^&]+)/,type:"iframe",w:425,h:350,url:"//www.youtube.com/embed/$2"},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"//player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc"},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'//maps.google.com/maps/ms?msid=$2&output=embed"'}];e.on("ResolveName",function(e){var t;1==e.target.nodeType&&(t=e.target.getAttribute("data-mce-object"))&&(e.name=t)}),e.on("preInit",function(){var t=e.schema.getSpecialElements();tinymce.each("video audio iframe object".split(" "),function(e){t[e]=new RegExp("]*>","gi")});var i=e.schema.getBoolAttrs();tinymce.each("webkitallowfullscreen mozallowfullscreen allowfullscreen".split(" "),function(e){i[e]={}}),e.parser.addNodeFilter("iframe,video,audio,object,embed,script",function(t,i){for(var o,a,c,s,n,m,u,d,l=t.length;l--;)if(a=t[l],a.parent&&("script"!=a.name||(d=r(a.attr("src"))))){for(c=new tinymce.html.Node("img",1),c.shortEnded=!0,d&&(d.width&&a.attr("width",d.width.toString()),d.height&&a.attr("height",d.height.toString())),m=a.attributes,o=m.length;o--;)s=m[o].name,n=m[o].value,"width"!==s&&"height"!==s&&"style"!==s&&(("data"==s||"src"==s)&&(n=e.convertURL(n,s)),c.attr("data-mce-p-"+s,n));u=a.firstChild&&a.firstChild.value,u&&(c.attr("data-mce-html",escape(u)),c.firstChild=null),c.attr({width:a.attr("width")||"300",height:a.attr("height")||("audio"==i?"30":"150"),style:a.attr("style"),src:tinymce.Env.transparentSrc,"data-mce-object":i,"class":"mce-object mce-object-"+i}),a.replace(c)}}),e.serializer.addAttributeFilter("data-mce-object",function(e,t){for(var i,r,o,a,c,s,n,m=e.length;m--;)if(i=e[m],i.parent){for(n=i.attr(t),r=new tinymce.html.Node(n,1),"audio"!=n&&"script"!=n&&r.attr({width:i.attr("width"),height:i.attr("height")}),r.attr({style:i.attr("style")}),a=i.attributes,o=a.length;o--;){var u=a[o].name;0===u.indexOf("data-mce-p-")&&r.attr(u.substr(11),a[o].value)}"script"==n&&r.attr("type","text/javascript"),c=i.attr("data-mce-html"),c&&(s=new tinymce.html.Node("#text",3),s.raw=!0,s.value=unescape(c),r.append(s)),i.replace(r)}})}),e.on("ObjectSelected",function(e){var t=e.target.getAttribute("data-mce-object");("audio"==t||"script"==t)&&e.preventDefault()}),e.on("objectResized",function(e){var t,i=e.target;i.getAttribute("data-mce-object")&&(t=i.getAttribute("data-mce-html"),t&&(t=unescape(t),i.setAttribute("data-mce-html",escape(m(t,{width:e.width,height:e.height})))))}),e.addButton("media",{tooltip:"Insert/edit video",onclick:o,stateSelector:["img[data-mce-object=video]","img[data-mce-object=iframe]"]}),e.addMenuItem("media",{icon:"media",text:"Insert video",onclick:o,context:"insert",prependToContext:!0})});tinymce.PluginManager.add("nonbreaking",function(n){var e=n.getParam("nonbreaking_force_tab");if(n.addCommand("mceNonBreaking",function(){n.insertContent(n.plugins.visualchars&&n.plugins.visualchars.state?' ':" "),n.dom.setAttrib(n.dom.select("span.mce-nbsp"),"data-mce-bogus","1")}),n.addButton("nonbreaking",{title:"Insert nonbreaking space",cmd:"mceNonBreaking"}),n.addMenuItem("nonbreaking",{text:"Nonbreaking space",cmd:"mceNonBreaking",context:"insert"}),e){var a=+e>1?+e:3;n.on("keydown",function(e){if(9==e.keyCode){if(e.shiftKey)return;e.preventDefault();for(var t=0;a>t;t++)n.execCommand("mceNonBreaking")}})}});tinymce.PluginManager.add("noneditable",function(e){function t(e){var t;if(1===e.nodeType){if(t=e.getAttribute(u),t&&"inherit"!==t)return t;if(t=e.contentEditable,"inherit"!==t)return t}return null}function n(e){for(var n;e;){if(n=t(e))return"false"===n?e:null;e=e.parentNode}}function r(){function r(e){for(;e;){if(e.id===g)return e;e=e.parentNode}}function a(e){var t;if(e)for(t=new f(e,e),e=t.current();e;e=t.next())if(3===e.nodeType)return e}function i(n,r){var a,i;return"false"===t(n)&&u.isBlock(n)?void s.select(n):(i=u.createRng(),"true"===t(n)&&(n.firstChild||n.appendChild(e.getDoc().createTextNode(" ")),n=n.firstChild,r=!0),a=u.create("span",{id:g,"data-mce-bogus":!0},m),r?n.parentNode.insertBefore(a,n):u.insertAfter(a,n),i.setStart(a.firstChild,1),i.collapse(!0),s.setRng(i),a)}function o(e){var t,n,i,o;if(e)t=s.getRng(!0),t.setStartBefore(e),t.setEndBefore(e),n=a(e),n&&n.nodeValue.charAt(0)==m&&(n=n.deleteData(0,1)),u.remove(e,!0),s.setRng(t);else for(i=r(s.getStart());(e=u.get(g))&&e!==o;)i!==e&&(n=a(e),n&&n.nodeValue.charAt(0)==m&&(n=n.deleteData(0,1)),u.remove(e,!0)),o=e}function l(){function e(e,n){var r,a,i,o,l;if(r=d.startContainer,a=d.startOffset,3==r.nodeType){if(l=r.nodeValue.length,a>0&&l>a||(n?a==l:0===a))return}else{if(!(a0?a-1:a;r=r.childNodes[u],r.hasChildNodes()&&(r=r.firstChild)}for(i=new f(r,e);o=i[n?"prev":"next"]();){if(3===o.nodeType&&o.nodeValue.length>0)return;if("true"===t(o))return o}return e}var r,a,l,d,u;o(),l=s.isCollapsed(),r=n(s.getStart()),a=n(s.getEnd()),(r||a)&&(d=s.getRng(!0),l?(r=r||a,(u=e(r,!0))?i(u,!0):(u=e(r,!1))?i(u,!1):s.select(r)):(d=s.getRng(!0),r&&d.setStartBefore(r),a&&d.setEndAfter(a),s.setRng(d)))}function d(a){function i(e,t){for(;e=e[t?"previousSibling":"nextSibling"];)if(3!==e.nodeType||e.nodeValue.length>0)return e}function d(e,t){s.select(e),s.collapse(t)}function g(a){function i(e){for(var t=d;t;){if(t===e)return;t=t.parentNode}u.remove(e),l()}function o(){var r,o,l=e.schema.getNonEmptyElements();for(o=new tinymce.dom.TreeWalker(d,e.getBody());(r=a?o.prev():o.next())&&!l[r.nodeName.toLowerCase()]&&!(3===r.nodeType&&tinymce.trim(r.nodeValue).length>0);)if("false"===t(r))return i(r),!0;return n(r)?!0:!1}var f,d,c,g;if(s.isCollapsed()){if(f=s.getRng(!0),d=f.startContainer,c=f.startOffset,d=r(d)||d,g=n(d))return i(g),!1;if(3==d.nodeType&&(a?c>0:ch||h>124)&&h!=c.DELETE&&h!=c.BACKSPACE){if((tinymce.isMac?a.metaKey:a.ctrlKey)&&(67==h||88==h||86==h))return;if(a.preventDefault(),h==c.LEFT||h==c.RIGHT){var y=h==c.LEFT;if(e.dom.isBlock(m)){var T=y?m.previousSibling:m.nextSibling,C=new f(T,T),b=y?C.prev():C.next();d(b,!y)}else d(m,y)}}else if(h==c.LEFT||h==c.RIGHT||h==c.BACKSPACE||h==c.DELETE){if(p=r(v)){if(h==c.LEFT||h==c.BACKSPACE)if(m=i(p,!0),m&&"false"===t(m)){if(a.preventDefault(),h!=c.LEFT)return void u.remove(m);d(m,!0)}else o(p);if(h==c.RIGHT||h==c.DELETE)if(m=i(p),m&&"false"===t(m)){if(a.preventDefault(),h!=c.RIGHT)return void u.remove(m);d(m,!1)}else o(p)}if((h==c.BACKSPACE||h==c.DELETE)&&!g(h==c.BACKSPACE))return a.preventDefault(),!1}}var u=e.dom,s=e.selection,g="mce_noneditablecaret",m="";e.on("mousedown",function(n){var r=e.selection.getNode();"false"===t(r)&&r==n.target&&l()}),e.on("mouseup keyup",l),e.on("keydown",d)}function a(t){var n=l.length,r=t.content,a=tinymce.trim(o);if("raw"!=t.format){for(;n--;)r=r.replace(l[n],function(t){var n=arguments,i=n[n.length-2];return i>0&&'"'==r.charAt(i-1)?t:''+e.dom.encode("string"==typeof n[1]?n[1]:n[0])+""});t.content=r}}var i,o,l,f=tinymce.dom.TreeWalker,d="contenteditable",u="data-mce-"+d,c=tinymce.util.VK;i=" "+tinymce.trim(e.getParam("noneditable_editable_class","mceEditable"))+" ",o=" "+tinymce.trim(e.getParam("noneditable_noneditable_class","mceNonEditable"))+" ",l=e.getParam("noneditable_regexp"),l&&!l.length&&(l=[l]),e.on("PreInit",function(){r(),l&&e.on("BeforeSetContent",a),e.parser.addAttributeFilter("class",function(e){for(var t,n,r=e.length;r--;)n=e[r],t=" "+n.attr("class")+" ",-1!==t.indexOf(i)?n.attr(u,"true"):-1!==t.indexOf(o)&&n.attr(u,"false")}),e.serializer.addAttributeFilter(u,function(e){for(var t,n=e.length;n--;)t=e[n],l&&t.attr("data-mce-content")?(t.name="#text",t.type=3,t.raw=!0,t.value=t.attr("data-mce-content")):(t.attr(d,null),t.attr(u,null))}),e.parser.addAttributeFilter(d,function(e){for(var t,n=e.length;n--;)t=e[n],t.attr(u,t.attr(d)),t.attr(d,null)})}),e.on("drop",function(e){n(e.target)&&e.preventDefault()})});tinymce.PluginManager.add("pagebreak",function(e){var a="mce-pagebreak",t=e.getParam("pagebreak_separator",""),n=new RegExp(t.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi"),r='';e.addCommand("mcePageBreak",function(){e.insertContent(e.settings.pagebreak_split_block?"

        "+r+"

        ":r)}),e.addButton("pagebreak",{title:"Page break",cmd:"mcePageBreak"}),e.addMenuItem("pagebreak",{text:"Page break",icon:"pagebreak",cmd:"mcePageBreak",context:"insert"}),e.on("ResolveName",function(t){"IMG"==t.target.nodeName&&e.dom.hasClass(t.target,a)&&(t.name="pagebreak")}),e.on("click",function(t){t=t.target,"IMG"===t.nodeName&&e.dom.hasClass(t,a)&&e.selection.select(t)}),e.on("BeforeSetContent",function(e){e.content=e.content.replace(n,r)}),e.on("PreInit",function(){e.serializer.addNodeFilter("img",function(a){for(var n,r,c=a.length;c--;)if(n=a[c],r=n.attr("class"),r&&-1!==r.indexOf("mce-pagebreak")){var o=n.parent;if(e.schema.getBlockElements()[o.name]&&e.settings.pagebreak_split_block){o.type=3,o.value=t,o.raw=!0,n.remove();continue}n.type=3,n.value=t,n.raw=!0}})})});!function(e,t){"use strict";function n(e,t){for(var n,i=[],r=0;r"),t&&/^(PRE|DIV)$/.test(t.nodeName)||!o?e=n.filter(e,[[/\n/g,"
        "]]):(e=n.filter(e,[[/\n\n/g,"

        "+a],[/^(.*<\/p>)(

        )$/,a+"$1"],[/\n/g,"
        "]]),-1!=e.indexOf("

        ")&&(e=a+e)),r(e)}function a(){var t=i.dom,n=i.getBody(),r=i.dom.getViewPort(i.getWin()),o=r.y,a=20,s;if(v=i.selection.getRng(),i.inline&&(s=i.selection.getScrollContainer(),s&&s.scrollTop>0&&(o=s.scrollTop)),v.getClientRects){var l=v.getClientRects();if(l.length)a=o+(l[0].top-t.getPos(n).y);else{a=o;var c=v.startContainer;c&&(3==c.nodeType&&c.parentNode!=n&&(c=c.parentNode),1==c.nodeType&&(a=t.getPos(c,s||n).y))}}h=t.add(i.getBody(),"div",{id:"mcepastebin",contentEditable:!0,"data-mce-bogus":"1",style:"position: absolute; top: "+a+"px;width: 10px; height: 10px; overflow: hidden; opacity: 0"},y),(e.ie||e.gecko)&&t.setStyle(h,"left","rtl"==t.getStyle(n,"direction",!0)?65535:-65535),t.bind(h,"beforedeactivate focusin focusout",function(e){e.stopPropagation()}),h.focus(),i.selection.select(h,!0)}function s(){if(h){for(var e;e=i.dom.get("mcepastebin");)i.dom.remove(e),i.dom.unbind(e);v&&i.selection.setRng(v)}x=!1,h=v=null}function l(){var e=y,t,n;for(t=i.dom.select("div[id=mcepastebin]"),n=t.length;n--;){var r=t[n].innerHTML;e==y&&(e=""),r.length>e.length&&(e=r)}return e}function c(e){var t={};if(e&&e.types){var n=e.getData("Text");n&&n.length>0&&(t["text/plain"]=n);for(var i=0;i')},t.readAsDataURL(e.getAsFile()),!0}}if(!(!i.settings.paste_data_images||"text/html"in t||"text/plain"in t)&&e.clipboardData){var o=e.clipboardData.items;if(o)for(var a=0;a0}function p(){i.on("keydown",function(n){if(!n.isDefaultPrevented()&&(t.metaKeyPressed(n)&&86==n.keyCode||n.shiftKey&&45==n.keyCode)){if(x=n.shiftKey&&86==n.keyCode,n.stopImmediatePropagation(),b=(new Date).getTime(),e.ie&&x)return n.preventDefault(),void i.fire("paste",{ieFake:!0});s(),a()}}),i.on("paste",function(t){var c=d(t),f=(new Date).getTime()-b<1e3,p="text"==g.pasteFormat||x;return t.isDefaultPrevented()?void s():u(t,c)?void s():(f||t.preventDefault(),!e.ie||f&&!t.ieFake||(a(),i.dom.bind(h,"paste",function(e){e.stopPropagation()}),i.getDoc().execCommand("Paste",!1,null),c["text/html"]=l()),void setTimeout(function(){var e=l();return h&&h.firstChild&&"mcepastebin"===h.firstChild.id&&(p=!0),s(),!p&&f&&e&&e!=y&&(c["text/html"]=e),e!=y&&f||(e=c["text/html"]||c["text/plain"]||y,e!=y)?(!m(c,"text/html")&&m(c,"text/plain")&&(p=!0),void(p?o(c["text/plain"]||n.innerText(e)):r(e))):void(f||i.windowManager.alert("Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents."))},0))}),i.on("dragstart",function(e){if(e.dataTransfer.types)try{e.dataTransfer.setData("mce-internal",i.selection.getContent())}catch(t){}}),i.on("drop",function(e){var t=f(e);if(t&&!e.isDefaultPrevented()){var n=c(e.dataTransfer),a=n["mce-internal"]||n["text/html"]||n["text/plain"];a&&(e.preventDefault(),i.undoManager.transact(function(){n["mce-internal"]&&i.execCommand("Delete"),i.selection.setRng(t),n["text/html"]?r(a):o(a)}))}})}var g=this,h,v,b=0,y="%MCEPASTEBIN%",x;g.pasteHtml=r,g.pasteText=o,i.on("preInit",function(){p(),i.parser.addNodeFilter("img",function(t){if(!i.settings.paste_data_images)for(var n=t.length;n--;){var r=t[n].attributes.map.src;r&&0===r.indexOf("data:image")&&(t[n].attr("data-mce-object")||r===e.transparentSrc||t[n].remove())}})}),i.on("PreProcess",function(){i.dom.remove(i.dom.get("mcepastebin"))})}}),i(g,[c,d,u,h,v,l],function(e,t,n,i,r,o){function a(e){return/l?n&&(n=n.parent.parent):(i=n,n=null)),n&&n.name==a?n.append(e):(i=i||n,n=new r(a,1),s>1&&n.attr("start",""+s),e.wrap(n)),e.name="li",t.value="";var c=t.next;c&&3==c.type&&(c.value=c.value.replace(/^\u00a0+/,"")),l>o&&i&&i.lastChild.append(n),o=l}for(var n,i,o=1,a=e.getAll("p"),s=0;s/gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\xa0"],[/([\s\u00a0]*)<\/span>/gi,function(e,t){return t.length>0?t.replace(/./," ").slice(Math.floor(t.length/2)).split("").join("\xa0"):""}]]);var g=l.paste_word_valid_elements;g||(g="-strong/b,-em/i,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-p/div,-table[width],-tr,-td[colspan|rowspan|width],-th,-thead,-tfoot,-tbody,-a[href|name],sub,sup,strike,br,del");var h=new n({valid_elements:g,valid_children:"-li[p]"});e.each(h.elements,function(e){e.attributes["class"]||(e.attributes["class"]={},e.attributesOrder.push("class")),e.attributes.style||(e.attributes.style={},e.attributesOrder.push("style"))});var v=new t({},h);v.addAttributeFilter("style",function(e){for(var t=e.length,n;t--;)n=e[t],n.attr("style",u(n,n.attr("style"))),"span"==n.name&&n.parent&&!n.attributes.length&&n.unwrap()}),v.addAttributeFilter("class",function(e){for(var t=e.length,n,i;t--;)n=e[t],i=n.attr("class"),/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(i)&&n.remove(),n.attr("class",null)}),v.addNodeFilter("del",function(e){for(var t=e.length;t--;)e[t].remove()}),v.addNodeFilter("a",function(e){for(var t=e.length,n,i,r;t--;)if(n=e[t],i=n.attr("href"),r=n.attr("name"),i&&-1!=i.indexOf("#_msocom_"))n.remove();else if(i&&0===i.indexOf("file://")&&(i=i.split("#")[1],i&&(i="#"+i)),i||r){if(r&&!/^_?(?:toc|edn|ftn)/i.test(r)){n.unwrap();continue}n.attr({href:i,name:r})}else n.unwrap()});var b=v.parse(f);d(b),c.content=new i({},h).serialize(b)}})}return s.isWordContent=a,s}),i(b,[m,c,g,l],function(e,t,n,i){return function(r){function o(e){r.on("BeforePastePreProcess",function(t){t.content=e(t.content)})}function a(e){return e=i.filter(e,[/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g,/|/g,[/\u00a0<\/span>/g,"\xa0"],/
        $/i])}function s(e){if(!n.isWordContent(e))return e;var o=[];t.each(r.schema.getBlockElements(),function(e,t){o.push(t)});var a=new RegExp("(?:
         [\\s\\r\\n]+|
        )*(<\\/?("+o.join("|")+")[^>]*>)(?:
         [\\s\\r\\n]+|
        )*","g");return e=i.filter(e,[[a,"$1"]]),e=i.filter(e,[[/

        /g,"

        "],[/
        /g," "],[/

        /g,"
        "]])}function l(e){if(n.isWordContent(e))return e;var t=r.settings.paste_webkit_styles;if(r.settings.paste_remove_styles_if_webkit===!1||"all"==t)return e;if(t&&(t=t.split(/[, ]/)),t){var i=r.dom,o=r.selection.getNode();e=e.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi,function(e,n,r,a){var s=i.parseStyle(r,"span"),l={};if("none"===t)return n+a;for(var c=0;c]+) style="([^"]*)"([^>]*>)/gi,"$1$3");return e=e.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi,function(e,t,n,i){return t+' style="'+n+'"'+i})}e.webkit&&(o(l),o(a)),e.ie&&o(s)}}),i(y,[x,f,g,b],function(e,t,n,i){var r;e.add("paste",function(e){function o(){"text"==s.pasteFormat?(this.active(!1),s.pasteFormat="html"):(s.pasteFormat="text",this.active(!0),r||(e.windowManager.alert("Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off."),r=!0))}var a=this,s,l=e.settings;a.clipboard=s=new t(e),a.quirks=new i(e),a.wordFilter=new n(e),e.settings.paste_as_text&&(a.clipboard.pasteFormat="text"),l.paste_preprocess&&e.on("PastePreProcess",function(e){l.paste_preprocess.call(a,a,e)}),l.paste_postprocess&&e.on("PastePostProcess",function(e){l.paste_postprocess.call(a,a,e)}),e.addCommand("mceInsertClipboardContent",function(e,t){t.content&&a.clipboard.pasteHtml(t.content),t.text&&a.clipboard.pasteText(t.text)}),e.paste_block_drop&&e.on("dragend dragover draggesture dragdrop drop drag",function(e){e.preventDefault(),e.stopPropagation()}),e.settings.paste_data_images||e.on("drop",function(e){var t=e.dataTransfer;t&&t.files&&t.files.length>0&&e.preventDefault()}),e.addButton("pastetext",{icon:"pastetext",tooltip:"Paste as text",onclick:o,active:"text"==a.clipboard.pasteFormat}),e.addMenuItem("pastetext",{text:"Paste as text",selectable:!0,active:s.pasteFormat,onclick:o})})}),a([l,f,g,b,y])}(this);tinymce.PluginManager.add("preview",function(e){var t=e.settings,i=!tinymce.Env.ie;e.addCommand("mcePreview",function(){e.windowManager.open({title:"Preview",width:parseInt(e.getParam("plugin_preview_width","650"),10),height:parseInt(e.getParam("plugin_preview_height","500"),10),html:'",buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var n,a="";a+='',tinymce.each(e.contentCSS,function(t){a+=''});var r=t.body_id||"tinymce";-1!=r.indexOf("=")&&(r=e.getParam("body_id","","hash"),r=r[e.id]||r);var d=t.body_class||"";-1!=d.indexOf("=")&&(d=e.getParam("body_class","","hash"),d=d[e.id]||"");var o=e.settings.directionality?' dir="'+e.settings.directionality+'"':"";if(n=""+a+'"+e.getContent()+"",i)this.getEl("body").firstChild.src="data:text/html;charset=utf-8,"+encodeURIComponent(n);else{var s=this.getEl("body").firstChild.contentWindow.document;s.open(),s.write(n),s.close()}}})}),e.addButton("preview",{title:"Preview",cmd:"mcePreview"}),e.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})});tinymce.PluginManager.add("print",function(t){t.addCommand("mcePrint",function(){t.getWin().print()}),t.addButton("print",{title:"Print",cmd:"mcePrint"}),t.addShortcut("Ctrl+P","","mcePrint"),t.addMenuItem("print",{text:"Print",cmd:"mcePrint",icon:"print",shortcut:"Ctrl+P",context:"file"})});tinymce.PluginManager.add("save",function(e){function a(){var a;return a=tinymce.DOM.getParent(e.id,"form"),!e.getParam("save_enablewhendirty",!0)||e.isDirty()?(tinymce.triggerSave(),e.getParam("save_onsavecallback")?void(e.execCallback("save_onsavecallback",e)&&(e.startContent=tinymce.trim(e.getContent({format:"raw"})),e.nodeChanged())):void(a?(e.isNotDirty=!0,(!a.onsubmit||a.onsubmit())&&("function"==typeof a.submit?a.submit():e.windowManager.alert("Error: Form submit field collision.")),e.nodeChanged()):e.windowManager.alert("Error: No form element found."))):void 0}function n(){var a=tinymce.trim(e.startContent);return e.getParam("save_oncancelcallback")?void e.execCallback("save_oncancelcallback",e):(e.setContent(a),e.undoManager.clear(),void e.nodeChanged())}function t(){var a=this;e.on("nodeChange",function(){a.disabled(e.getParam("save_enablewhendirty",!0)&&!e.isDirty())})}e.addCommand("mceSave",a),e.addCommand("mceCancel",n),e.addButton("save",{icon:"save",text:"Save",cmd:"mceSave",disabled:!0,onPostRender:t}),e.addButton("cancel",{text:"Cancel",icon:!1,cmd:"mceCancel",disabled:!0,onPostRender:t}),e.addShortcut("ctrl+s","","mceSave")});!function(){function e(e,t,n,a,r){function i(e,t){if(t=t||0,!e[0])throw"findAndReplaceDOMText cannot handle zero-length matches";var n=e.index;if(t>0){var a=e[t];if(!a)throw"Invalid capture group";n+=e[0].indexOf(a),e[0]=a}return[n,n+e[0].length,[e[0]]]}function d(e){var t;if(3===e.nodeType)return e.data;if(h[e.nodeName]&&!u[e.nodeName])return"";if(t="",(u[e.nodeName]||m[e.nodeName])&&(t+="\n"),e=e.firstChild)do t+=d(e);while(e=e.nextSibling);return t}function o(e,t,n){var a,r,i,d,o=[],l=0,c=e,s=t.shift(),f=0;e:for(;;){if((u[c.nodeName]||m[c.nodeName])&&l++,3===c.nodeType&&(!r&&c.length+l>=s[1]?(r=c,d=s[1]-l):a&&o.push(c),!a&&c.length+l>s[0]&&(a=c,i=s[0]-l),l+=c.length),a&&r){if(c=n({startNode:a,startNodeIndex:i,endNode:r,endNodeIndex:d,innerNodes:o,match:s[2],matchIndex:f}),l-=r.length-d,a=null,r=null,o=[],s=t.shift(),f++,!s)break}else{if((!h[c.nodeName]||u[c.nodeName])&&c.firstChild){c=c.firstChild;continue}if(c.nextSibling){c=c.nextSibling;continue}}for(;;){if(c.nextSibling){c=c.nextSibling;break}if(c.parentNode===e)break e;c=c.parentNode}}}function l(e){var t;if("function"!=typeof e){var n=e.nodeType?e:f.createElement(e);t=function(e,t){var a=n.cloneNode(!1);return a.setAttribute("data-mce-index",t),e&&a.appendChild(f.createTextNode(e)),a}}else t=e;return function(e){var n,a,r,i=e.startNode,d=e.endNode,o=e.matchIndex;if(i===d){var l=i;r=l.parentNode,e.startNodeIndex>0&&(n=f.createTextNode(l.data.substring(0,e.startNodeIndex)),r.insertBefore(n,l));var c=t(e.match[0],o);return r.insertBefore(c,l),e.endNodeIndexh;++h){var g=e.innerNodes[h],p=t(g.data,o);g.parentNode.replaceChild(p,g),u.push(p)}var x=t(d.data.substring(0,e.endNodeIndex),o);return r=i.parentNode,r.insertBefore(n,i),r.insertBefore(s,i),r.removeChild(i),r=d.parentNode,r.insertBefore(x,d),r.insertBefore(a,d),r.removeChild(d),x}}var c,s,f,u,h,m,g=[],p=0;if(f=t.ownerDocument,u=r.getBlockElements(),h=r.getWhiteSpaceElements(),m=r.getShortEndedElements(),s=d(t)){if(e.global)for(;c=e.exec(s);)g.push(i(c,a));else c=s.match(e),g.push(i(c,a));return g.length&&(p=g.length,o(t,g,l(n))),p}}function t(t){function n(){function e(){r.statusbar.find("#next").disabled(!d(s+1).length),r.statusbar.find("#prev").disabled(!d(s-1).length)}function n(){tinymce.ui.MessageBox.alert("Could not find the specified string.",function(){r.find("#find")[0].focus()})}var a={},r=tinymce.ui.Factory.create({type:"window",layout:"flex",pack:"center",align:"center",onClose:function(){t.focus(),c.done()},onSubmit:function(t){var i,o,l,f;return t.preventDefault(),o=r.find("#case").checked(),f=r.find("#words").checked(),l=r.find("#find").value(),l.length?a.text==l&&a.caseState==o&&a.wholeWord==f?0===d(s+1).length?void n():(c.next(),void e()):(i=c.find(l,o,f),i||n(),r.statusbar.items().slice(1).disabled(0===i),e(),void(a={text:l,caseState:o,wholeWord:f})):(c.done(!1),void r.statusbar.items().slice(1).disabled(!0))},buttons:[{text:"Find",onclick:function(){r.submit()}},{text:"Replace",disabled:!0,onclick:function(){c.replace(r.find("#replace").value())||(r.statusbar.items().slice(1).disabled(!0),s=-1,a={})}},{text:"Replace all",disabled:!0,onclick:function(){c.replace(r.find("#replace").value(),!0,!0),r.statusbar.items().slice(1).disabled(!0),a={}}},{type:"spacer",flex:1},{text:"Prev",name:"prev",disabled:!0,onclick:function(){c.prev(),e()}},{text:"Next",name:"next",disabled:!0,onclick:function(){c.next(),e()}}],title:"Find and replace",items:{type:"form",padding:20,labelGap:30,spacing:10,items:[{type:"textbox",name:"find",size:40,label:"Find",value:t.selection.getNode().src},{type:"textbox",name:"replace",size:40,label:"Replace with"},{type:"checkbox",name:"case",text:"Match case",label:" "},{type:"checkbox",name:"words",text:"Whole words",label:" "}]}}).renderTo().reflow()}function a(e){var t=e.getAttribute("data-mce-index");return"number"==typeof t?""+t:t}function r(n){var a,r;return r=t.dom.create("span",{"data-mce-bogus":1}),r.className="mce-match-marker",a=t.getBody(),c.done(!1),e(n,a,r,!1,t.schema)}function i(e){var t=e.parentNode;e.firstChild&&t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function d(e){var n,r=[];if(n=tinymce.toArray(t.getBody().getElementsByTagName("span")),n.length)for(var i=0;is&&f[o].setAttribute("data-mce-index",m-1)}return t.undoManager.add(),s=p,n?(g=d(p+1).length>0,c.next()):(g=d(p-1).length>0,c.prev()),!r&&g},c.done=function(e){var n,r,d,o;for(r=tinymce.toArray(t.getBody().getElementsByTagName("span")),n=0;n=d.end?(r=c,a=d.end-s):o&&l.push(c),!o&&c.length+s>d.start&&(o=c,i=d.start-s),s+=c.length),o&&r){if(c=n({startNode:o,startNodeIndex:i,endNode:r,endNodeIndex:a,innerNodes:l,match:d.text,matchIndex:u}),s-=r.length-a,o=null,r=null,l=[],d=t.shift(),u++,!d)break}else{if((!P[c.nodeName]||S[c.nodeName])&&c.firstChild){c=c.firstChild;continue}if(c.nextSibling){c=c.nextSibling;continue}}for(;;){if(c.nextSibling){c=c.nextSibling;break}if(c.parentNode===e)break e;c=c.parentNode}}}function i(e){function t(t,n){var o=w[n];o.stencil||(o.stencil=e(o));var r=o.stencil.cloneNode(!1);return r.setAttribute("data-mce-index",n),t&&r.appendChild(k.doc.createTextNode(t)),r}return function(e){var n,o,r,i=e.startNode,a=e.endNode,l=e.matchIndex,s=k.doc;if(i===a){var c=i;r=c.parentNode,e.startNodeIndex>0&&(n=s.createTextNode(c.data.substring(0,e.startNodeIndex)),r.insertBefore(n,c));var d=t(e.match,l);return r.insertBefore(d,c),e.endNodeIndexm;++m){var g=e.innerNodes[m],h=t(g.data,l);g.parentNode.replaceChild(h,g),f.push(h)}var v=t(a.data.substring(0,e.endNodeIndex),l);return r=i.parentNode,r.insertBefore(n,i),r.insertBefore(u,i),r.removeChild(i),r=a.parentNode,r.insertBefore(v,a),r.insertBefore(o,a),r.removeChild(a),v}}function a(e){var t=e.parentNode;t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function l(t){var n=e.getElementsByTagName("*"),o=[];t="number"==typeof t?""+t:null;for(var r=0;rt&&e(w[t],t)!==!1;t++);return this}function u(t){return w.length&&r(e,w,i(t)),this}function f(e,t){if(C&&e.global)for(;x=e.exec(C);)w.push(n(x,t));return this}function m(e){var t,n=l(e?s(e):null);for(t=n.length;t--;)a(n[t]);return this}function p(e){return w[e.getAttribute("data-mce-index")]}function g(e){return l(s(e))[0]}function h(e,t,n){return w.push({start:e,end:e+t,text:C.substr(e,t),data:n}),this}function v(e){var n=l(s(e)),o=t.dom.createRng();return o.setStartBefore(n[0]),o.setEndAfter(n[n.length-1]),o}function b(e,n){var o=v(e);return o.deleteContents(),n.length>0&&o.insertNode(t.dom.doc.createTextNode(n)),o}function y(){return w.splice(0,w.length),m(),this}var x,w=[],C,k=t.dom,S,P,N;return S=t.schema.getBlockElements(),P=t.schema.getWhiteSpaceElements(),N=t.schema.getShortEndedElements(),C=o(e),{text:C,matches:w,each:d,filter:c,reset:y,matchFromElement:p,elementFromMatch:g,find:f,add:h,wrap:u,unwrap:m,replace:b,rangeFromMatch:v,indexOf:s}}}),o(c,[s,d,u,f,m,p,g,h],function(e,t,n,o,r,i,a,l){t.add("spellchecker",function(t,s){function c(){return C.textMatcher||(C.textMatcher=new e(t.getBody(),t)),C.textMatcher}function d(e,t){var o=[];return n.each(t,function(e){o.push({selectable:!0,text:e.name,data:e.value})}),o}function u(e){for(var t in e)return!1;return!0}function f(e,i){var a=[],l=k[e];n.each(l,function(e){a.push({text:e,onclick:function(){t.insertContent(t.dom.encode(e)),t.dom.remove(i),g()}})}),a.push.apply(a,[{text:"-"},{text:"Ignore",onclick:function(){h(e,i)}},{text:"Ignore all",onclick:function(){h(e,i,!0)}},{text:"Finish",onclick:v}]),P=new o({items:a,context:"contextmenu",onautohide:function(e){-1!=e.target.className.indexOf("spellchecker")&&e.preventDefault()},onhide:function(){P.remove(),P=null}}),P.renderTo(document.body);var s=r.DOM.getPos(t.getContentAreaContainer()),c=t.dom.getPos(i[0]),d=t.dom.getRoot();"BODY"==d.nodeName?(c.x-=d.ownerDocument.documentElement.scrollLeft||d.scrollLeft,c.y-=d.ownerDocument.documentElement.scrollTop||d.scrollTop):(c.x-=d.scrollLeft,c.y-=d.scrollTop),s.x+=c.x,s.y+=c.y,P.moveTo(s.x,s.y+i[0].offsetHeight)}function m(){return t.getParam("spellchecker_wordchar_pattern")||new RegExp('[^\\s!"#$%&()*+,-./:;<=>?@[\\]^_{|}`\xa7\xa9\xab\xae\xb1\xb6\xb7\xb8\xbb\xbc\xbd\xbe\xbf\xd7\xf7\xa4\u201d\u201c\u201e]+',"g")}function p(){function e(e){return t.setProgressState(!1),u(e)?(t.windowManager.alert("No misspellings found"),void(S=!1)):(k=e,c().find(m()).filter(function(t){return!!e[t.text]}).wrap(function(e){return t.dom.create("span",{"class":"mce-spellchecker-word","data-mce-bogus":1,"data-mce-word":e.text})}),void t.fire("SpellcheckStart"))}function n(e){t.windowManager.alert(e),t.setProgressState(!1),v()}function o(e,t,o){i.send({url:new a(s).toAbsolute(N.spellchecker_rpc_url),type:"post",content_type:"application/x-www-form-urlencoded",data:"text="+encodeURIComponent(t)+"&lang="+N.spellchecker_language,success:function(e){e=l.parse(e),e?e.error?n(e.error):o(e.words):n("Sever response wasn't proper JSON.")},error:function(e,t){n("Spellchecker request error: "+t.status)}})}if(S)return void v();v(),S=!0,t.setProgressState(!0);var r=N.spellchecker_callback||o;r.call(C,"spellcheck",c().text,e,n),t.focus()}function g(){t.dom.select("span.mce-spellchecker-word").length||v()}function h(e,o,r){t.selection.collapse(),r?n.each(t.dom.select("span.mce-spellchecker-word"),function(n){n.getAttribute("data-mce-word")==e&&t.dom.remove(n,!0)}):t.dom.remove(o,!0),g()}function v(){c().reset(),C.textMatcher=null,S&&(S=!1,t.fire("SpellcheckEnd"))}function b(e){var t=e.getAttribute("data-mce-index");return"number"==typeof t?""+t:t}function y(e){var o,r=[];if(o=n.toArray(t.getBody().getElementsByTagName("span")),o.length)for(var i=0;i0){var r=t.dom.createRng();r.setStartBefore(o[0]),r.setEndAfter(o[o.length-1]),t.selection.setRng(r),f(n.getAttribute("data-mce-word"),o)}}}),t.addMenuItem("spellchecker",{text:"Spellcheck",context:"tools",onclick:p,selectable:!0,onPostRender:function(){var e=this;t.on("SpellcheckStart SpellcheckEnd",function(){e.active(S)})}});var T={tooltip:"Spellcheck",onclick:p,onPostRender:function(){var e=this;t.on("SpellcheckStart SpellcheckEnd",function(){e.active(S)})}};w.length>1&&(T.type="splitbutton",T.menu=w,T.onshow=x,T.onselect=function(e){N.spellchecker_language=e.control.settings.data}),t.addButton("spellchecker",T),t.addCommand("mceSpellCheck",p),t.on("remove",function(){P&&(P.remove(),P=null)}),t.on("change",g),this.getTextMatcher=c,this.getWordCharPattern=m,this.getLanguage=function(){return N.spellchecker_language},N.spellchecker_language=N.spellchecker_language||N.language||"en"})}),a([s,c])}(this);tinymce.PluginManager.add("tabfocus",function(e){function n(e){9!==e.keyCode||e.ctrlKey||e.altKey||e.metaKey||e.preventDefault()}function t(n){function t(n){function t(e){return"BODY"===e.nodeName||"hidden"!=e.type&&"none"!=e.style.display&&"hidden"!=e.style.visibility&&t(e.parentNode)}function r(e){return e.tabIndex||"INPUT"==e.nodeName||"TEXTAREA"==e.nodeName}function c(e){return!r(e)&&"-1"!=e.getAttribute("tabindex")&&t(e)}if(u=i.select(":input:enabled,*[tabindex]:not(iframe)"),o(u,function(n,t){return n.id==e.id?(a=t,!1):void 0}),n>0){for(d=a+1;d=0;d--)if(c(u[d]))return u[d];return null}var a,u,c,d;if(!(9!==n.keyCode||n.ctrlKey||n.altKey||n.metaKey)&&(c=r(e.getParam("tab_focus",e.getParam("tabfocus_elements",":prev,:next"))),1==c.length&&(c[1]=c[0],c[0]=":prev"),u=n.shiftKey?":prev"==c[0]?t(-1):i.get(c[0]):":next"==c[1]?t(1):i.get(c[1]))){var y=tinymce.get(u.id||u.name);u.id&&y?y.focus():window.setTimeout(function(){tinymce.Env.webkit||window.focus(),u.focus()},10),n.preventDefault()}}var i=tinymce.DOM,o=tinymce.each,r=tinymce.explode;e.on("init",function(){e.inline&&tinymce.DOM.setAttrib(e.getBody(),"tabIndex",null)}),e.on("keyup",n),tinymce.Env.gecko?e.on("keypress keydown",t):e.on("keydown",t)});!function(e,t){"use strict";function n(e,t){for(var n,o=[],i=0;i "+t+" tr",a);i(n,function(n,r){r+=e,i(I.select("> td, > th",n),function(e,n){var i,a,l,s;if(A[r])for(;A[r][n];)n++;for(l=o(e,"rowspan"),s=o(e,"colspan"),a=r;r+l>a;a++)for(A[a]||(A[a]=[]),i=n;n+s>i;i++)A[a][i]={part:t,real:a==r&&i==n,elm:e,rowspan:l,colspan:s}})}),e+=n.length})}function s(e,t){return e=e.cloneNode(t),e.removeAttribute("id"),e}function c(e,t){var n;return n=A[t],n?n[e]:void 0}function d(e,t,n){e&&(n=parseInt(n,10),1===n?e.removeAttribute(t,1):e.setAttribute(t,n,1))}function u(e){return e&&(I.hasClass(e.elm,"mce-item-selected")||e==M)}function f(){var e=[];return i(a.rows,function(t){i(t.cells,function(n){return I.hasClass(n,"mce-item-selected")||M&&n==M.elm?(e.push(t),!1):void 0})}),e}function m(){var e=I.createRng();e.setStartAfter(a),e.setEndAfter(a),E.setRng(e),I.remove(a)}function p(t){var o,a={};return r.settings.table_clone_elements!==!1&&(a=e.makeMap((r.settings.table_clone_elements||"strong em b i span font h1 h2 h3 h4 h5 h6 p div").toUpperCase(),/[ ,]/)),e.walk(t,function(e){var r;return 3==e.nodeType?(i(I.getParents(e.parentNode,null,t).reverse(),function(e){a[e.nodeName]&&(e=s(e,!1),o?r&&r.appendChild(e):o=r=e,r=e)}),r&&(r.innerHTML=n.ie?" ":'
        '),!1):void 0},"childNodes"),t=s(t,!1),d(t,"rowSpan",1),d(t,"colSpan",1),o?t.appendChild(o):n.ie||(t.innerHTML='
        '),t}function g(){var e=I.createRng(),t;return i(I.select("tr",a),function(e){0===e.cells.length&&I.remove(e)}),0===I.select("tr",a).length?(e.setStartBefore(a),e.setEndBefore(a),E.setRng(e),void I.remove(a)):(i(I.select("thead,tbody,tfoot",a),function(e){0===e.rows.length&&I.remove(e)}),l(),void(B&&(t=A[Math.min(A.length-1,B.y)],t&&(E.select(t[Math.min(t.length-1,B.x)].elm,!0),E.collapse(!0)))))}function h(e,t,n,o){var i,r,a,l,s;for(i=A[t][e].elm.parentNode,a=1;n>=a;a++)if(i=I.getNext(i,"tr")){for(r=e;r>=0;r--)if(s=A[t+a][r].elm,s.parentNode==i){for(l=1;o>=l;l++)I.insertAfter(p(s),s);break}if(-1==r)for(l=1;o>=l;l++)i.insertBefore(p(i.cells[0]),i.cells[0])}}function v(){i(A,function(e,t){i(e,function(e,n){var i,r,a;if(u(e)&&(e=e.elm,i=o(e,"colspan"),r=o(e,"rowspan"),i>1||r>1)){for(d(e,"rowSpan",1),d(e,"colSpan",1),a=0;i-1>a;a++)I.insertAfter(p(e),e);h(n,t,r-1,i)}})})}function b(t,n,o){var r,a,s,f,m,p,h,b,y,w,x;if(t?(r=T(t),a=r.x,s=r.y,f=a+(n-1),m=s+(o-1)):(B=D=null,i(A,function(e,t){i(e,function(e,n){u(e)&&(B||(B={x:n,y:t}),D={x:n,y:t})})}),B&&(a=B.x,s=B.y,f=D.x,m=D.y)),b=c(a,s),y=c(f,m),b&&y&&b.part==y.part){for(v(),l(),b=c(a,s).elm,d(b,"colSpan",f-a+1),d(b,"rowSpan",m-s+1),h=s;m>=h;h++)for(p=a;f>=p;p++)A[h]&&A[h][p]&&(t=A[h][p].elm,t!=b&&(w=e.grep(t.childNodes),i(w,function(e){b.appendChild(e)}),w.length&&(w=e.grep(b.childNodes),x=0,i(w,function(e){"BR"==e.nodeName&&I.getAttrib(e,"data-mce-bogus")&&x++0&&A[n-1][l]&&(g=A[n-1][l].elm,h=o(g,"rowSpan"),h>1)){d(g,"rowSpan",h+1);continue}}else if(h=o(r,"rowspan"),h>1){d(r,"rowSpan",h+1);continue}m=p(r),d(m,"colSpan",r.colSpan),f.appendChild(m),a=r}f.hasChildNodes()&&(e?c.parentNode.insertBefore(f,c):I.insertAfter(f,c))}}function w(e){var t,n;i(A,function(n){return i(n,function(n,o){return u(n)&&(t=o,e)?!1:void 0}),e?!t:void 0}),i(A,function(i,r){var a,l,s;i[t]&&(a=i[t].elm,a!=n&&(s=o(a,"colspan"),l=o(a,"rowspan"),1==s?e?(a.parentNode.insertBefore(p(a),a),h(t,r,l-1,s)):(I.insertAfter(p(a),a),h(t,r,l-1,s)):d(a,"colSpan",a.colSpan+1),n=a))})}function x(){var t=[];i(A,function(n){i(n,function(n,r){u(n)&&-1===e.inArray(t,r)&&(i(A,function(e){var t=e[r].elm,n;n=o(t,"colSpan"),n>1?d(t,"colSpan",n-1):I.remove(t)}),t.push(r))})}),g()}function C(){function e(e){var t,n,r;t=I.getNext(e,"tr"),i(e.cells,function(e){var t=o(e,"rowSpan");t>1&&(d(e,"rowSpan",t-1),n=T(e),h(n.x,n.y,1,1))}),n=T(e.cells[0]),i(A[n.y],function(e){var t;e=e.elm,e!=r&&(t=o(e,"rowSpan"),1>=t?I.remove(e):d(e,"rowSpan",t-1),r=e)})}var t;t=f(),i(t.reverse(),function(t){e(t)}),g()}function P(){var e=f();return I.remove(e),g(),e}function S(){var e=f();return i(e,function(t,n){e[n]=s(t,!0)}),e}function R(e,t){var n=f(),o=n[t?0:n.length-1],r=o.cells.length;e&&(i(A,function(e){var t;return r=0,i(e,function(e){e.real&&(r+=e.colspan),e.elm.parentNode==o&&(t=1)}),t?!1:void 0}),t||e.reverse(),i(e,function(e){var n,i=e.cells.length,a;for(n=0;i>n;n++)a=e.cells[n],d(a,"colSpan",1),d(a,"rowSpan",1);for(n=i;r>n;n++)e.appendChild(p(e.cells[i-1]));for(n=r;i>n;n++)I.remove(e.cells[n]);t?o.parentNode.insertBefore(e,o):I.insertAfter(e,o)}),I.removeClass(I.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"))}function T(e){var t;return i(A,function(n,o){return i(n,function(n,i){return n.elm==e?(t={x:i,y:o},!1):void 0}),!t}),t}function k(e){B=T(e)}function N(){var e,t;return e=t=0,i(A,function(n,o){i(n,function(n,i){var r,a;u(n)&&(n=A[o][i],i>e&&(e=i),o>t&&(t=o),n.real&&(r=n.colspan-1,a=n.rowspan-1,r&&i+r>e&&(e=i+r),a&&o+a>t&&(t=o+a)))})}),{x:e,y:t}}function _(e){var t,n,o,i,r,a,l,s,c,d;if(D=T(e),B&&D){for(t=Math.min(B.x,D.x),n=Math.min(B.y,D.y),o=Math.max(B.x,D.x),i=Math.max(B.y,D.y),r=o,a=i,d=n;a>=d;d++)e=A[d][t],e.real||t-(e.colspan-1)=c;c++)e=A[n][c],e.real||n-(e.rowspan-1)=d;d++)for(c=t;o>=c;c++)e=A[d][c],e.real&&(l=e.colspan-1,s=e.rowspan-1,l&&c+l>r&&(r=c+l),s&&d+s>a&&(a=d+s));for(I.removeClass(I.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),d=n;a>=d;d++)for(c=t;r>=c;c++)A[d][c]&&I.addClass(A[d][c].elm,"mce-item-selected")}}var A,B,D,M,E=r.selection,I=E.dom;a=a||I.getParent(E.getStart(),"table"),l(),M=I.getParent(E.getStart(),"th,td"),M&&(B=T(M),D=N(),M=c(B.x,B.y)),e.extend(this,{deleteTable:m,split:v,merge:b,insertRow:y,insertCol:w,deleteCols:x,deleteRows:C,cutRows:P,copyRows:S,pasteRows:R,getPos:T,setStartCell:k,setEndCell:_})}}),o(u,[f,d,c],function(e,t,n){function o(e,t){return parseInt(e.getAttribute(t)||1,10)}var i=n.each;return function(n){function r(){function t(t){function r(e,o){var i=e?"previousSibling":"nextSibling",r=n.dom.getParent(o,"tr"),l=r[i];if(l)return h(n,o,l,e),t.preventDefault(),!0;var d=n.dom.getParent(r,"table"),u=r.parentNode,f=u.nodeName.toLowerCase();if("tbody"===f||f===(e?"tfoot":"thead")){var m=a(e,d,u,"tbody");if(null!==m)return s(e,m,o)}return c(e,r,i,d)}function a(e,t,o,i){var r=n.dom.select(">"+i,t),a=r.indexOf(o);if(e&&0===a||!e&&a===r.length-1)return l(e,t);if(-1===a){var s="thead"===o.tagName.toLowerCase()?0:r.length-1;return r[s]}return r[a+(e?-1:1)]}function l(e,t){var o=e?"thead":"tfoot",i=n.dom.select(">"+o,t);return 0!==i.length?i[0]:null}function s(e,o,i){var r=d(o,e);return r&&h(n,i,r,e),t.preventDefault(),!0}function c(e,o,i,a){var l=a[i];if(l)return u(l),!0;var s=n.dom.getParent(a,"td,th");if(s)return r(e,s,t);var c=d(o,!e);return u(c),t.preventDefault(),!1}function d(e,t){var o=e&&e[t?"lastChild":"firstChild"];return o&&"BR"===o.nodeName?n.dom.getParent(o,"td,th"):o}function u(e){n.selection.setCursorLocation(e,0)}function f(){return y==e.UP||y==e.DOWN}function m(e){var t=e.selection.getNode(),n=e.dom.getParent(t,"tr");return null!==n}function p(e){for(var t=0,n=e;n.previousSibling;)n=n.previousSibling,t+=o(n,"colspan");return t}function g(e,t){var n=0,r=0;return i(e.children,function(e,i){return n+=o(e,"colspan"),r=i,n>t?!1:void 0}),r}function h(e,t,o,i){var r=p(n.dom.getParent(t,"td,th")),a=g(o,r),l=o.childNodes[a],s=d(l,i);u(s||l)}function v(e){var t=n.selection.getNode(),o=n.dom.getParent(t,"td,th"),i=n.dom.getParent(e,"td,th");return o&&o!==i&&b(o,i)}function b(e,t){return n.dom.getParent(e,"TABLE")===n.dom.getParent(t,"TABLE")}var y=t.keyCode;if(f()&&m(n)){var w=n.selection.getNode();setTimeout(function(){v(w)&&r(!t.shiftKey&&y===e.UP,w,t)},0)}}n.on("KeyDown",function(e){t(e)})}function a(){function e(e,t){var n=t.ownerDocument,o=n.createRange(),i;return o.setStartBefore(t),o.setEnd(e.endContainer,e.endOffset),i=n.createElement("body"),i.appendChild(o.cloneContents()),0===i.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length}n.on("KeyDown",function(t){var o,i,r=n.dom;(37==t.keyCode||38==t.keyCode)&&(o=n.selection.getRng(),i=r.getParent(o.startContainer,"table"),i&&n.getBody().firstChild==i&&e(o,i)&&(o=r.createRng(),o.setStartBefore(i),o.setEndBefore(i),n.selection.setRng(o),t.preventDefault()))})}function l(){n.on("KeyDown SetContent VisualAid",function(){var e;for(e=n.getBody().lastChild;e;e=e.previousSibling)if(3==e.nodeType){if(e.nodeValue.length>0)break}else if(1==e.nodeType&&!e.getAttribute("data-mce-bogus"))break;e&&"TABLE"==e.nodeName&&(n.settings.forced_root_block?n.dom.add(n.getBody(),n.settings.forced_root_block,n.settings.forced_root_block_attrs,t.ie&&t.ie<11?" ":'
        '):n.dom.add(n.getBody(),"br",{"data-mce-bogus":"1"}))}),n.on("PreProcess",function(e){var t=e.node.lastChild;t&&("BR"==t.nodeName||1==t.childNodes.length&&("BR"==t.firstChild.nodeName||"\xa0"==t.firstChild.nodeValue))&&t.previousSibling&&"TABLE"==t.previousSibling.nodeName&&n.dom.remove(t)})}function s(){function e(e,t,n,o){var i=3,r=e.dom.getParent(t.startContainer,"TABLE"),a,l,s;return r&&(a=r.parentNode),l=t.startContainer.nodeType==i&&0===t.startOffset&&0===t.endOffset&&o&&("TR"==n.nodeName||n==a),s=("TD"==n.nodeName||"TH"==n.nodeName)&&!o,l||s}function t(){var t=n.selection.getRng(),o=n.selection.getNode(),i=n.dom.getParent(t.startContainer,"TD,TH");if(e(n,t,o,i)){i||(i=o);for(var r=i.lastChild;r.lastChild;)r=r.lastChild;t.setEnd(r,r.nodeValue.length),n.selection.setRng(t)}}n.on("KeyDown",function(){t()}),n.on("MouseDown",function(e){2!=e.button&&t()})}function c(){n.on("keydown",function(t){if((t.keyCode==e.DELETE||t.keyCode==e.BACKSPACE)&&!t.isDefaultPrevented()){var o=n.dom.getParent(n.selection.getStart(),"table");if(o){for(var i=n.dom.select("td,th",o),r=i.length;r--;)if(!n.dom.hasClass(i[r],"mce-item-selected"))return;t.preventDefault(),n.execCommand("mceTableDelete")}}})}c(),t.webkit&&(r(),s()),t.gecko&&(a(),l()),t.ie>10&&(a(),l())}}),o(m,[s,p,c],function(e,t,n){return function(o){function i(){o.getBody().style.webkitUserSelect="",d&&(o.dom.removeClass(o.dom.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),d=!1)}function r(t){var n,i,r=t.target;if(s&&(l||r!=s)&&("TD"==r.nodeName||"TH"==r.nodeName)){i=a.getParent(r,"table"),i==c&&(l||(l=new e(o,i),l.setStartCell(s),o.getBody().style.webkitUserSelect="none"),l.setEndCell(r),d=!0),n=o.selection.getSel();try{n.removeAllRanges?n.removeAllRanges():n.empty()}catch(u){}t.preventDefault()}}var a=o.dom,l,s,c,d=!0;return o.on("MouseDown",function(e){2!=e.button&&(i(),s=a.getParent(e.target,"td,th"),c=a.getParent(s,"table"))}),o.on("mouseover",r),o.on("remove",function(){a.unbind(o.getDoc(),"mouseover",r)}),o.on("MouseUp",function(){function e(e,o){var r=new t(e,e);do{if(3==e.nodeType&&0!==n.trim(e.nodeValue).length)return void(o?i.setStart(e,0):i.setEnd(e,e.nodeValue.length));if("BR"==e.nodeName)return void(o?i.setStartBefore(e):i.setEndBefore(e))}while(e=o?r.next():r.prev())}var i,r=o.selection,d,u,f,m,p;if(s){if(l&&(o.getBody().style.webkitUserSelect=""),d=a.select("td.mce-item-selected,th.mce-item-selected"),d.length>0){i=a.createRng(),f=d[0],p=d[d.length-1],i.setStartBefore(f),i.setEndAfter(f),e(f,1),u=new t(f,a.getParent(d[0],"table"));do if("TD"==f.nodeName||"TH"==f.nodeName){if(!a.hasClass(f,"mce-item-selected"))break;m=f}while(f=u.next());e(m),r.setRng(i)}o.nodeChanged(),s=l=c=null}}),o.on("KeyUp Drop",function(){i(),s=l=c=null}),{clear:i}}}),o(g,[s,u,m,c,p,d,h],function(e,t,n,o,i,r,a){function l(o){function i(e){return e?e.replace(/px$/,""):""}function a(e){return/^[0-9]+$/.test(e)&&(e+="px"),e}function l(e){s("left center right".split(" "),function(t){o.formatter.remove("align"+t,{},e)})}function c(e){s("top middle bottom".split(" "),function(t){o.formatter.remove("valign"+t,{},e)})}function d(){var e=o.dom,t,n,c,d;t=e.getParent(o.selection.getStart(),"table"),d={width:i(e.getStyle(t,"width")||e.getAttrib(t,"width")),height:i(e.getStyle(t,"height")||e.getAttrib(t,"height")),cellspacing:t?e.getAttrib(t,"cellspacing"):"",cellpadding:t?e.getAttrib(t,"cellpadding"):"",border:t?e.getAttrib(t,"border"):"",caption:!!e.select("caption",t)[0]},s("left center right".split(" "),function(e){o.formatter.matchNode(t,"align"+e)&&(d.align=e)}),t||(n={label:"Cols",name:"cols"},c={label:"Rows",name:"rows"}),o.windowManager.open({title:"Table properties",items:{type:"form",layout:"grid",columns:2,data:d,defaults:{type:"textbox",maxWidth:50},items:[n,c,{label:"Width",name:"width"},{label:"Height",name:"height"},{label:"Cell spacing",name:"cellspacing"},{label:"Cell padding",name:"cellpadding"},{label:"Border",name:"border"},{label:"Caption",name:"caption",type:"checkbox"},{label:"Alignment",minWidth:90,name:"align",type:"listbox",text:"None",maxWidth:null,values:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]}]},onsubmit:function(){var n=this.toJSON(),i;o.undoManager.transact(function(){t||(t=g(n.cols||1,n.rows||1)),o.dom.setAttribs(t,{cellspacing:n.cellspacing,cellpadding:n.cellpadding,border:n.border}),o.dom.setStyles(t,{width:a(n.width),height:a(n.height)}),i=e.select("caption",t)[0],i&&!n.caption&&e.remove(i),!i&&n.caption&&(i=e.create("caption"),i.innerHTML=r.ie?"\xa0":'
        ',t.insertBefore(i,t.firstChild)),l(t),n.align&&o.formatter.apply("align"+n.align,{},t),o.focus(),o.addVisual()})}})}function u(e,t){o.windowManager.open({title:"Merge cells",body:[{label:"Cols",name:"cols",type:"textbox",size:10},{label:"Rows",name:"rows",type:"textbox",size:10}],onsubmit:function(){var n=this.toJSON();o.undoManager.transact(function(){e.merge(t,n.cols,n.rows)})}})}function f(){var e=o.dom,t,n,r=[];r=o.dom.select("td.mce-item-selected,th.mce-item-selected"),t=o.dom.getParent(o.selection.getStart(),"td,th"),!r.length&&t&&r.push(t),t=t||r[0],t&&(n={width:i(e.getStyle(t,"width")||e.getAttrib(t,"width")),height:i(e.getStyle(t,"height")||e.getAttrib(t,"height")),scope:e.getAttrib(t,"scope")},n.type=t.nodeName.toLowerCase(),s("left center right".split(" "),function(e){o.formatter.matchNode(t,"align"+e)&&(n.align=e)}),s("top middle bottom".split(" "),function(e){o.formatter.matchNode(t,"valign"+e)&&(n.valign=e)}),o.windowManager.open({title:"Cell properties",items:{type:"form",data:n,layout:"grid",columns:2,defaults:{type:"textbox",maxWidth:50},items:[{label:"Width",name:"width"},{label:"Height",name:"height"},{label:"Cell type",name:"type",type:"listbox",text:"None",minWidth:90,maxWidth:null,values:[{text:"Cell",value:"td"},{text:"Header cell",value:"th"}]},{label:"Scope",name:"scope",type:"listbox",text:"None",minWidth:90,maxWidth:null,values:[{text:"None",value:""},{text:"Row",value:"row"},{text:"Column",value:"col"},{text:"Row group",value:"rowgroup"},{text:"Column group",value:"colgroup"}]},{label:"H Align",name:"align",type:"listbox",text:"None",minWidth:90,maxWidth:null,values:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]},{label:"V Align",name:"valign",type:"listbox",text:"None",minWidth:90,maxWidth:null,values:[{text:"None",value:""},{text:"Top",value:"top"},{text:"Middle",value:"middle"},{text:"Bottom",value:"bottom"}]}]},onsubmit:function(){var t=this.toJSON();o.undoManager.transact(function(){s(r,function(n){o.dom.setAttrib(n,"scope",t.scope),o.dom.setStyles(n,{width:a(t.width),height:a(t.height)}),t.type&&n.nodeName.toLowerCase()!=t.type&&(n=e.rename(n,t.type)),l(n),t.align&&o.formatter.apply("align"+t.align,{},n),c(n),t.valign&&o.formatter.apply("valign"+t.valign,{},n)}),o.focus()})}}))}function m(){var e=o.dom,t,n,r,c,d=[];t=o.dom.getParent(o.selection.getStart(),"table"),n=o.dom.getParent(o.selection.getStart(),"td,th"),s(t.rows,function(t){s(t.cells,function(o){return e.hasClass(o,"mce-item-selected")||o==n?(d.push(t),!1):void 0})}),r=d[0],r&&(c={height:i(e.getStyle(r,"height")||e.getAttrib(r,"height")),scope:e.getAttrib(r,"scope")},c.type=r.parentNode.nodeName.toLowerCase(),s("left center right".split(" "),function(e){o.formatter.matchNode(r,"align"+e)&&(c.align=e)}),o.windowManager.open({title:"Row properties",items:{type:"form",data:c,columns:2,defaults:{type:"textbox"},items:[{type:"listbox",name:"type",label:"Row type",text:"None",maxWidth:null,values:[{text:"Header",value:"thead"},{text:"Body",value:"tbody"},{text:"Footer",value:"tfoot"}]},{type:"listbox",name:"align",label:"Alignment",text:"None",maxWidth:null,values:[{text:"None",value:""},{text:"Left",value:"left"},{text:"Center",value:"center"},{text:"Right",value:"right"}]},{label:"Height",name:"height"}]},onsubmit:function(){var t=this.toJSON(),n,i,r;o.undoManager.transact(function(){var c=t.type;s(d,function(s){o.dom.setAttrib(s,"scope",t.scope),o.dom.setStyles(s,{height:a(t.height)}),c!=s.parentNode.nodeName.toLowerCase()&&(n=e.getParent(s,"table"),i=s.parentNode,r=e.select(c,n)[0],r||(r=e.create(c),n.firstChild?n.insertBefore(r,n.firstChild):n.appendChild(r)),r.appendChild(s),i.hasChildNodes()||e.remove(i)),l(s),t.align&&o.formatter.apply("align"+t.align,{},s)}),o.focus()})}}))}function p(e){return function(){o.execCommand(e)}}function g(e,t){var n,i,a;for(a='',n=0;t>n;n++){for(a+="",i=0;e>i;i++)a+="";a+=""}a+="
        "+(r.ie?" ":"
        ")+"
        ",o.insertContent(a);var l=o.dom.get("__mce");return o.dom.setAttrib(l,"id",null),l}function h(e,t){function n(){e.disabled(!o.dom.getParent(o.selection.getStart(),t)),o.selection.selectorChanged(t,function(t){e.disabled(!t)})}o.initialized?n():o.on("init",n)}function v(){h(this,"table")}function b(){h(this,"td,th")}function y(){var e="";e='';for(var t=0;10>t;t++){e+="";for(var n=0;10>n;n++)e+='';e+=""}return e+="
        ",e+='

        '}function w(e,t,n){var i=n.getEl().getElementsByTagName("table")[0],r,a,l,s,c,d=n.isRtl()||"tl-tr"==n.parent().rel;for(i.nextSibling.innerHTML=e+1+" x "+(t+1),d&&(e=9-e),a=0;10>a;a++)for(r=0;10>r;r++)s=i.rows[a].childNodes[r].firstChild,c=(d?r>=e:e>=r)&&t>=a,o.dom.toggleClass(s,"mce-active",c),c&&(l=s);return l.parentNode}var x,C,P=this;o.settings.table_grid===!1?o.addMenuItem("inserttable",{text:"Insert table",icon:"table",context:"table",onclick:d}):o.addMenuItem("inserttable",{text:"Insert table",icon:"table",context:"table",ariaHideMenu:!0,onclick:function(e){e.aria&&(this.parent().hideAll(),e.stopImmediatePropagation(),d())},onshow:function(){w(0,0,this.menu.items()[0])},onhide:function(){var e=this.menu.items()[0].getEl().getElementsByTagName("a");o.dom.removeClass(e,"mce-active"),o.dom.addClass(e[0],"mce-active")},menu:[{type:"container",html:y(),onPostRender:function(){this.lastX=this.lastY=0},onmousemove:function(e){var t=e.target,n,o;"A"==t.tagName.toUpperCase()&&(n=parseInt(t.getAttribute("data-mce-x"),10),o=parseInt(t.getAttribute("data-mce-y"),10),(this.isRtl()||"tl-tr"==this.parent().rel)&&(n=9-n),(n!==this.lastX||o!==this.lastY)&&(w(n,o,e.control),this.lastX=n,this.lastY=o))},onkeydown:function(e){var t=this.lastX,n=this.lastY,o;switch(e.keyCode){case 37:t>0&&(t--,o=!0);break;case 39:o=!0,9>t&&t++;break;case 38:o=!0,n>0&&n--;break;case 40:o=!0,9>n&&n++}o&&(e.preventDefault(),e.stopPropagation(),w(t,n,e.control).focus(),this.lastX=t,this.lastY=n)},onclick:function(e){"A"==e.target.tagName.toUpperCase()&&(e.preventDefault(),e.stopPropagation(),this.parent().cancel(),g(this.lastX+1,this.lastY+1))}}]}),o.addMenuItem("tableprops",{text:"Table properties",context:"table",onPostRender:v,onclick:d}),o.addMenuItem("deletetable",{text:"Delete table",context:"table",onPostRender:v,cmd:"mceTableDelete"}),o.addMenuItem("cell",{separator:"before",text:"Cell",context:"table",menu:[{text:"Cell properties",onclick:p("mceTableCellProps"),onPostRender:b},{text:"Merge cells",onclick:p("mceTableMergeCells"),onPostRender:b},{text:"Split cell",onclick:p("mceTableSplitCells"),onPostRender:b}]}),o.addMenuItem("row",{text:"Row",context:"table",menu:[{text:"Insert row before",onclick:p("mceTableInsertRowBefore"),onPostRender:b},{text:"Insert row after",onclick:p("mceTableInsertRowAfter"),onPostRender:b},{text:"Delete row",onclick:p("mceTableDeleteRow"),onPostRender:b},{text:"Row properties",onclick:p("mceTableRowProps"),onPostRender:b},{text:"-"},{text:"Cut row",onclick:p("mceTableCutRow"),onPostRender:b},{text:"Copy row",onclick:p("mceTableCopyRow"),onPostRender:b},{text:"Paste row before",onclick:p("mceTablePasteRowBefore"),onPostRender:b},{text:"Paste row after",onclick:p("mceTablePasteRowAfter"),onPostRender:b}]}),o.addMenuItem("column",{text:"Column",context:"table",menu:[{text:"Insert column before",onclick:p("mceTableInsertColBefore"),onPostRender:b},{text:"Insert column after",onclick:p("mceTableInsertColAfter"),onPostRender:b},{text:"Delete column",onclick:p("mceTableDeleteCol"),onPostRender:b}]});var S=[];s("inserttable tableprops deletetable | cell row column".split(" "),function(e){S.push("|"==e?{text:"-"}:o.menuItems[e])}),o.addButton("table",{type:"menubutton",title:"Table",menu:S}),r.isIE||o.on("click",function(e){e=e.target,"TABLE"===e.nodeName&&(o.selection.select(e),o.nodeChanged())}),P.quirks=new t(o),o.on("Init",function(){x=o.windowManager,P.cellSelection=new n(o)}),s({mceTableSplitCells:function(e){e.split()},mceTableMergeCells:function(e){var t,n,i;i=o.dom.getParent(o.selection.getStart(),"th,td"),i&&(t=i.rowSpan,n=i.colSpan),o.dom.select("td.mce-item-selected,th.mce-item-selected").length?e.merge():u(e,i)},mceTableInsertRowBefore:function(e){e.insertRow(!0)},mceTableInsertRowAfter:function(e){e.insertRow()},mceTableInsertColBefore:function(e){e.insertCol(!0)},mceTableInsertColAfter:function(e){e.insertCol()},mceTableDeleteCol:function(e){e.deleteCols()},mceTableDeleteRow:function(e){e.deleteRows()},mceTableCutRow:function(e){C=e.cutRows()},mceTableCopyRow:function(e){C=e.copyRows()},mceTablePasteRowBefore:function(e){e.pasteRows(C,!0)},mceTablePasteRowAfter:function(e){e.pasteRows(C)},mceTableDelete:function(e){e.deleteTable()}},function(t,n){o.addCommand(n,function(){var n=new e(o);n&&(t(n),o.execCommand("mceRepaint"),P.cellSelection.clear())})}),s({mceInsertTable:function(){d()},mceTableRowProps:m,mceTableCellProps:f},function(e,t){o.addCommand(t,function(t,n){e(n)})})}var s=o.each;a.add("table",l)}),a([s,u,m,g])}(this);tinymce.PluginManager.add("template",function(e){function t(t){return function(){var a=e.settings.templates;"string"==typeof a?tinymce.util.XHR.send({url:a,success:function(e){t(tinymce.util.JSON.parse(e))}}):t(a)}}function a(t){function a(t){function a(t){if(-1==t.indexOf("")){var a="";tinymce.each(e.contentCSS,function(t){a+=''}),t=""+a+""+t+""}t=r(t,"template_preview_replace_values");var l=n.find("iframe")[0].getEl().contentWindow.document;l.open(),l.write(t),l.close()}var c=t.control.value();c.url?tinymce.util.XHR.send({url:c.url,success:function(e){l=e,a(l)}}):(l=c.content,a(l)),n.find("#description")[0].text(t.control.value().description)}var n,l,i=[];return t&&0!==t.length?(tinymce.each(t,function(e){i.push({selected:!i.length,text:e.title,value:{url:e.url,content:e.content,description:e.description}})}),n=e.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:i,onselect:a}}]},{type:"label",name:"description",label:"Description",text:" "},{type:"iframe",flex:1,border:1}],onsubmit:function(){c(!1,l)},width:e.getParam("template_popup_width",600),height:e.getParam("template_popup_height",500)}),void n.find("listbox")[0].fire("select")):void e.windowManager.alert("No templates defined")}function n(t,a){function n(e,t){if(e=""+e,e.length0&&(o=p.create("div",null),o.appendChild(s[0].cloneNode(!0))),i(p.select("*",o),function(t){c(t,e.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_cdate_format",e.getLang("template.cdate_format")))),c(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_mdate_format",e.getLang("template.mdate_format")))),c(t,e.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(t.innerHTML=m)}),l(o),e.execCommand("mceInsertContent",!1,o.innerHTML),e.addVisual()}var i=tinymce.each;e.addCommand("mceInsertTemplate",c),e.addButton("template",{title:"Insert template",onclick:t(a)}),e.addMenuItem("template",{text:"Insert template",onclick:t(a),context:"insert"}),e.on("PreProcess",function(t){var a=e.dom;i(a.select("div",t.node),function(t){a.hasClass(t,"mceTmpl")&&(i(a.select("*",t),function(t){a.hasClass(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=n(e.getParam("template_mdate_format",e.getLang("template.mdate_format"))))}),l(t))})})});tinymce.PluginManager.add("textcolor",function(e){function t(){var t,o,l=[];for(o=e.settings.textcolor_map||["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Red violet","C0C0C0","Silver","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum","FFFFFF","White"],t=0;t',a=o.length-1,c=e.settings.textcolor_rows||5,i=e.settings.textcolor_cols||8,F=0;c>F;F++){for(r+="",n=0;i>n;n++)d=F*i+n,d>a?r+="":(l=o[d],r+='
        ');r+=""}return r+=""}function l(t){var o,l=this.parent();(o=t.target.getAttribute("data-mce-color"))&&(this.lastId&&document.getElementById(this.lastId).setAttribute("aria-selected",!1),t.target.setAttribute("aria-selected",!0),this.lastId=t.target.id,l.hidePanel(),o="#"+o,l.color(o),e.execCommand(l.settings.selectcmd,!1,o))}function r(){var t=this;t._color&&e.execCommand(t.settings.selectcmd,!1,t._color)}e.addButton("forecolor",{type:"colorbutton",tooltip:"Text color",selectcmd:"ForeColor",panel:{role:"application",ariaRemember:!0,html:o,onclick:l},onclick:r}),e.addButton("backcolor",{type:"colorbutton",tooltip:"Background color",selectcmd:"HiliteColor",panel:{role:"application",ariaRemember:!0,html:o,onclick:l},onclick:r})});tinymce.PluginManager.add("visualblocks",function(e,s){function o(){var s=this;s.active(a),e.on("VisualBlocks",function(){s.active(e.dom.hasClass(e.getBody(),"mce-visualblocks"))})}var l,t,a;window.NodeList&&(e.addCommand("mceVisualBlocks",function(){var o,c=e.dom;l||(l=c.uniqueId(),o=c.create("link",{id:l,rel:"stylesheet",href:s+"/css/visualblocks.css"}),e.getDoc().getElementsByTagName("head")[0].appendChild(o)),e.on("PreviewFormats AfterPreviewFormats",function(s){a&&c.toggleClass(e.getBody(),"mce-visualblocks","afterpreviewformats"==s.type)}),c.toggleClass(e.getBody(),"mce-visualblocks"),a=e.dom.hasClass(e.getBody(),"mce-visualblocks"),t&&t.active(c.hasClass(e.getBody(),"mce-visualblocks")),e.fire("VisualBlocks")}),e.addButton("visualblocks",{title:"Show blocks",cmd:"mceVisualBlocks",onPostRender:o}),e.addMenuItem("visualblocks",{text:"Show blocks",cmd:"mceVisualBlocks",onPostRender:o,selectable:!0,context:"view",prependToContext:!0}),e.on("init",function(){e.settings.visualblocks_default_state&&e.execCommand("mceVisualBlocks",!1,null,{skip_focus:!0})}),e.on("remove",function(){e.dom.removeClass(e.getBody(),"mce-visualblocks")}))});tinymce.PluginManager.add("visualchars",function(e){function a(a){var t,s,i,r,c,d,l=e.getBody(),m=e.selection;if(n=!n,o.state=n,e.fire("VisualChars",{state:n}),a&&(d=m.getBookmark()),n)for(s=[],tinymce.walk(l,function(e){3==e.nodeType&&e.nodeValue&&-1!=e.nodeValue.indexOf(" ")&&s.push(e)},"childNodes"),i=0;i$1'),c=e.dom.create("div",null,r);t=c.lastChild;)e.dom.insertAfter(t,s[i]);e.dom.remove(s[i])}else for(s=e.dom.select("span.mce-nbsp",l),i=s.length-1;i>=0;i--)e.dom.remove(s[i],1);m.moveToBookmark(d)}function t(){var a=this;e.on("VisualChars",function(e){a.active(e.state)})}var n,o=this;e.addCommand("mceVisualChars",a),e.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:t}),e.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:t,selectable:!0,context:"view",prependToContext:!0}),e.on("beforegetcontent",function(e){n&&"raw"!=e.format&&!e.draft&&(n=!0,a(!1))})});tinymce.PluginManager.add("wordcount",function(e){function t(){e.theme.panel.find("#wordcount").text(["Words: {0}",a.getCount()])}var n,o,a=this;n=e.getParam("wordcount_countregex",/[\w\u2019\x27\-\u00C0-\u1FFF]+/g),o=e.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),e.on("init",function(){var n=e.theme.panel&&e.theme.panel.find("#statusbar")[0];n&&window.setTimeout(function(){n.insert({type:"label",name:"wordcount",text:["Words: {0}",a.getCount()],classes:"wordcount",disabled:e.settings.readonly},0),e.on("setcontent beforeaddundo",t),e.on("keyup",function(e){32==e.keyCode&&t()})},0)}),a.getCount=function(){var t=e.getContent({format:"raw"}),a=0;if(t){t=t.replace(/\.\.\./g," "),t=t.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),t=t.replace(/(\w+)(&#?[a-z0-9]+;)+(\w+)/i,"$1$3").replace(/&.+?;/g," "),t=t.replace(o,"");var r=t.match(n);r&&(a=r.length)}return a}});tinymce.ThemeManager.add("modern",function(e){function t(){function t(t){var n,o=[];if(t)return d(t.split(/[ ,]/),function(t){function i(){var i=e.selection;"bullist"==r&&i.selectorChanged("ul > li",function(e,i){for(var n,o=i.parents.length;o--&&(n=i.parents[o].nodeName,"OL"!=n&&"UL"!=n););t.active(e&&"UL"==n)}),"numlist"==r&&i.selectorChanged("ol > li",function(e,i){for(var n,o=i.parents.length;o--&&(n=i.parents[o].nodeName,"OL"!=n&&"UL"!=n););t.active(e&&"OL"==n)}),t.settings.stateSelector&&i.selectorChanged(t.settings.stateSelector,function(e){t.active(e)},!0),t.settings.disabledStateSelector&&i.selectorChanged(t.settings.disabledStateSelector,function(e){t.disabled(e)})}var r;"|"==t?n=null:c.has(t)?(t={type:t},u.toolbar_items_size&&(t.size=u.toolbar_items_size),o.push(t),n=null):(n||(n={type:"buttongroup",items:[]},o.push(n)),e.buttons[t]&&(r=t,t=e.buttons[r],"function"==typeof t&&(t=t()),t.type=t.type||"button",u.toolbar_items_size&&(t.size=u.toolbar_items_size),t=c.create(t),n.items.push(t),e.initialized?i():e.on("init",i)))}),i.push({type:"toolbar",layout:"flow",items:o}),!0}var i=[];if(tinymce.isArray(u.toolbar)){if(0===u.toolbar.length)return;tinymce.each(u.toolbar,function(e,t){u["toolbar"+(t+1)]=e}),delete u.toolbar}for(var n=1;10>n&&t(u["toolbar"+n]);n++);return i.length||u.toolbar===!1||t(u.toolbar||f),i.length?{type:"panel",layout:"stack",classes:"toolbar-grp",ariaRoot:!0,ariaRemember:!0,items:i}:void 0}function i(){function t(t){var i;return"|"==t?{text:"|"}:i=e.menuItems[t]}function i(i){var n,o,r,a,s;if(s=tinymce.makeMap((u.removed_menuitems||"").split(/[ ,]/)),u.menu?(o=u.menu[i],a=!0):o=h[i],o){n={text:o.title},r=[],d((o.items||"").split(/[ ,]/),function(e){var i=t(e);i&&!s[e]&&r.push(t(e))}),a||d(e.menuItems,function(e){e.context==i&&("before"==e.separator&&r.push({text:"|"}),e.prependToContext?r.unshift(e):r.push(e),"after"==e.separator&&r.push({text:"|"}))});for(var l=0;l -screen test +screen frame1 diff --git a/lib/test/data/screen/screen_frame2.html b/lib/test/data/screen/screen_frame2.html index b66cd70..e6e17e6 100644 --- a/lib/test/data/screen/screen_frame2.html +++ b/lib/test/data/screen/screen_frame2.html @@ -1,6 +1,6 @@ -screen test +screen frame2 diff --git a/lib/test/data/slowLoadingResourcePage.html b/lib/test/data/slowLoadingResourcePage.html index e05f954..02796c3 100644 --- a/lib/test/data/slowLoadingResourcePage.html +++ b/lib/test/data/slowLoadingResourcePage.html @@ -7,6 +7,6 @@ too long to respond. Normally these things are loaded in an iframe, which is what we're doing here.

        - + - \ No newline at end of file + diff --git a/lib/test/data/tinymce.html b/lib/test/data/tinymce.html new file mode 100644 index 0000000..067b66c --- /dev/null +++ b/lib/test/data/tinymce.html @@ -0,0 +1,10 @@ + + + + TinyMCE + + + + + + \ No newline at end of file diff --git a/lib/test/data/transparentUpload.html b/lib/test/data/transparentUpload.html new file mode 100644 index 0000000..87b02bf --- /dev/null +++ b/lib/test/data/transparentUpload.html @@ -0,0 +1,70 @@ + + + + Upload Form + + + + +
        +
        +
        + Upload + +
        +
        +
        + + +
        + + diff --git a/lib/test/fileserver.js b/lib/test/fileserver.js index a4165df..6a86947 100644 --- a/lib/test/fileserver.js +++ b/lib/test/fileserver.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -20,18 +22,21 @@ var fs = require('fs'), path = require('path'), url = require('url'); +var express = require('express'); +var multer = require('multer'); +var serveIndex = require('serve-index'); + var Server = require('./httpserver').Server, resources = require('./resources'), promise = require('../..').promise, isDevMode = require('../../_base').isDevMode(), string = require('../../_base').require('goog.string'); -var WEB_ROOT = isDevMode ? '/common/src/web' : '/common'; +var WEB_ROOT = '/common'; var JS_ROOT = '/javascript'; -var baseDirectory = resources.locate('.'), - server = new Server(onRequest); - +var baseDirectory = resources.locate(isDevMode ? 'common/src/web' : '.'); +var jsDirectory = resources.locate(isDevMode ? 'javascript' : '..'); var Pages = (function() { var pages = {}; @@ -58,6 +63,7 @@ var Pages = (function() { addPage('documentWrite', 'document_write_in_onload.html'); addPage('dynamicallyModifiedPage', 'dynamicallyModifiedPage.html'); addPage('dynamicPage', 'dynamic.html'); + addPage('echoPage', 'echo'); addPage('errorsPage', 'errors.html'); addPage('xhtmlFormPage', 'xhtmlFormPage.xhtml'); addPage('formPage', 'formPage.html'); @@ -102,93 +108,41 @@ var Pages = (function() { var Path = { - BASIC_AUTH: path.join(WEB_ROOT, 'basicAuth'), - MANIFEST: path.join(WEB_ROOT, 'manifest'), - REDIRECT: path.join(WEB_ROOT, 'redirect'), - PAGE: path.join(WEB_ROOT, 'page'), - SLEEP: path.join(WEB_ROOT, 'sleep'), - UPLOAD: path.join(WEB_ROOT, 'upload') + BASIC_AUTH: WEB_ROOT + '/basicAuth', + ECHO: WEB_ROOT + '/echo', + GENERATED: WEB_ROOT + '/generated', + MANIFEST: WEB_ROOT + '/manifest', + REDIRECT: WEB_ROOT + '/redirect', + PAGE: WEB_ROOT + '/page', + SLEEP: WEB_ROOT + '/sleep', + UPLOAD: WEB_ROOT + '/upload' }; - -/** - * HTTP request handler. - * @param {!http.ServerRequest} request The request object. - * @param {!http.ServerResponse} response The response object. - */ -function onRequest(request, response) { - if (request.method !== 'GET' && request.method !== 'HEAD') { - response.writeHead(405, {'Allowed': 'GET,HEAD'}); - return response.end(); - } - - var pathname = path.resolve(url.parse(request.url).pathname); - if (pathname === '/') { - return sendIndex(request, response); - } - - if (pathname === '/favicon.ico') { - response.writeHead(204); - return response.end(); - } - - switch (pathname) { - case Path.BASIC_AUTH: return sendBasicAuth(response); - case Path.MANIFEST: return sendManifest(response); - case Path.PAGE: return sendInifinitePage(request, response); - case Path.REDIRECT: return redirectToResultPage(response); - case Path.SLEEP: return sendDelayedResponse(request, response); - case Path.UPLOAD: return sendUpload(response); - } - - if (string.startsWith(pathname, Path.PAGE + '/')) { - return sendInifinitePage(request, response); - } - - if ((string.startsWith(pathname, WEB_ROOT) || - string.startsWith(pathname, JS_ROOT)) && - string.endsWith(pathname, '.appcache')) { - return sendManifest(response); - } - - if (string.startsWith(pathname, WEB_ROOT)) { - if (!isDevMode) { - pathname = pathname.substring(WEB_ROOT.length); - } - } else if (string.startsWith(pathname, JS_ROOT)) { - if (!isDevMode) { - sendSimpleError(response, 404, request.url); - return; - } - pathname = pathname.substring(JS_ROOT); - } - - try { - var fullPath = resources.locate(pathname); - } catch (ex) { - fullPath = ''; - } - - if (fullPath.lastIndexOf(baseDirectory, 0) == -1) { - sendSimpleError(response, 404, request.url); - return; - } - - fs.stat(fullPath, function(err, stats) { - if (err) { - sendIOError(request, response, err); - } else if (stats.isDirectory()) { - sendDirectoryListing(request, response, fullPath); - } else if (stats.isFile()) { - sendFile(request, response, fullPath); - } else { - sendSimpleError(response, 404, request.url); - } - }); +var app = express(); + +app.get('/', sendIndex) +.get('/favicon.ico', function(req, res) { + res.writeHead(204); + res.end(); +}) +.use(JS_ROOT, serveIndex(jsDirectory), express.static(jsDirectory)) +.post(Path.UPLOAD, handleUpload) +.use(WEB_ROOT, serveIndex(baseDirectory), express.static(baseDirectory)) +.get(Path.ECHO, sendEcho) +.get(Path.PAGE, sendInifinitePage) +.get(Path.PAGE + '/*', sendInifinitePage) +.get(Path.REDIRECT, redirectToResultPage) +.get(Path.SLEEP, sendDelayedResponse) + +if (isDevMode) { + var closureDir = resources.locate('third_party/closure/goog'); + app.use('/third_party/closure/goog', + serveIndex(closureDir), express.static(closureDir)); } +var server = new Server(app); -function redirectToResultPage(response) { +function redirectToResultPage(_, response) { response.writeHead(303, { Location: Pages.resultPage }); @@ -197,23 +151,21 @@ function redirectToResultPage(response) { function sendInifinitePage(request, response) { - setTimeout(function() { - var pathname = url.parse(request.url).pathname; - var lastIndex = pathname.lastIndexOf('/'); - var pageNumber = - (lastIndex == -1 ? 'Unknown' : pathname.substring(lastIndex + 1)); - var body = [ - '', - 'Page', pageNumber, '', - 'Page number ', pageNumber, '', - '

        top' - ].join(''); - response.writeHead(200, { - 'Content-Length': Buffer.byteLength(body, 'utf8'), - 'Content-Type': 'text/html; charset=utf-8' - }); - response.end(body); - }, 500); + var pathname = url.parse(request.url).pathname; + var lastIndex = pathname.lastIndexOf('/'); + var pageNumber = + (lastIndex == -1 ? 'Unknown' : pathname.substring(lastIndex + 1)); + var body = [ + '', + 'Page', pageNumber, '', + 'Page number ', pageNumber, '', + '

        top' + ].join(''); + response.writeHead(200, { + 'Content-Length': Buffer.byteLength(body, 'utf8'), + 'Content-Type': 'text/html; charset=utf-8' + }); + response.end(body); } @@ -222,7 +174,7 @@ function sendDelayedResponse(request, response) { var query = url.parse(request.url).query || ''; var match = query.match(/\btime=(\d+)/); if (match) { - duration = parseInt(match[1]); + duration = parseInt(match[1], 10); } setTimeout(function() { @@ -243,74 +195,36 @@ function sendDelayedResponse(request, response) { } -/** - * Sends an error in response to an I/O operation. - * @param {!http.ServerRequest} request The request object. - * @param {!http.ServerResponse} response The response object. - * @param {!Error} err The I/O error. - */ -function sendIOError(request, response, err) { - var code = 500; - if (err.code === 'ENOENT') { - code = 404; - } else if (err.code === 'EACCES') { - code = 403; - } - sendSimpleError(response, code, request.url); -} - - -/** - * Sends a simple error message to the client and instructs it to close the - * connection. - * @param {!http.ServerResponse} response The response to populate. - * @param {number} code The numeric HTTP code to send. - * @param {string} message The error message. - */ -function sendSimpleError(response, code, message) { - response.writeHead(code, { - 'Content-Type': 'text/html; charset=utf-8', - 'Connection': 'close' - }); - response.end( - '

        ' + code + ' ' + http.STATUS_CODES[code] + - '


        ' + message); +function handleUpload(request, response, next) { + multer({ + inMemory: true, + onFileUploadComplete: function(file) { + response.writeHead(200); + response.write(file.buffer); + response.end(''); + } + })(request, response, function() {}); } -var MimeType = { - 'css': 'text/css', - 'gif': 'image/gif', - 'html': 'text/html', - 'js': 'application/javascript', - 'png': 'image/png', - 'svg': 'image/svg+xml', - 'txt': 'text/plain', - 'xhtml': 'application/xhtml+xml', - 'xsl': 'application/xml', - 'xml': 'application/xml' -}; - - -/** - * Responds to a request for an individual file. - * @param {!http.ServerRequest} request The request object. - * @param {!http.ServerResponse} response The response object. - * @param {string} filePath Path to the file to return. - */ -function sendFile(request, response, filePath) { - fs.readFile(filePath, function(err, buffer) { - if (err) { - sendIOError(request, response, err); - return; - } - var index = filePath.lastIndexOf('.'); - var type = MimeType[index < 0 ? '' : filePath.substring(index + 1)]; - var headers = {'Content-Length': buffer.length}; - if (type) headers['Content-Type'] = type; - response.writeHead(200, headers); - response.end(buffer); +function sendEcho(request, response) { + var body = [ + '', + 'Echo', + '
        ', + request.method, ' ', request.url, ' ', 'HTTP/', request.httpVersion, + '
        ' + ]; + for (var name in request.headers) { + body.push('
        ', + name, ': ', request.headers[name], '
        '); + } + body = body.join(''); + response.writeHead(200, { + 'Content-Length': Buffer.byteLength(body, 'utf8'), + 'Content-Type': 'text/html; charset=utf-8' }); + response.end(body); } @@ -350,69 +264,6 @@ function sendIndex(request, response) { } -/** - * Responds to a request for a directory listing. - * @param {!http.ServerRequest} request The request object. - * @param {!http.ServerResponse} response The response object. - * @param {string} dirPath Path to the directory to generate a listing for. - */ -function sendDirectoryListing(request, response, dirPath) { - var pathname = url.parse(request.url).pathname; - - var host = request.headers.host; - if (!host) { - host = server.host(); - } - - var requestUrl = ['http://' + host + pathname].join(''); - if (requestUrl[requestUrl.length - 1] !== '/') { - response.writeHead(303, {'Location': requestUrl + '/'}); - return response.end(); - } - - fs.readdir(dirPath, function(err, files) { - if (err) { - sendIOError(request, response, err); - return; - } - - var data = ['

        ', pathname, '


          ']; - if (pathname !== '/') { - data.push(createListEntry('../')); - } - processNextFile(); - - function processNextFile() { - var file = files.shift(); - if (file) { - fs.stat(path.join(dirPath, file), function(err, stats) { - if (err) { - sendIOError(request, response, err); - return; - } - - data.push(createListEntry( - stats.isDirectory() ? file + '/' : file)); - processNextFile(); - }); - } else { - data = new Buffer(data.join(''), 'utf-8'); - response.writeHead(200, { - 'Content-Type': 'text/html; charset=utf-8', - 'Content-Length': data.length - }); - response.end(data); - } - } - - function createListEntry(path) { - var url = requestUrl + path; - return ['
        • ', path, ''].join(''); - } - }); -} - - // PUBLIC application @@ -451,7 +302,11 @@ exports.url = server.url.bind(server); * @throws {Error} If the server is not running. */ exports.whereIs = function(filePath) { - return server.url(path.join(WEB_ROOT, filePath)); + filePath = filePath.replace(/\\/g, '/'); + if (!string.startsWith(filePath, '/')) { + filePath = '/' + filePath; + } + return server.url(WEB_ROOT + filePath); }; diff --git a/lib/test/httpserver.js b/lib/test/httpserver.js index e75298e..55b1255 100644 --- a/lib/test/httpserver.js +++ b/lib/test/httpserver.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -19,7 +21,8 @@ var assert = require('assert'), http = require('http'), url = require('url'); -var portprober = require('../../net/portprober'), +var net = require('../../net'), + portprober = require('../../net/portprober'), promise = require('../..').promise; @@ -51,6 +54,8 @@ var Server = function(requestHandler) { * with the server host when it has fully started. */ this.start = function(opt_port) { + assert(typeof opt_port !== 'function', + "start invoked with function, not port (mocha callback)?"); var port = opt_port || portprober.findFreePort('localhost'); return promise.when(port, function(port) { return promise.checkedNodeCall( @@ -88,8 +93,8 @@ var Server = function(requestHandler) { * @throws {Error} If the server is not running. */ this.host = function() { - var addr = this.address(); - return addr.address + ':' + addr.port; + return net.getLoopbackAddress() + ':' + + this.address().port; }; /** @@ -103,7 +108,7 @@ var Server = function(requestHandler) { var pathname = opt_pathname || ''; return url.format({ protocol: 'http', - hostname: addr.address, + hostname: net.getLoopbackAddress(), port: addr.port, pathname: pathname }); diff --git a/lib/test/index.js b/lib/test/index.js index f328bee..f6ccc11 100644 --- a/lib/test/index.js +++ b/lib/test/index.js @@ -1,74 +1,98 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; var assert = require('assert'); -var webdriver = require('../..'), +var build = require('./build'), + webdriver = require('../..'), flow = webdriver.promise.controlFlow(), + _base = require('../../_base'), + remote = require('../../remote'), testing = require('../../testing'), - fileserver = require('./fileserver'), - seleniumserver = require('./seleniumserver'); - - -var Browser = { - ANDROID: 'android', - CHROME: 'chrome', - IE: 'internet explorer', - // Shorthand for IPAD && IPHONE when using the browsers predciate. - IOS: 'iOS', - IPAD: 'iPad', - IPHONE: 'iPhone', - FIREFOX: 'firefox', - OPERA: 'opera', - PHANTOMJS: 'phantomjs', - SAFARI: 'safari', - - // Browsers that should always be tested via the java Selenium server. - REMOTE_CHROME: 'remote.chrome', - REMOTE_PHANTOMJS: 'remote.phantomjs' -}; + fileserver = require('./fileserver'); /** * Browsers with native support. - * @type {!Array.} + * @type {!Array.} */ var NATIVE_BROWSERS = [ - Browser.CHROME, - Browser.PHANTOMJS + webdriver.Browser.CHROME, + webdriver.Browser.FIREFOX, + webdriver.Browser.IE, + webdriver.Browser.OPERA, + webdriver.Browser.PHANTOM_JS, + webdriver.Browser.SAFARI ]; +var serverJar = process.env['SELENIUM_SERVER_JAR']; +var remoteUrl = process.env['SELENIUM_REMOTE_URL']; +var startServer = !!serverJar && !remoteUrl; +var nativeRun = !serverJar && !remoteUrl; + + var browsersToTest = (function() { - var browsers = process.env['SELENIUM_BROWSER'] || Browser.CHROME; - browsers = browsers.split(','); + var permitRemoteBrowsers = !!remoteUrl || !!serverJar; + var permitUnknownBrowsers = !nativeRun; + var browsers = process.env['SELENIUM_BROWSER'] || webdriver.Browser.FIREFOX; + + browsers = browsers.split(',').map(function(browser) { + var parts = browser.split(/:/); + if (parts[0] === 'ie') { + parts[0] = webdriver.Browser.IE; + } + return parts.join(':'); + }); browsers.forEach(function(browser) { - if (browser === Browser.IOS) { - throw Error('Invalid browser name: ' + browser); + var parts = browser.split(/:/, 3); + if (parts[0] === 'ie') { + parts[0] = webdriver.Browser.IE; + } + + if (NATIVE_BROWSERS.indexOf(parts[0]) == -1 && !permitRemoteBrowsers) { + throw Error('Browser ' + parts[0] + ' requires a WebDriver server and ' + + 'neither the SELENIUM_REMOTE_URL nor the SELENIUM_SERVER_JAR ' + + 'environment variables have been set.'); } - for (var name in Browser) { - if (Browser.hasOwnProperty(name) && Browser[name] === browser) { - return; + var recognized = false; + for (var prop in webdriver.Browser) { + if (webdriver.Browser.hasOwnProperty(prop) && + webdriver.Browser[prop] === parts[0]) { + recognized = true; + break; } } - throw Error('Unrecognized browser: ' + browser); + if (!recognized && !permitUnknownBrowsers) { + throw Error('Unrecognized browser: ' + browser); + } }); + + console.log('Running tests against [' + browsers.join(',') + ']'); + if (remoteUrl) { + console.log('Using remote server ' + remoteUrl); + } else if (serverJar) { + console.log('Using standalone Selenium server ' + serverJar); + } + return browsers; })(); @@ -81,10 +105,7 @@ var browsersToTest = (function() { */ function browsers(currentBrowser, browsersToIgnore) { return function() { - var checkIos = - currentBrowser === Browser.IPAD || currentBrowser === Browser.IPHONE; - return browsersToIgnore.indexOf(currentBrowser) != -1 || - (checkIos && browsersToIgnore.indexOf(Browser.IOS) != -1); + return browsersToIgnore.indexOf(currentBrowser) != -1; }; } @@ -96,80 +117,38 @@ function browsers(currentBrowser, browsersToIgnore) { */ function TestEnvironment(browserName, server) { var name = browserName; - if (name.lastIndexOf('remote.', 0) == 0) { - name = name.substring('remote.'.length); - } - var autoCreate = true; - this.__defineGetter__( - 'autoCreateDriver', function() { return autoCreate; }); - this.__defineSetter__( - 'autoCreateDriver', function(auto) { autoCreate = auto; }); - - this.__defineGetter__('browser', function() { return name; }); + this.currentBrowser = function() { + return browserName; + }; - var driver; - this.__defineGetter__('driver', function() { return driver; }); + this.isRemote = function() { + return server || remoteUrl; + }; this.browsers = function(var_args) { var browsersToIgnore = Array.prototype.slice.apply(arguments, [0]); - var remoteVariants = []; - browsersToIgnore.forEach(function(browser) { - if (browser.lastIndexOf('remote.', 0) === 0) { - remoteVariants.push(browser.substring('remote.'.length)); - } - }); - browsersToIgnore = browsersToIgnore.concat(remoteVariants); return browsers(browserName, browsersToIgnore); }; this.builder = function() { - assert.ok(!driver, 'Can only have one driver at a time'); var builder = new webdriver.Builder(); var realBuild = builder.build; builder.build = function() { - builder.getCapabilities(). - set(webdriver.Capability.BROWSER_NAME, name); - + var parts = browserName.split(/:/, 3); + builder.forBrowser(parts[0], parts[1], parts[2]); if (server) { builder.usingServer(server.address()); + } else if (remoteUrl) { + builder.usingServer(remoteUrl); } - return driver = realBuild.call(builder); + builder.disableEnvironmentOverrides(); + return realBuild.call(builder); }; return builder; }; - - this.createDriver = function() { - if (!driver) { - driver = this.builder().build(); - } - return driver; - }; - - this.refreshDriver = function() { - if (driver) { - driver.quit(); - driver = null; - } - this.createDriver(); - }; - - this.dispose = function() { - if (driver) { - driver.quit(); - driver = null; - } - }; - - this.waitForTitleToBe = function(expected) { - driver.wait(function() { - return driver.getTitle().then(function(title) { - return title === expected; - }); - }, 5000, 'Waiting for title to be ' + expected); - }; } @@ -193,43 +172,51 @@ function suite(fn, opt_options) { // Filter out browser specific tests when that browser is not currently // selected for testing. browsers = browsers.filter(function(browser) { - if (browsersToTest.indexOf(browser) != -1) { - return true; - } - return browsersToTest.indexOf( - browser.substring('remote.'.length)) != -1; + return browsersToTest.indexOf(browser) != -1; }); } else { browsers = browsersToTest; } try { - browsers.forEach(function(browser) { + // Server is only started if required for a specific config. + testing.after(function() { + if (seleniumServer) { + return seleniumServer.stop(); + } + }); + + browsers.forEach(function(browser) { testing.describe('[' + browser + ']', function() { - var serverToUse = null; - if (NATIVE_BROWSERS.indexOf(browser) == -1) { - serverToUse = seleniumServer; - if (!serverToUse) { - serverToUse = seleniumServer = new seleniumserver.Server(); + if (_base.isDevMode() && nativeRun) { + if (browser === webdriver.Browser.FIREFOX) { + testing.before(function() { + return build.of('//javascript/firefox-driver:webdriver') + .onlyOnce().go(); + }); + } else if (browser === webdriver.Browser.SAFARI) { + testing.before(function() { + return build.of('//javascript/safari-driver:client') + .onlyOnce().go(); + }); } - testing.before(seleniumServer.start.bind(seleniumServer, 60 * 1000)); } - var env = new TestEnvironment(browser, serverToUse); + var serverToUse = null; - testing.beforeEach(function() { - if (env.autoCreateDriver) { - env.createDriver(); + if (!!serverJar && !remoteUrl) { + if (!(serverToUse = seleniumServer)) { + serverToUse = seleniumServer = new remote.SeleniumServer(serverJar); } - }); - - testing.after(function() { - env.dispose(); - }); - fn(env); + testing.before(function() { + this.timeout(0); + return seleniumServer.start(60 * 1000); + }); + } + fn(new TestEnvironment(browser, serverToUse)); }); }); } finally { @@ -240,18 +227,17 @@ function suite(fn, opt_options) { // GLOBAL TEST SETUP +testing.before(function() { + // Do not pass register fileserver.start directly with testing.before, + // as start takes an optional port, which before assumes is an async + // callback. + return fileserver.start(); +}); -testing.before(fileserver.start); -testing.after(fileserver.stop); - -// Server is only started if required for a specific config. testing.after(function() { - if (seleniumServer) { - seleniumServer.stop(); - } + return fileserver.stop(); }); - // PUBLIC API @@ -263,6 +249,5 @@ exports.beforeEach = testing.beforeEach; exports.it = testing.it; exports.ignore = testing.ignore; -exports.Browser = Browser; exports.Pages = fileserver.Pages; exports.whereIs = fileserver.whereIs; diff --git a/lib/test/resources.js b/lib/test/resources.js index 2f95828..ccd0eeb 100644 --- a/lib/test/resources.js +++ b/lib/test/resources.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; diff --git a/lib/test/seleniumserver.js b/lib/test/seleniumserver.js index 932a02a..7925972 100644 --- a/lib/test/seleniumserver.js +++ b/lib/test/seleniumserver.js @@ -17,6 +17,7 @@ var assert = require('assert'), fs = require('fs'), + path = require('path'), util = require('util'); var promise = require('../..').promise, @@ -25,8 +26,8 @@ var promise = require('../..').promise, build = require('./build'); -var DEV_MODE_JAR_PATH = - 'build/java/server/src/org/openqa/grid/selenium/selenium-standalone.jar'; +var DEV_MODE_JAR_PATH = path.join(__dirname, '../../../../..', + 'build/java/server/src/org/openqa/grid/selenium/selenium-standalone.jar'); var SELENIUM_SERVER_JAR_ENV = 'SELENIUM_SERVER_JAR'; var PROD_MODE_JAR_PATH = process.env[SELENIUM_SERVER_JAR_ENV]; @@ -59,7 +60,8 @@ function getProdModeJarPath() { function Server() { var jarPath = isDevMode ? DEV_MODE_JAR_PATH : getProdModeJarPath(); RemoteServer.call(this, jarPath, { - port: 0 + port: 0, + stdio: 'inherit' }); } util.inherits(Server, RemoteServer); diff --git a/lib/webdriver/abstractbuilder.js b/lib/webdriver/abstractbuilder.js index f535f47..274f81a 100644 --- a/lib/webdriver/abstractbuilder.js +++ b/lib/webdriver/abstractbuilder.js @@ -114,6 +114,19 @@ webdriver.AbstractBuilder.prototype.getCapabilities = function() { }; +/** + * Sets the logging preferences for the created session. Preferences may be + * changed by repeated calls, or by calling {@link #withCapabilities}. + * @param {!(webdriver.logging.Preferences|Object.)} prefs The + * desired logging preferences. + * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling. + */ +webdriver.AbstractBuilder.prototype.setLoggingPreferences = function(prefs) { + this.capabilities_.set(webdriver.Capability.LOGGING_PREFS, prefs); + return this; +}; + + /** * Builds a new {@link webdriver.WebDriver} instance using this builder's * current configuration. diff --git a/lib/webdriver/actionsequence.js b/lib/webdriver/actionsequence.js index cd42979..28106ac 100644 --- a/lib/webdriver/actionsequence.js +++ b/lib/webdriver/actionsequence.js @@ -1,17 +1,19 @@ -// Copyright 2012 Selenium comitters -// Copyright 2012 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.ActionSequence'); @@ -27,15 +29,15 @@ goog.require('webdriver.Key'); * Class for defining sequences of complex user interactions. Each sequence * will not be executed until {@link #perform} is called. * - *

          Example:

          
          - *   new webdriver.ActionSequence(driver).
          - *       keyDown(webdriver.Key.SHIFT).
          - *       click(element1).
          - *       click(element2).
          - *       dragAndDrop(element3, element4).
          - *       keyUp(webdriver.Key.SHIFT).
          - *       perform();
          - * 
          + * Example: + * + * new webdriver.ActionSequence(driver). + * keyDown(webdriver.Key.SHIFT). + * click(element1). + * click(element2). + * dragAndDrop(element3, element4). + * keyUp(webdriver.Key.SHIFT). + * perform(); * * @param {!webdriver.WebDriver} driver The driver instance to use. * @constructor @@ -103,13 +105,7 @@ webdriver.ActionSequence.prototype.mouseMove = function(location, opt_offset) { if (goog.isNumber(location.x)) { setOffset(/** @type {{x: number, y: number}} */(location)); } else { - // The interactions API expect the element ID to be encoded as a simple - // string, not the usual JSON object. - var id = /** @type {!webdriver.WebElement} */ (location).toWireValue(). - then(function(value) { - return value['ELEMENT']; - }); - command.setParameter('element', id); + command.setParameter('element', location.getRawId()); if (opt_offset) { setOffset(opt_offset); } @@ -167,12 +163,13 @@ webdriver.ActionSequence.prototype.scheduleMouseAction_ = function( * sequence or another. The behavior for out-of-order events (e.g. mouseDown, * click) is undefined. * - *

          If an element is provided, the mouse will first be moved to the center + * If an element is provided, the mouse will first be moved to the center * of that element. This is equivalent to: - *

          sequence.mouseMove(element).mouseDown()
          * - *

          Warning: this method currently only supports the left mouse button. See - * http://code.google.com/p/selenium/issues/detail?id=4047 + * sequence.mouseMove(element).mouseDown() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). * * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either * the element to interact with or the button to click with. @@ -194,12 +191,13 @@ webdriver.ActionSequence.prototype.mouseDown = function(opt_elementOrButton, * Releases a mouse button. Behavior is undefined for calling this function * without a previous call to {@link #mouseDown}. * - *

          If an element is provided, the mouse will first be moved to the center + * If an element is provided, the mouse will first be moved to the center * of that element. This is equivalent to: - *

          sequence.mouseMove(element).mouseUp()
          * - *

          Warning: this method currently only supports the left mouse button. See - * http://code.google.com/p/selenium/issues/detail?id=4047 + * sequence.mouseMove(element).mouseUp() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). * * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either * the element to interact with or the button to click with. @@ -234,9 +232,10 @@ webdriver.ActionSequence.prototype.dragAndDrop = function(element, location) { /** * Clicks a mouse button. * - *

          If an element is provided, the mouse will first be moved to the center + * If an element is provided, the mouse will first be moved to the center * of that element. This is equivalent to: - *

          sequence.mouseMove(element).click()
          + * + * sequence.mouseMove(element).click() * * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either * the element to interact with or the button to click with. @@ -257,12 +256,13 @@ webdriver.ActionSequence.prototype.click = function(opt_elementOrButton, /** * Double-clicks a mouse button. * - *

          If an element is provided, the mouse will first be moved to the center of + * If an element is provided, the mouse will first be moved to the center of * that element. This is equivalent to: - *

          sequence.mouseMove(element).doubleClick()
          * - *

          Warning: this method currently only supports the left mouse button. See - * http://code.google.com/p/selenium/issues/detail?id=4047 + * sequence.mouseMove(element).doubleClick() + * + * Warning: this method currently only supports the left mouse button. See + * [issue 4047](http://code.google.com/p/selenium/issues/detail?id=4047). * * @param {(webdriver.WebElement|webdriver.Button)=} opt_elementOrButton Either * the element to interact with or the button to click with. diff --git a/lib/webdriver/builder.js b/lib/webdriver/builder.js index 28d1721..27e7eaf 100644 --- a/lib/webdriver/builder.js +++ b/lib/webdriver/builder.js @@ -1,70 +1,145 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.Builder'); +goog.require('goog.Uri'); goog.require('goog.userAgent'); -goog.require('webdriver.AbstractBuilder'); +goog.require('webdriver.Capabilities'); goog.require('webdriver.FirefoxDomExecutor'); goog.require('webdriver.WebDriver'); goog.require('webdriver.http.CorsClient'); goog.require('webdriver.http.Executor'); goog.require('webdriver.http.XhrClient'); -goog.require('webdriver.process'); /** + * Creates new {@code webdriver.WebDriver} clients for use in a browser + * environment. Upon instantiation, each Builder will configure itself based + * on the following query parameters: + *

          + *
          wdurl + *
          Defines the WebDriver server to send commands to. If this is a + * relative URL, the builder will use the standard WebDriver wire + * protocol and a {@link webdriver.http.XhrClient}. Otherwise, it will + * use a {@link webdriver.http.CorsClient}; this only works when + * connecting to an instance of the Java Selenium server. The server URL + * may be changed using {@code #usingServer}. + * + *
          wdsid + *
          Defines the session to connect to. If omitted, will request a new + * session from the server. + *
          + * + * @param {Window=} opt_window The window to extract query parameters from. * @constructor - * @extends {webdriver.AbstractBuilder} + * @final + * @struct */ -webdriver.Builder = function() { - goog.base(this); - - /** - * ID of an existing WebDriver session that new clients should use. - * Initialized from the value of the - * {@link webdriver.AbstractBuilder.SESSION_ID_ENV} environment variable, but - * may be overridden using - * {@link webdriver.AbstractBuilder#usingSession}. - * @private {string} - */ +webdriver.Builder = function(opt_window) { + var win = opt_window || window; + var data = new goog.Uri(win.location).getQueryData(); + + /** @private {string} */ + this.serverUrl_ = + /** @type {string} */ (data.get(webdriver.Builder.SERVER_URL_PARAM, + webdriver.Builder.DEFAULT_SERVER_URL)).replace(/\/$/, ""); + + /** @private {string} */ this.sessionId_ = - webdriver.process.getEnv(webdriver.Builder.SESSION_ID_ENV); + /** @type {string} */ (data.get(webdriver.Builder.SESSION_ID_PARAM)); + + /** @private {boolean} */ + this.useBrowserCors_ = + /** @type {boolean} */ (data.containsKey(webdriver.Builder.USE_BROWSER_CORS)); + + /** @private {!webdriver.Capabilities} */ + this.capabilities_ = new webdriver.Capabilities(); }; -goog.inherits(webdriver.Builder, webdriver.AbstractBuilder); /** - * Environment variable that defines the session ID of an existing WebDriver - * session to use when creating clients. If set, all new Builder instances will - * default to creating clients that use this session. To create a new session, - * use {@code #useExistingSession(boolean)}. The use of this environment - * variable requires that {@link webdriver.AbstractBuilder.SERVER_URL_ENV} also - * be set. + * Query parameter that defines which session to connect to. + * @type {string} + * @const + */ +webdriver.Builder.SESSION_ID_PARAM = 'wdsid'; + + +/** + * Query parameter that defines the URL of the remote server to connect to. + * @type {string} + * @const + */ +webdriver.Builder.SERVER_URL_PARAM = 'wdurl'; + + +/** + * The default server URL to use. + * @type {string} + * @const + */ +webdriver.Builder.DEFAULT_SERVER_URL = 'http://localhost:4444/wd/hub'; + + +/** + * Query parameter that defines whether browser CORS support should be used, + * if available. * @type {string} * @const - * @see webdriver.process.getEnv */ -webdriver.Builder.SESSION_ID_ENV = 'wdsid'; +webdriver.Builder.USE_BROWSER_CORS = 'wdcors'; + + +/** + * Configures the WebDriver to use browser's CORS, if available. + * @return {!webdriver.Builder} This Builder instance for chain calling. + */ +webdriver.Builder.prototype.useBrowserCors = function() { + this.useBrowserCors_ = true; + return this; +}; + +/** + * Configures which WebDriver server should be used for new sessions. + * @param {string} url URL of the server to use. + * @return {!webdriver.Builder} This Builder instance for chain calling. + */ +webdriver.Builder.prototype.usingServer = function(url) { + this.serverUrl_ = url.replace(/\/$/, ""); + return this; +}; + + +/** + * @return {string} The URL of the WebDriver server this instance is configured + * to use. + */ +webdriver.Builder.prototype.getServerUrl = function() { + return this.serverUrl_; +}; /** * Configures the builder to create a client that will use an existing WebDriver * session. * @param {string} id The existing session ID to use. - * @return {!webdriver.AbstractBuilder} This Builder instance for chain calling. + * @return {!webdriver.Builder} This Builder instance for chain calling. */ webdriver.Builder.prototype.usingSession = function(id) { this.sessionId_ = id; @@ -82,7 +157,22 @@ webdriver.Builder.prototype.getSession = function() { /** - * @override + * Sets the desired capabilities when requesting a new session. This will + * overwrite any previously set desired capabilities. + * @param {!(Object|webdriver.Capabilities)} capabilities The desired + * capabilities for a new session. + * @return {!webdriver.Builder} This Builder instance for chain calling. + */ +webdriver.Builder.prototype.withCapabilities = function(capabilities) { + this.capabilities_ = new webdriver.Capabilities(capabilities); + return this; +}; + + +/** + * Builds a new {@link webdriver.WebDriver} instance using this builder's + * current configuration. + * @return {!webdriver.WebDriver} A new WebDriver client. */ webdriver.Builder.prototype.build = function() { if (goog.userAgent.GECKO && document.readyState != 'complete') { @@ -93,12 +183,13 @@ webdriver.Builder.prototype.build = function() { if (webdriver.FirefoxDomExecutor.isAvailable()) { executor = new webdriver.FirefoxDomExecutor(); - return webdriver.WebDriver.createSession(executor, this.getCapabilities()); + return webdriver.WebDriver.createSession(executor, this.capabilities_); } else { - var url = this.getServerUrl() || - webdriver.AbstractBuilder.DEFAULT_SERVER_URL; + var url = this.serverUrl_; var client; - if (url[0] == '/') { + if (this.useBrowserCors_ && webdriver.http.CorsClient.isAvailable()) { + client = new webdriver.http.XhrClient(url); + } else if (url[0] == '/') { var origin = window.location.origin || (window.location.protocol + '//' + window.location.host); client = new webdriver.http.XhrClient(origin + url); @@ -107,8 +198,8 @@ webdriver.Builder.prototype.build = function() { } executor = new webdriver.http.Executor(client); - if (this.getSession()) { - return webdriver.WebDriver.attachToSession(executor, this.getSession()); + if (this.sessionId_) { + return webdriver.WebDriver.attachToSession(executor, this.sessionId_); } else { throw new Error('Unable to create a new client for this browser. The ' + 'WebDriver session ID has not been defined.'); diff --git a/lib/webdriver/button.js b/lib/webdriver/button.js index aedb0b3..d45a8dd 100644 --- a/lib/webdriver/button.js +++ b/lib/webdriver/button.js @@ -1,17 +1,19 @@ -// Copyright 2012 Selenium comitters -// Copyright 2012 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.Button'); diff --git a/lib/webdriver/capabilities.js b/lib/webdriver/capabilities.js index 783322a..b441633 100644 --- a/lib/webdriver/capabilities.js +++ b/lib/webdriver/capabilities.js @@ -1,16 +1,19 @@ -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Defines the webdriver.Capabilities class. @@ -19,6 +22,10 @@ goog.provide('webdriver.Browser'); goog.provide('webdriver.Capabilities'); goog.provide('webdriver.Capability'); +goog.provide('webdriver.ProxyConfig'); + +goog.require('webdriver.Serializable'); +goog.require('webdriver.logging'); @@ -30,6 +37,7 @@ webdriver.Browser = { ANDROID: 'android', CHROME: 'chrome', FIREFOX: 'firefox', + IE: 'internet explorer', INTERNET_EXPLORER: 'internet explorer', IPAD: 'iPad', IPHONE: 'iPhone', @@ -41,6 +49,23 @@ webdriver.Browser = { +/** + * Describes how a proxy should be configured for a WebDriver session. + * Proxy configuration object, as defined by the WebDriver wire protocol. + * @typedef {( + * {proxyType: string}| + * {proxyType: string, + * proxyAutoconfigUrl: string}| + * {proxyType: string, + * ftpProxy: string, + * httpProxy: string, + * sslProxy: string, + * noProxy: string})} + */ +webdriver.ProxyConfig; + + + /** * Common webdriver capability keys. * @enum {string} @@ -50,8 +75,7 @@ webdriver.Capability = { /** * Indicates whether a driver should accept all SSL certs by default. This * capability only applies when requesting a new session. To query whether - * a driver can handle insecure SSL certs, see - * {@link webdriver.Capability.SECURE_SSL}. + * a driver can handle insecure SSL certs, see {@link #SECURE_SSL}. */ ACCEPT_SSL_CERTS: 'acceptSslCerts', @@ -62,10 +86,18 @@ webdriver.Capability = { */ BROWSER_NAME: 'browserName', + /** + * Defines how elements should be scrolled into the viewport for interaction. + * This capability will be set to zero (0) if elements are aligned with the + * top of the viewport, or one (1) if aligned with the bottom. The default + * behavior is to align with the top of the viewport. + */ + ELEMENT_SCROLL_BEHAVIOR: 'elementScrollBehavior', + /** * Whether the driver is capable of handling modal alerts (e.g. alert, * confirm, prompt). To define how a driver should handle alerts, - * use {@link webdriver.Capability.UNEXPECTED_ALERT_BEHAVIOR}. + * use {@link #UNEXPECTED_ALERT_BEHAVIOR}. */ HANDLES_ALERTS: 'handlesAlerts', @@ -74,6 +106,11 @@ webdriver.Capability = { */ LOGGING_PREFS: 'loggingPrefs', + /** + * Whether this session generates native events when simulating user input. + */ + NATIVE_EVENTS: 'nativeEvents', + /** * Describes the platform the browser is running on. Will be one of * ANDROID, IOS, LINUX, MAC, UNIX, or WINDOWS. When requesting a @@ -93,19 +130,13 @@ webdriver.Capability = { /** * Whether a driver is only capable of handling secure SSL certs. To request * that a driver accept insecure SSL certs by default, use - * {@link webdriver.Capability.ACCEPT_SSL_CERTS}. + * {@link #ACCEPT_SSL_CERTS}. */ SECURE_SSL: 'secureSsl', /** Whether the driver supports manipulating the app cache. */ SUPPORTS_APPLICATION_CACHE: 'applicationCacheEnabled', - /** - * Whether the driver supports controlling the browser's internet - * connectivity. - */ - SUPPORTS_BROWSER_CONNECTION: 'browserConnectionEnabled', - /** Whether the driver supports locating elements with CSS selectors. */ SUPPORTS_CSS_SELECTORS: 'cssSelectorsEnabled', @@ -134,16 +165,19 @@ webdriver.Capability = { * @param {(webdriver.Capabilities|Object)=} opt_other Another set of * capabilities to merge into this instance. * @constructor + * @extends {webdriver.Serializable.>} */ webdriver.Capabilities = function(opt_other) { + webdriver.Serializable.call(this); - /** @private {!Object} */ + /** @private {!Object.} */ this.caps_ = {}; if (opt_other) { this.merge(opt_other); } }; +goog.inherits(webdriver.Capabilities, webdriver.Serializable); /** @@ -214,7 +248,6 @@ webdriver.Capabilities.opera = function() { set(webdriver.Capability.BROWSER_NAME, webdriver.Browser.OPERA); }; - /** * @return {!webdriver.Capabilities} A basic set of capabilities for * PhantomJS. @@ -254,8 +287,12 @@ webdriver.Capabilities.htmlunitwithjs = function() { }; -/** @return {!Object} The JSON representation of this instance. */ -webdriver.Capabilities.prototype.toJSON = function() { +/** + * @return {!Object.} The JSON representation of this instance. Note, + * the returned object may contain nested promises that are promised values. + * @override + */ +webdriver.Capabilities.prototype.serialize = function() { return this.caps_; }; @@ -316,3 +353,59 @@ webdriver.Capabilities.prototype.get = function(key) { webdriver.Capabilities.prototype.has = function(key) { return !!this.get(key); }; + + +/** + * Sets the logging preferences. Preferences may be specified as a + * {@link webdriver.logging.Preferences} instance, or a as a map of log-type to + * log-level. + * @param {!(webdriver.logging.Preferences|Object.)} prefs The + * logging preferences. + * @return {!webdriver.Capabilities} A self reference. + */ +webdriver.Capabilities.prototype.setLoggingPrefs = function(prefs) { + return this.set(webdriver.Capability.LOGGING_PREFS, prefs); +}; + + +/** + * Sets the proxy configuration for this instance. + * @param {webdriver.ProxyConfig} proxy The desired proxy configuration. + * @return {!webdriver.Capabilities} A self reference. + */ +webdriver.Capabilities.prototype.setProxy = function(proxy) { + return this.set(webdriver.Capability.PROXY, proxy); +}; + + +/** + * Sets whether native events should be used. + * @param {boolean} enabled Whether to enable native events. + * @return {!webdriver.Capabilities} A self reference. + */ +webdriver.Capabilities.prototype.setEnableNativeEvents = function(enabled) { + return this.set(webdriver.Capability.NATIVE_EVENTS, enabled); +}; + + +/** + * Sets how elements should be scrolled into view for interaction. + * @param {number} behavior The desired scroll behavior: either 0 to align with + * the top of the viewport or 1 to align with the bottom. + * @return {!webdriver.Capabilities} A self reference. + */ +webdriver.Capabilities.prototype.setScrollBehavior = function(behavior) { + return this.set(webdriver.Capability.ELEMENT_SCROLL_BEHAVIOR, behavior); +}; + + +/** + * Sets the default action to take with an unexpected alert before returning + * an error. + * @param {string} behavior The desired behavior; should be "accept", "dismiss", + * or "ignore". Defaults to "dismiss". + * @return {!webdriver.Capabilities} A self reference. + */ +webdriver.Capabilities.prototype.setAlertBehavior = function(behavior) { + return this.set(webdriver.Capability.UNEXPECTED_ALERT_BEHAVIOR, behavior); +}; diff --git a/lib/webdriver/command.js b/lib/webdriver/command.js index 5c3d95a..f501179 100644 --- a/lib/webdriver/command.js +++ b/lib/webdriver/command.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Contains several classes for handling commands. @@ -199,9 +202,9 @@ webdriver.CommandName = { // optional for these commands. CLICK: 'mouseClick', DOUBLE_CLICK: 'mouseDoubleClick', - MOUSE_DOWN: 'mouseDown', - MOUSE_UP: 'mouseUp', - MOVE_TO: 'mouseMove', + MOUSE_DOWN: 'mouseButtonDown', + MOUSE_UP: 'mouseButtonUp', + MOVE_TO: 'mouseMoveTo', SEND_KEYS_TO_ACTIVE_ELEMENT: 'sendKeysToActiveElement', // These belong to the Advanced Touch API @@ -216,13 +219,16 @@ webdriver.CommandName = { GET_AVAILABLE_LOG_TYPES: 'getAvailableLogTypes', GET_LOG: 'getLog', - GET_SESSION_LOGS: 'getSessionLogs' + GET_SESSION_LOGS: 'getSessionLogs', + + // Non-standard commands used by the standalone Selenium server. + UPLOAD_FILE: 'uploadFile' }; /** - * Handles the execution of {@code webdriver.Command} objects. + * Handles the execution of WebDriver {@link webdriver.Command commands}. * @interface */ webdriver.CommandExecutor = function() {}; diff --git a/lib/webdriver/events.js b/lib/webdriver/events.js index a420163..cad3bac 100644 --- a/lib/webdriver/events.js +++ b/lib/webdriver/events.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview A light weight event system modeled after Node's EventEmitter. diff --git a/lib/webdriver/firefoxdomexecutor.js b/lib/webdriver/firefoxdomexecutor.js index b581f67..397cbaf 100644 --- a/lib/webdriver/firefoxdomexecutor.js +++ b/lib/webdriver/firefoxdomexecutor.js @@ -1,23 +1,26 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.FirefoxDomExecutor'); goog.require('bot.response'); -goog.require('goog.json'); goog.require('goog.userAgent.product'); goog.require('webdriver.Command'); +goog.require('webdriver.CommandExecutor'); goog.require('webdriver.CommandName'); @@ -113,12 +116,9 @@ webdriver.FirefoxDomExecutor.prototype.execute = function(command, callback) { command.getName() != webdriver.CommandName.SWITCH_TO_FRAME) { parameters['id'] = parameters['id']['ELEMENT']; } - - var json = goog.json.serialize({ + var json = JSON.stringify({ 'name': command.getName(), - 'sessionId': { - 'value': parameters['sessionId'] - }, + 'sessionId': parameters['sessionId'], 'parameters': parameters }); this.docElement_.setAttribute( @@ -155,7 +155,7 @@ webdriver.FirefoxDomExecutor.prototype.onResponse_ = function() { try { var response = bot.response.checkResponse( - /** @type {!bot.response.ResponseObject} */ (goog.json.parse(json))); + /** @type {!bot.response.ResponseObject} */ (JSON.parse(json))); } catch (ex) { command.callback(ex); return; diff --git a/lib/webdriver/http/corsclient.js b/lib/webdriver/http/corsclient.js index b0ea15a..262106b 100644 --- a/lib/webdriver/http/corsclient.js +++ b/lib/webdriver/http/corsclient.js @@ -1,20 +1,23 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.http.CorsClient'); -goog.require('goog.json'); +goog.require('webdriver.http.Client'); goog.require('webdriver.http.Response'); @@ -47,10 +50,10 @@ goog.require('webdriver.http.Response'); * This limitation appears to be intentional and is documented in WebKit's * Layout tests: * //LayoutTests/http/tests/xmlhttprequest/access-control-and-redirects.html - *
        • If the server does not return a 2xx response, IE and Opera's - * implementations will fire the XDomainRequest/XMLHttpRequest object's + *
        • If the server does not return a 2xx response, IE + * implementation will fire the XDomainRequest/XMLHttpRequest object's * onerror handler, but without the corresponding response text returned by - * the server. This renders IE and Opera incapable of handling command + * the server. This renders IE incapable of handling command * failures in the standard JSON protocol. *
        * @@ -58,7 +61,7 @@ goog.require('webdriver.http.Response'); * @constructor * @implements {webdriver.http.Client} * @see CORS Spec - * @see + * @see * JSON wire protocol */ webdriver.http.CorsClient = function(url) { @@ -119,7 +122,7 @@ webdriver.http.CorsClient.prototype.send = function(request, callback) { // optimized away by the compiler, which leaves us where we were before. xhr.onprogress = xhr.ontimeout = function() {}; - xhr.send(goog.json.serialize({ + xhr.send(JSON.stringify({ 'method': request.method, 'path': request.path, 'data': request.data diff --git a/lib/webdriver/http/http.js b/lib/webdriver/http/http.js index 3adff37..ab0d1bd 100644 --- a/lib/webdriver/http/http.js +++ b/lib/webdriver/http/http.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Defines a {@code webdriver.CommandExecutor} that communicates @@ -24,9 +27,10 @@ goog.provide('webdriver.http.Response'); goog.require('bot.ErrorCode'); goog.require('goog.array'); -goog.require('goog.json'); +goog.require('webdriver.CommandExecutor'); goog.require('webdriver.CommandName'); -goog.require('webdriver.promise.Deferred'); +goog.require('webdriver.logging'); +goog.require('webdriver.promise'); @@ -41,9 +45,9 @@ webdriver.http.Client = function() { /** * Sends a request to the server. If an error occurs while sending the request, * such as a failure to connect to the server, the provided callback will be - * invoked with a non-null {@code Error} describing the error. Otherwise, when + * invoked with a non-null {@link Error} describing the error. Otherwise, when * the server's response has been received, the callback will be invoked with a - * null Error and non-null {@code webdriver.http.Response} object. + * null Error and non-null {@link webdriver.http.Response} object. * * @param {!webdriver.http.Request} request The request to send. * @param {function(Error, !webdriver.http.Response=)} callback the function to @@ -69,12 +73,43 @@ webdriver.http.Executor = function(client) { * @private {!webdriver.http.Client} */ this.client_ = client; + + /** + * @private {!Object<{method:string, path:string}>} + */ + this.customCommands_ = {}; + + /** + * @private {!webdriver.logging.Logger} + */ + this.log_ = webdriver.logging.getLogger('webdriver.http.Executor'); +}; + + +/** + * Defines a new command for use with this executor. When a command is sent, + * the {@code path} will be preprocessed using the command's parameters; any + * path segments prefixed with ":" will be replaced by the parameter of the + * same name. For example, given "/person/:name" and the parameters + * "{name: 'Bob'}", the final command path will be "/person/Bob". + * + * @param {string} name The command name. + * @param {string} method The HTTP method to use when sending this command. + * @param {string} path The path to send the command to, relative to + * the WebDriver server's command root and of the form + * "/path/:variable/segment". + */ +webdriver.http.Executor.prototype.defineCommand = function( + name, method, path) { + this.customCommands_[name] = {method: method, path: path}; }; /** @override */ webdriver.http.Executor.prototype.execute = function(command, callback) { - var resource = webdriver.http.Executor.COMMAND_MAP_[command.getName()]; + var resource = + this.customCommands_[command.getName()] || + webdriver.http.Executor.COMMAND_MAP_[command.getName()]; if (!resource) { throw new Error('Unrecognized command: ' + command.getName()); } @@ -84,13 +119,22 @@ webdriver.http.Executor.prototype.execute = function(command, callback) { var request = new webdriver.http.Request(resource.method, path, parameters); request.headers['connection'] = command.getName() == 'quit'? 'close' : 'keep-alive'; + var log = this.log_; + log.finer(function() { + return '>>>\n' + request; + }); + this.client_.send(request, function(e, response) { var responseObj; if (!e) { + log.finer(function() { + return '<<<\n' + response; + }); try { responseObj = webdriver.http.Executor.parseHttpResponse_( /** @type {!webdriver.http.Response} */ (response)); } catch (ex) { + log.warning('Error parsing response', ex); e = ex; } } @@ -145,7 +189,7 @@ webdriver.http.Executor.buildPath_ = function(path, parameters) { */ webdriver.http.Executor.parseHttpResponse_ = function(httpResponse) { try { - return /** @type {!bot.response.ResponseObject} */ (goog.json.parse( + return /** @type {!bot.response.ResponseObject} */ (JSON.parse( httpResponse.body)); } catch (ex) { // Whoops, looks like the server sent us a malformed response. We'll need @@ -280,6 +324,22 @@ webdriver.http.Executor.COMMAND_MAP_ = (function() { put(webdriver.CommandName.MOVE_TO, post('/session/:sessionId/moveto')). put(webdriver.CommandName.SEND_KEYS_TO_ACTIVE_ELEMENT, post('/session/:sessionId/keys')). + put(webdriver.CommandName.TOUCH_SINGLE_TAP, + post('/session/:sessionId/touch/click')). + put(webdriver.CommandName.TOUCH_DOUBLE_TAP, + post('/session/:sessionId/touch/doubleclick')). + put(webdriver.CommandName.TOUCH_DOWN, + post('/session/:sessionId/touch/down')). + put(webdriver.CommandName.TOUCH_UP, + post('/session/:sessionId/touch/up')). + put(webdriver.CommandName.TOUCH_MOVE, + post('/session/:sessionId/touch/move')). + put(webdriver.CommandName.TOUCH_SCROLL, + post('/session/:sessionId/touch/scroll')). + put(webdriver.CommandName.TOUCH_LONG_PRESS, + post('/session/:sessionId/touch/longclick')). + put(webdriver.CommandName.TOUCH_FLICK, + post('/session/:sessionId/touch/flick')). put(webdriver.CommandName.ACCEPT_ALERT, post('/session/:sessionId/accept_alert')). put(webdriver.CommandName.DISMISS_ALERT, @@ -292,6 +352,7 @@ webdriver.http.Executor.COMMAND_MAP_ = (function() { put(webdriver.CommandName.GET_AVAILABLE_LOG_TYPES, get('/session/:sessionId/log/types')). put(webdriver.CommandName.GET_SESSION_LOGS, post('/logs')). + put(webdriver.CommandName.UPLOAD_FILE, post('/session/:sessionId/file')). build(); /** @constructor */ @@ -334,7 +395,7 @@ webdriver.http.headersToString_ = function(headers) { /** * Describes a partial HTTP request. This class is a "partial" request and only * defines the path on the server to send a request to. It is each - * {@code webdriver.http.Client}'s responsibility to build the full URL for the + * {@link webdriver.http.Client}'s responsibility to build the full URL for the * final request. * @param {string} method The HTTP method to use for the request. * @param {string} path Path on the server to send the request to. @@ -375,7 +436,7 @@ webdriver.http.Request.prototype.toString = function() { this.method + ' ' + this.path + ' HTTP/1.1', webdriver.http.headersToString_(this.headers), '', - goog.json.serialize(this.data) + JSON.stringify(this.data) ].join('\n'); }; @@ -415,8 +476,8 @@ webdriver.http.Response = function(status, headers, body) { /** - * Builds a {@code webdriver.http.Response} from a {@code XMLHttpRequest} or - * {@code XDomainRequest} response object. + * Builds a {@link webdriver.http.Response} from a {@link XMLHttpRequest} or + * {@link XDomainRequest} response object. * @param {!(XDomainRequest|XMLHttpRequest)} xhr The request to parse. * @return {!webdriver.http.Response} The parsed response. */ diff --git a/lib/webdriver/http/xhrclient.js b/lib/webdriver/http/xhrclient.js index 4f69fc3..58c69a3 100644 --- a/lib/webdriver/http/xhrclient.js +++ b/lib/webdriver/http/xhrclient.js @@ -1,23 +1,26 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** @fileoverview A XHR client. */ goog.provide('webdriver.http.XhrClient'); -goog.require('goog.json'); goog.require('goog.net.XmlHttp'); +goog.require('webdriver.http.Client'); goog.require('webdriver.http.Response'); @@ -57,7 +60,7 @@ webdriver.http.XhrClient.prototype.send = function(request, callback) { xhr.setRequestHeader(header, request.headers[header] + ''); } - xhr.send(goog.json.serialize(request.data)); + xhr.send(JSON.stringify(request.data)); } catch (ex) { callback(ex); } diff --git a/lib/webdriver/key.js b/lib/webdriver/key.js index e231581..af8ba61 100644 --- a/lib/webdriver/key.js +++ b/lib/webdriver/key.js @@ -1,16 +1,19 @@ -// Copyright 2012 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.Key'); diff --git a/lib/webdriver/locators.js b/lib/webdriver/locators.js index 8d993d8..fde52e9 100644 --- a/lib/webdriver/locators.js +++ b/lib/webdriver/locators.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Factory methods for the supported locator strategies. @@ -73,14 +76,13 @@ goog.exportSymbol('By', webdriver.By); /** * Short-hand expressions for the primary element locator strategies. * For example the following two statements are equivalent: - *
        - * var e1 = driver.findElement(webdriver.By.id('foo'));
        - * var e2 = driver.findElement({id: 'foo'});
        - * 
        * - *

        Care should be taken when using JavaScript minifiers (such as the + * var e1 = driver.findElement(webdriver.By.id('foo')); + * var e2 = driver.findElement({id: 'foo'}); + * + * Care should be taken when using JavaScript minifiers (such as the * Closure compiler), as locator hashes will always be parsed using - * the un-obfuscated properties listed below. + * the un-obfuscated properties listed. * * @typedef {( * {className: string}| @@ -111,7 +113,7 @@ webdriver.By.className = webdriver.Locator.factory_('class name'); /** * Locates elements using a CSS selector. For browsers that do not support * CSS selectors, WebDriver implementations may return an - * {@link bot.Error.State.INVALID_SELECTOR invalid selector} error. An + * {@linkplain bot.Error.State.INVALID_SELECTOR invalid selector} error. An * implementation may, however, emulate the CSS selector API. * * @param {string} selector The CSS selector to use. @@ -131,7 +133,7 @@ webdriver.By.id = webdriver.Locator.factory_('id'); /** - * Locates link elements whose {@link webdriver.WebElement#getText visible + * Locates link elements whose {@linkplain webdriver.WebElement#getText visible * text} matches the given string. * * @param {string} text The link text to search for. @@ -142,7 +144,7 @@ webdriver.By.linkText = webdriver.Locator.factory_('link text'); /** * Locates an elements by evaluating a - * {@link webdriver.WebDriver#executeScript JavaScript expression}. + * {@linkplain webdriver.WebDriver#executeScript JavaScript expression}. * The result of this expression must be an element or list of elements. * * @param {!(string|Function)} script The script to execute. @@ -168,7 +170,7 @@ webdriver.By.name = webdriver.Locator.factory_('name'); /** - * Locates link elements whose {@link webdriver.WebElement#getText visible + * Locates link elements whose {@linkplain webdriver.WebElement#getText visible * text} contains the given substring. * * @param {string} text The substring to check for in a link's visible text. @@ -180,7 +182,9 @@ webdriver.By.partialLinkText = webdriver.Locator.factory_( /** * Locates elements with a given tag name. The returned locator is - * equivalent to using the {@code getElementsByTagName} DOM function. + * equivalent to using the + * [getElementsByTagName](https://developer.mozilla.org/en-US/docs/Web/API/Element.getElementsByTagName) + * DOM function. * * @param {string} text The substring to check for in a link's visible text. * @return {!webdriver.Locator} The new locator. diff --git a/lib/webdriver/logging.js b/lib/webdriver/logging.js index 775f758..ac0909d 100644 --- a/lib/webdriver/logging.js +++ b/lib/webdriver/logging.js @@ -1,49 +1,176 @@ -// Copyright 2013 Selenium comitters -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines WebDriver's logging system. The logging system is + * broken into major components: local and remote logging. + * + * The local logging API, which is anchored by the + * {@link webdriver.logging.Logger Logger} class, is similar to Java's + * logging API. Loggers, retrieved by {@link webdriver.logging.getLogger}, use + * hierarchical, dot-delimited namespaces + * (e.g. "" > "webdriver" > "webdriver.logging"). Recorded log messages are + * represented by the {@link webdriver.logging.LogRecord LogRecord} class. You + * can capture log records by + * {@linkplain webdriver.logging.Logger#addHandler attaching} a handler function + * to the desired logger. For convenience, you can quickly enable logging to + * the console by simply calling + * {@link webdriver.logging.installConsoleHandler()}. + * + * The [remote logging API](https://github.com/SeleniumHQ/selenium/wiki/Logging) + * allows you to retrieve logs from a remote WebDriver server. This API uses the + * {@link Preferences} class to define desired log levels prior to create a + * WebDriver session: + * + * var prefs = new webdriver.logging.Preferences(); + * prefs.setLevel(webdriver.logging.Type.BROWSER, + * webdriver.logging.Level.DEBUG); + * + * var caps = webdriver.Capabilities.chrome(); + * caps.setLoggingPrefs(prefs); + * // ... + * + * Remote log entries are represented by the {@link Entry} class and may be + * retrieved via {@link webdriver.WebDriver.Logs}: + * + * driver.manage().logs().get(webdriver.logging.Type.BROWSER) + * .then(function(entries) { + * entries.forEach(function(entry) { + * console.log('[%s] %s', entry.level.name, entry.message); + * }); + * }); + * + * **NOTE:** Only a few browsers support the remote logging API (notably + * Firefox and Chrome). Firefox supports basic logging functionality, while + * Chrome exposes robust + * [performance logging](https://sites.google.com/a/chromium.org/chromedriver/logging) + * options. Remote logging is still considered a non-standard feature, and the + * APIs exposed by this module for it are non-frozen. Once logging is officially + * defined by the [W3C WebDriver spec](http://www.w3.org/TR/webdriver/), this + * module will be updated to use a consistent API for local and remote logging. + */ + +goog.module('webdriver.logging'); +goog.module.declareLegacyNamespace(); + +var LogManager = goog.require('goog.debug.LogManager'); +var LogRecord = goog.require('goog.debug.LogRecord'); +var Logger = goog.require('goog.debug.Logger'); +var Objects = goog.require('goog.object'); +var padNumber = goog.require('goog.string').padNumber; + -goog.provide('webdriver.logging'); -goog.provide('webdriver.logging.Preferences'); +/** @const */ +exports.LogRecord = LogRecord; -goog.require('goog.object'); + +/** @const */ +exports.Logger = Logger; + + +/** @const */ +exports.Level = Logger.Level; /** - * Log level names from WebDriver's JSON wire protocol. - * @enum {string} + * DEBUG is a message level for debugging messages and has the same log level + * as the {@link Logger.Level.CONFIG} message level. + * @const {!Logger.Level} + */ +Logger.Level.DEBUG = new Logger.Level('DEBUG', Logger.Level.CONFIG.value); + + +/** + * Finds a named logger. + * + * @param {string=} opt_name The dot-delimited logger name, such as + * "webdriver.logging.Logger". Defaults to the name of the root logger. + * @return {!Logger} The named logger. + */ +function getLogger(opt_name) { + return LogManager.getLogger(opt_name || Logger.ROOT_LOGGER_NAME); +} +exports.getLogger = getLogger; + + +/** + * Logs all messages to the Console API. + */ +function consoleHandler(record) { + if (typeof console === 'undefined') { + return; + } + record = /** @type {!LogRecord} */(record); + var timestamp = new Date(record.getMillis()); + var msg = + '[' + timestamp.getUTCFullYear() + '-' + + padNumber(timestamp.getUTCMonth() + 1, 2) + '-' + + padNumber(timestamp.getUTCDate(), 2) + 'T' + + padNumber(timestamp.getUTCHours(), 2) + ':' + + padNumber(timestamp.getUTCMinutes(), 2) + ':' + + padNumber(timestamp.getUTCSeconds(), 2) + 'Z]' + + '[' + record.getLevel().name + ']' + + '[' + record.getLoggerName() + '] ' + + record.getMessage(); + + var level = record.getLevel().value; + if (level >= Logger.Level.SEVERE.value) { + console.error(msg); + } else if (level >= Logger.Level.WARNING.value) { + console.warn(msg); + } else { + console.log(msg); + } +} + + +/** + * Adds the console handler to the given logger. The console handler will log + * all messages using the JavaScript Console API. + * + * @param {Logger=} opt_logger The logger to add the handler to; defaults + * to the root logger. + * @see exports.removeConsoleHandler */ -webdriver.logging.LevelName = { - ALL: 'ALL', - DEBUG: 'DEBUG', - INFO: 'INFO', - WARNING: 'WARNING', - SEVERE: 'SEVERE', - OFF: 'OFF' +exports.addConsoleHandler = function(opt_logger) { + var logger = opt_logger || getLogger(); + logger.addHandler(consoleHandler); }; /** - * Logging levels. - * @enum {{value: number, name: webdriver.logging.LevelName}} + * Installs the console log handler on the root logger. + * @see exports.addConsoleHandler */ -webdriver.logging.Level = { - ALL: {value: Number.MIN_VALUE, name: webdriver.logging.LevelName.ALL}, - DEBUG: {value: 700, name: webdriver.logging.LevelName.DEBUG}, - INFO: {value: 800, name: webdriver.logging.LevelName.INFO}, - WARNING: {value: 900, name: webdriver.logging.LevelName.WARNING}, - SEVERE: {value: 1000, name: webdriver.logging.LevelName.SEVERE}, - OFF: {value: Number.MAX_VALUE, name: webdriver.logging.LevelName.OFF} +exports.installConsoleHandler = function() { + exports.addConsoleHandler(); +}; + + +/** + * Removes the console log handler from the given logger. + * + * @param {Logger=} opt_logger The logger to remove the handler from; defaults + * to the root logger. + * @see exports.addConsoleHandler + */ +exports.removeConsoleHandler = function(opt_logger) { + var logger = opt_logger || getLogger(); + logger.removeHandler(consoleHandler); }; @@ -53,23 +180,58 @@ webdriver.logging.Level = { * will be returned. * @param {(number|string)} nameOrValue The log level name, or value, to * convert . - * @return {!webdriver.logging.Level} The converted level. + * @return {!Logger.Level} The converted level. */ -webdriver.logging.getLevel = function(nameOrValue) { - var predicate = goog.isString(nameOrValue) ? - function(val) { return val.name === nameOrValue; } : - function(val) { return val.value === nameOrValue; }; +function getLevel(nameOrValue) { + // DEBUG is not a predefined Closure log level, but maps to CONFIG. Since + // DEBUG is a predefined level for the WebDriver protocol, we prefer it over + // CONFIG. + if ('DEBUG' === nameOrValue || Logger.Level.DEBUG.value === nameOrValue) { + return Logger.Level.DEBUG; + } else if (goog.isString(nameOrValue)) { + return Logger.Level.getPredefinedLevel(/** @type {string} */(nameOrValue)) + || Logger.Level.ALL; + } else { + return Logger.Level.getPredefinedLevelByValue( + /** @type {number} */(nameOrValue)) || Logger.Level.ALL; + } +} +exports.getLevel = getLevel; - return goog.object.findValue(webdriver.logging.Level, predicate) || - webdriver.logging.Level.ALL; -}; + +/** + * Normalizes a {@link Logger.Level} to one of the distinct values recognized + * by WebDriver's wire protocol. + * @param {!Logger.Level} level The input level. + * @return {!Logger.Level} The normalized level. + */ +function normalizeLevel(level) { + if (level.value <= Logger.Level.ALL.value) { // ALL is 0. + return Logger.Level.ALL; + + } else if (level.value === Logger.Level.OFF.value) { // OFF is Infinity + return Logger.Level.OFF; + + } else if (level.value < Logger.Level.INFO.value) { + return Logger.Level.DEBUG; + + } else if (level.value < Logger.Level.WARNING.value) { + return Logger.Level.INFO; + + } else if (level.value < Logger.Level.SEVERE.value) { + return Logger.Level.WARNING; + + } else { + return Logger.Level.SEVERE; + } +} /** * Common log types. * @enum {string} */ -webdriver.logging.Type = { +var Type = { /** Logs originating from the browser. */ BROWSER: 'browser', /** Logs from a WebDriver client. */ @@ -81,78 +243,105 @@ webdriver.logging.Type = { /** Logs from the remote server. */ SERVER: 'server' }; +exports.Type = Type; /** - * A hash describing log preferences. - * @typedef {Object.} + * Describes the log preferences for a WebDriver session. + * @final */ -webdriver.logging.Preferences; +var Preferences = goog.defineClass(null, { + /** @constructor */ + constructor: function() { + /** @private {!Object.} */ + this.prefs_ = {}; + }, + + /** + * Sets the desired logging level for a particular log type. + * @param {(string|Type)} type The log type. + * @param {!Logger.Level} level The desired log level. + */ + setLevel: function(type, level) { + this.prefs_[type] = normalizeLevel(level); + }, + + /** + * Converts this instance to its JSON representation. + * @return {!Object.} The JSON representation of this set of + * preferences. + */ + toJSON: function() { + var obj = {}; + for (var type in this.prefs_) { + if (this.prefs_.hasOwnProperty(type)) { + obj[type] = this.prefs_[type].name; + } + } + return obj; + } +}); +exports.Preferences = Preferences; /** - * A single log entry. - * @param {(!webdriver.logging.Level|string)} level The entry level. - * @param {string} message The log message. - * @param {number=} opt_timestamp The time this entry was generated, in - * milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the - * current time will be used. - * @param {string=} opt_type The log type, if known. - * @constructor + * A single log entry recorded by a WebDriver component, such as a remote + * WebDriver server. + * @final */ -webdriver.logging.Entry = function(level, message, opt_timestamp, opt_type) { +var Entry = goog.defineClass(null, { + /** + * @param {(!Logger.Level|string)} level The entry level. + * @param {string} message The log message. + * @param {number=} opt_timestamp The time this entry was generated, in + * milliseconds since 0:00:00, January 1, 1970 UTC. If omitted, the + * current time will be used. + * @param {string=} opt_type The log type, if known. + * @constructor + */ + constructor: function(level, message, opt_timestamp, opt_type) { - /** @type {!webdriver.logging.Level} */ - this.level = - goog.isString(level) ? webdriver.logging.getLevel(level) : level; + /** @type {!Logger.Level} */ + this.level = goog.isString(level) ? getLevel(level) : level; - /** @type {string} */ - this.message = message; + /** @type {string} */ + this.message = message; - /** @type {number} */ - this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now(); + /** @type {number} */ + this.timestamp = goog.isNumber(opt_timestamp) ? opt_timestamp : goog.now(); - /** @type {string} */ - this.type = opt_type || ''; -}; + /** @type {string} */ + this.type = opt_type || ''; + }, + statics: { + /** + * Converts a {@link goog.debug.LogRecord} into a + * {@link webdriver.logging.Entry}. + * @param {!goog.debug.LogRecord} logRecord The record to convert. + * @param {string=} opt_type The log type. + * @return {!Entry} The converted entry. + */ + fromClosureLogRecord: function(logRecord, opt_type) { + return new Entry( + normalizeLevel(/** @type {!Logger.Level} */(logRecord.getLevel())), + '[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(), + logRecord.getMillis(), + opt_type); + } + }, -/** - * @return {{level: string, message: string, timestamp: number, - * type: string}} The JSON representation of this entry. - */ -webdriver.logging.Entry.prototype.toJSON = function() { - return { - 'level': this.level.name, - 'message': this.message, - 'timestamp': this.timestamp, - 'type': this.type - }; -}; - - -/** - * Converts a {@link goog.debug.LogRecord} into a - * {@link webdriver.logging.Entry}. - * @param {!goog.debug.LogRecord} logRecord The record to convert. - * @param {string=} opt_type The log type. - * @return {!webdriver.logging.Entry} The converted entry. - */ -webdriver.logging.Entry.fromClosureLogRecord = function(logRecord, opt_type) { - var closureLevel = logRecord.getLevel(); - var level = webdriver.logging.Level.SEVERE; - - if (closureLevel.value <= webdriver.logging.Level.DEBUG.value) { - level = webdriver.logging.Level.DEBUG; - } else if (closureLevel.value <= webdriver.logging.Level.INFO.value) { - level = webdriver.logging.Level.INFO; - } else if (closureLevel.value <= webdriver.logging.Level.WARNING.value) { - level = webdriver.logging.Level.WARNING; + /** + * @return {{level: string, message: string, timestamp: number, + * type: string}} The JSON representation of this entry. + */ + toJSON: function() { + return { + 'level': this.level.name, + 'message': this.message, + 'timestamp': this.timestamp, + 'type': this.type + }; } - - return new webdriver.logging.Entry( - level, - '[' + logRecord.getLoggerName() + '] ' + logRecord.getMessage(), - logRecord.getMillis(), - opt_type); -}; +}); +exports.Entry = Entry; diff --git a/lib/webdriver/promise.js b/lib/webdriver/promise.js index 59dc609..5756429 100644 --- a/lib/webdriver/promise.js +++ b/lib/webdriver/promise.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @license Portions of this code are from the Dojo toolkit, received under the @@ -46,528 +49,705 @@ * http://wiki.commonjs.org/wiki/Promises. */ -goog.provide('webdriver.promise'); -goog.provide('webdriver.promise.ControlFlow'); -goog.provide('webdriver.promise.ControlFlow.Timer'); -goog.provide('webdriver.promise.Deferred'); -goog.provide('webdriver.promise.Promise'); +goog.module('webdriver.promise'); +goog.module.declareLegacyNamespace(); -goog.require('goog.array'); -goog.require('goog.debug.Error'); -goog.require('goog.object'); -goog.require('webdriver.EventEmitter'); -goog.require('webdriver.stacktrace.Snapshot'); +var Arrays = goog.require('goog.array'); +var asserts = goog.require('goog.asserts'); +var asyncRun = goog.require('goog.async.run'); +var throwException = goog.require('goog.async.throwException'); +var DebugError = goog.require('goog.debug.Error'); +var Objects = goog.require('goog.object'); +var EventEmitter = goog.require('webdriver.EventEmitter'); +var stacktrace = goog.require('webdriver.stacktrace'); /** - * Represents the eventual value of a completed operation. Each promise may be - * in one of three states: pending, resolved, or rejected. Each promise starts - * in the pending state and may make a single transition to either a - * fulfilled or failed state. - * - *

        This class is based on the Promise/A proposal from CommonJS. Additional - * functions are provided for API compatibility with Dojo Deferred objects. + * @define {boolean} Whether to append traces of {@code then} to rejection + * errors. + */ +goog.define('webdriver.promise.LONG_STACK_TRACES', false); + +/** @const */ +var promise = exports; + + +/** + * Generates an error to capture the current stack trace. + * @param {string} name Error name for this stack trace. + * @param {string} msg Message to record. + * @param {!Function} topFn The function that should appear at the top of the + * stack; only applicable in V8. + * @return {!Error} The generated error. + */ +promise.captureStackTrace = function(name, msg, topFn) { + var e = Error(msg); + e.name = name; + if (Error.captureStackTrace) { + Error.captureStackTrace(e, topFn); + } else { + var stack = stacktrace.getStack(e); + e.stack = e.toString(); + if (stack) { + e.stack += '\n' + stack; + } + } + return e; +}; + + +/** + * Error used when the computation of a promise is cancelled. * + * @param {string=} opt_msg The cancellation message. * @constructor - * @see http://wiki.commonjs.org/wiki/Promises/A + * @extends {DebugError} + * @final */ -webdriver.promise.Promise = function() { +promise.CancellationError = function(opt_msg) { + DebugError.call(this, opt_msg); + + /** @override */ + this.name = 'CancellationError'; }; +goog.inherits(promise.CancellationError, DebugError); + + +/** + * Wraps the given error in a CancellationError. Will trivially return + * the error itself if it is an instanceof CancellationError. + * + * @param {*} error The error to wrap. + * @param {string=} opt_msg The prefix message to use. + * @return {!promise.CancellationError} A cancellation error. + */ +promise.CancellationError.wrap = function(error, opt_msg) { + if (error instanceof promise.CancellationError) { + return /** @type {!promise.CancellationError} */(error); + } else if (opt_msg) { + var message = opt_msg; + if (error) { + message += ': ' + error; + } + return new promise.CancellationError(message); + } + var message; + if (error) { + message = error + ''; + } + return new promise.CancellationError(message); +}; + + + +/** + * Thenable is a promise-like object with a {@code then} method which may be + * used to schedule callbacks on a promised value. + * + * @interface + * @extends {IThenable} + * @template T + */ +promise.Thenable = function() {}; /** * Cancels the computation of this promise's value, rejecting the promise in the - * process. - * @param {*} reason The reason this promise is being cancelled. If not an - * {@code Error}, one will be created using the value's string - * representation. + * process. This method is a no-op if the promise has already been resolved. + * + * @param {(string|promise.CancellationError)=} opt_reason The reason this + * promise is being cancelled. */ -webdriver.promise.Promise.prototype.cancel = function(reason) { - throw new TypeError('Unimplemented function: "cancel"'); -}; +promise.Thenable.prototype.cancel = function(opt_reason) {}; /** @return {boolean} Whether this promise's value is still being computed. */ -webdriver.promise.Promise.prototype.isPending = function() { - throw new TypeError('Unimplemented function: "isPending"'); -}; +promise.Thenable.prototype.isPending = function() {}; /** - * Registers listeners for when this instance is resolved. This function most - * overridden by subtypes. + * Registers listeners for when this instance is resolved. * - * @param {Function=} opt_callback The function to call if this promise is - * successfully resolved. The function should expect a single argument: the - * promise's resolved value. - * @param {Function=} opt_errback The function to call if this promise is - * rejected. The function should expect a single argument: the rejection - * reason. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. + * @param {?(function(T): (R|IThenable))=} opt_callback The + * function to call if this promise is successfully resolved. The function + * should expect a single argument: the promise's resolved value. + * @param {?(function(*): (R|IThenable))=} opt_errback + * The function to call if this promise is rejected. The function should + * expect a single argument: the rejection reason. + * @return {!promise.Promise} A new promise which will be + * resolved with the result of the invoked callback. + * @template R */ -webdriver.promise.Promise.prototype.then = function( - opt_callback, opt_errback) { - throw new TypeError('Unimplemented function: "then"'); -}; +promise.Thenable.prototype.then = function(opt_callback, opt_errback) {}; /** * Registers a listener for when this promise is rejected. This is synonymous * with the {@code catch} clause in a synchronous API: - *

        
        - *   // Synchronous API:
        - *   try {
        - *     doSynchronousWork();
        - *   } catch (ex) {
        - *     console.error(ex);
        - *   }
          *
        - *   // Asynchronous promise API:
        - *   doAsynchronousWork().thenCatch(function(ex) {
        - *     console.error(ex);
        - *   });
        - * 
        + * // Synchronous API: + * try { + * doSynchronousWork(); + * } catch (ex) { + * console.error(ex); + * } + * + * // Asynchronous promise API: + * doAsynchronousWork().thenCatch(function(ex) { + * console.error(ex); + * }); * - * @param {!Function} errback The function to call if this promise is - * rejected. The function should expect a single argument: the rejection - * reason. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. + * @param {function(*): (R|IThenable)} errback The + * function to call if this promise is rejected. The function should + * expect a single argument: the rejection reason. + * @return {!promise.Promise} A new promise which will be + * resolved with the result of the invoked callback. + * @template R */ -webdriver.promise.Promise.prototype.thenCatch = function(errback) { - return this.then(null, errback); -}; +promise.Thenable.prototype.thenCatch = function(errback) {}; /** * Registers a listener to invoke when this promise is resolved, regardless * of whether the promise's value was successfully computed. This function * is synonymous with the {@code finally} clause in a synchronous API: - *
        
        - *   // Synchronous API:
        - *   try {
        - *     doSynchronousWork();
        - *   } finally {
        - *     cleanUp();
        - *   }
          *
        - *   // Asynchronous promise API:
        - *   doAsynchronousWork().thenFinally(cleanUp);
        - * 
        + * // Synchronous API: + * try { + * doSynchronousWork(); + * } finally { + * cleanUp(); + * } * - * Note: similar to the {@code finally} clause, if the registered + * // Asynchronous promise API: + * doAsynchronousWork().thenFinally(cleanUp); + * + * __Note:__ similar to the {@code finally} clause, if the registered * callback returns a rejected promise or throws an error, it will silently * replace the rejection error (if any) from this promise: - *
        
        - *   try {
        - *     throw Error('one');
        - *   } finally {
        - *     throw Error('two');  // Hides Error: one
        - *   }
          *
        - *   webdriver.promise.rejected(Error('one'))
        - *       .thenFinally(function() {
        - *         throw Error('two');  // Hides Error: one
        - *       });
        - * 
        + * try { + * throw Error('one'); + * } finally { + * throw Error('two'); // Hides Error: one + * } * + * promise.rejected(Error('one')) + * .thenFinally(function() { + * throw Error('two'); // Hides Error: one + * }); * - * @param callback - * @returns {!webdriver.promise.Promise} + * @param {function(): (R|IThenable)} callback The function + * to call when this promise is resolved. + * @return {!promise.Promise} A promise that will be fulfilled + * with the callback result. + * @template R */ -webdriver.promise.Promise.prototype.thenFinally = function(callback) { - return this.then(callback, callback); -}; +promise.Thenable.prototype.thenFinally = function(callback) {}; /** - * Registers a function to be invoked when this promise is successfully - * resolved. This function is provided for backwards compatibility with the - * Dojo Deferred API. - * - * @param {Function} callback The function to call if this promise is - * successfully resolved. The function should expect a single argument: the - * promise's resolved value. - * @param {!Object=} opt_self The object which |this| should refer to when the - * function is invoked. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. - * @deprecated Use {@link #then()} instead. + * Property used to flag constructor's as implementing the Thenable interface + * for runtime type checking. + * @type {string} + * @const */ -webdriver.promise.Promise.prototype.addCallback = function(callback, opt_self) { - return this.then(goog.bind(callback, opt_self)); -}; +var IMPLEMENTED_BY_PROP = '$webdriver_Thenable'; /** - * Registers a function to be invoked when this promise is rejected. - * This function is provided for backwards compatibility with the - * Dojo Deferred API. - * - * @param {Function} errback The function to call if this promise is - * rejected. The function should expect a single argument: the rejection - * reason. - * @param {!Object=} opt_self The object which |this| should refer to when the - * function is invoked. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. - * @deprecated Use {@link #thenCatch()} instead. + * Adds a property to a class prototype to allow runtime checks of whether + * instances of that class implement the Thenable interface. This function will + * also ensure the prototype's {@code then} function is exported from compiled + * code. + * @param {function(new: promise.Thenable, ...?)} ctor The + * constructor whose prototype to modify. */ -webdriver.promise.Promise.prototype.addErrback = function(errback, opt_self) { - return this.thenCatch(goog.bind(errback, opt_self)); +promise.Thenable.addImplementation = function(ctor) { + // Based on goog.promise.Thenable.isImplementation. + ctor.prototype['then'] = ctor.prototype.then; + try { + // Old IE7 does not support defineProperty; IE8 only supports it for + // DOM elements. + Object.defineProperty( + ctor.prototype, + IMPLEMENTED_BY_PROP, + {'value': true, 'enumerable': false}); + } catch (ex) { + ctor.prototype[IMPLEMENTED_BY_PROP] = true; + } }; /** - * Registers a function to be invoked when this promise is either rejected or - * resolved. This function is provided for backwards compatibility with the - * Dojo Deferred API. - * - * @param {Function} callback The function to call when this promise is - * either resolved or rejected. The function should expect a single - * argument: the resolved value or rejection error. - * @param {!Object=} opt_self The object which |this| should refer to when the - * function is invoked. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. - * @deprecated Use {@link #thenFinally()} instead. + * Checks if an object has been tagged for implementing the Thenable interface + * as defined by {@link webdriver.promise.Thenable.addImplementation}. + * @param {*} object The object to test. + * @return {boolean} Whether the object is an implementation of the Thenable + * interface. */ -webdriver.promise.Promise.prototype.addBoth = function(callback, opt_self) { - return this.thenFinally(goog.bind(callback, opt_self)); +promise.Thenable.isImplementation = function(object) { + // Based on goog.promise.Thenable.isImplementation. + if (!object) { + return false; + } + try { + return !!object[IMPLEMENTED_BY_PROP]; + } catch (e) { + return false; // Property access seems to be forbidden. + } }; + /** - * An alias for {@code webdriver.promise.Promise.prototype.then} that permits - * the scope of the invoked function to be specified. This function is provided - * for backwards compatibility with the Dojo Deferred API. - * - * @param {Function} callback The function to call if this promise is - * successfully resolved. The function should expect a single argument: the - * promise's resolved value. - * @param {Function} errback The function to call if this promise is - * rejected. The function should expect a single argument: the rejection - * reason. - * @param {!Object=} opt_self The object which |this| should refer to when the - * function is invoked. - * @return {!webdriver.promise.Promise} A new promise which will be resolved - * with the result of the invoked callback. - * @deprecated Use {@link #then()} instead. + * @enum {string} */ -webdriver.promise.Promise.prototype.addCallbacks = function( - callback, errback, opt_self) { - return this.then(goog.bind(callback, opt_self), - goog.bind(errback, opt_self)); +var PromiseState = { + PENDING: 'pending', + BLOCKED: 'blocked', + REJECTED: 'rejected', + FULFILLED: 'fulfilled' }; /** - * Represents a value that will be resolved at some point in the future. This - * class represents the protected "producer" half of a Promise - each Deferred - * has a {@code promise} property that may be returned to consumers for - * registering callbacks, reserving the ability to resolve the deferred to the - * producer. - * - *

        If this Deferred is rejected and there are no listeners registered before - * the next turn of the event loop, the rejection will be passed to the - * {@link webdriver.promise.ControlFlow} as an unhandled failure. - * - *

        If this Deferred is cancelled, the cancellation reason will be forward to - * the Deferred's canceller function (if provided). The canceller may return a - * truth-y value to override the reason provided for rejection. + * Represents the eventual value of a completed operation. Each promise may be + * in one of three states: pending, fulfilled, or rejected. Each promise starts + * in the pending state and may make a single transition to either a + * fulfilled or rejected state, at which point the promise is considered + * resolved. * - * @param {Function=} opt_canceller Function to call when cancelling the - * computation of this instance's value. - * @param {webdriver.promise.ControlFlow=} opt_flow The control flow - * this instance was created under. This should only be provided during - * unit tests. + * @param {function( + * function((T|IThenable|Thenable)=), + * function(*=))} resolver + * Function that is invoked immediately to begin computation of this + * promise's value. The function should accept a pair of callback functions, + * one for fulfilling the promise and another for rejecting it. + * @param {promise.ControlFlow=} opt_flow The control flow + * this instance was created under. Defaults to the currently active flow. * @constructor - * @extends {webdriver.promise.Promise} + * @implements {promise.Thenable} + * @template T + * @see http://promises-aplus.github.io/promises-spec/ */ -webdriver.promise.Deferred = function(opt_canceller, opt_flow) { - /* NOTE: This class's implementation diverges from the prototypical style - * used in the rest of the atoms library. This was done intentionally to - * protect the internal Deferred state from consumers, as outlined by - * http://wiki.commonjs.org/wiki/Promises - */ - goog.base(this); +promise.Promise = function(resolver, opt_flow) { + goog.getUid(this); - var flow = opt_flow || webdriver.promise.controlFlow(); + /** @private {!promise.ControlFlow} */ + this.flow_ = opt_flow || promise.controlFlow(); - /** - * The listeners registered with this Deferred. Each element in the list will - * be a 3-tuple of the callback function, errback function, and the - * corresponding deferred object. - * @type {!Array.} - */ - var listeners = []; + /** @private {Error} */ + this.stack_ = null; + if (promise.LONG_STACK_TRACES) { + this.stack_ = promise.captureStackTrace('Promise', 'new', promise.Promise); + } - /** - * Whether this Deferred's resolution was ever handled by a listener. - * If the Deferred is rejected and its value is not handled by a listener - * before the next turn of the event loop, the error will be passed to the - * global error handler. - * @type {boolean} - */ - var handled = false; + /** @private {promise.Promise} */ + this.parent_ = null; - /** - * Key for the timeout used to delay reproting an unhandled rejection to the - * parent {@link webdriver.promise.ControlFlow}. - * @type {?number} - */ - var pendingRejectionKey = null; + /** @private {Array} */ + this.callbacks_ = null; - /** - * This Deferred's current state. - * @type {!webdriver.promise.Deferred.State_} - */ - var state = webdriver.promise.Deferred.State_.PENDING; + /** @private {PromiseState} */ + this.state_ = PromiseState.PENDING; - /** - * This Deferred's resolved value; set when the state transitions from - * {@code webdriver.promise.Deferred.State_.PENDING}. - * @type {*} - */ - var value; + /** @private {boolean} */ + this.handled_ = false; + + /** @private {boolean} */ + this.pendingNotifications_ = false; - /** @return {boolean} Whether this promise's value is still pending. */ - function isPending() { - return state == webdriver.promise.Deferred.State_.PENDING; + /** @private {*} */ + this.value_ = undefined; + + try { + var self = this; + resolver(function(value) { + self.resolve_(PromiseState.FULFILLED, value); + }, function(reason) { + self.resolve_(PromiseState.REJECTED, reason); + }); + } catch (ex) { + this.resolve_(PromiseState.REJECTED, ex); } +}; +promise.Thenable.addImplementation(promise.Promise); - /** - * Removes all of the listeners previously registered on this deferred. - * @throws {Error} If this deferred has already been resolved. - */ - function removeAll() { - listeners = []; + +/** @override */ +promise.Promise.prototype.toString = function() { + return 'Promise::' + goog.getUid(this) + + ' {[[PromiseStatus]]: "' + this.state_ + '"}'; +}; + + +/** + * Resolves this promise. If the new value is itself a promise, this function + * will wait for it to be resolved before notifying the registered listeners. + * @param {PromiseState} newState The promise's new state. + * @param {*} newValue The promise's new value. + * @throws {TypeError} If {@code newValue === this}. + * @private + */ +promise.Promise.prototype.resolve_ = function(newState, newValue) { + if (PromiseState.PENDING !== this.state_) { + return; } - /** - * Resolves this deferred. If the new value is a promise, this function will - * wait for it to be resolved before notifying the registered listeners. - * @param {!webdriver.promise.Deferred.State_} newState The deferred's new - * state. - * @param {*} newValue The deferred's new value. - */ - function resolve(newState, newValue) { - if (webdriver.promise.Deferred.State_.PENDING !== state) { + if (newValue === this) { + // See promise a+, 2.3.1 + // http://promises-aplus.github.io/promises-spec/#point-48 + throw new TypeError('A promise may not resolve to itself'); + } + + this.parent_ = null; + this.state_ = PromiseState.BLOCKED; + + if (promise.Thenable.isImplementation(newValue)) { + // 2.3.2 + newValue = /** @type {!promise.Thenable} */(newValue); + newValue.then( + this.unblockAndResolve_.bind(this, PromiseState.FULFILLED), + this.unblockAndResolve_.bind(this, PromiseState.REJECTED)); + return; + + } else if (goog.isObject(newValue)) { + // 2.3.3 + + try { + // 2.3.3.1 + var then = newValue['then']; + } catch (e) { + // 2.3.3.2 + this.state_ = PromiseState.REJECTED; + this.value_ = e; + this.scheduleNotifications_(); return; } - state = webdriver.promise.Deferred.State_.BLOCKED; + // NB: goog.isFunction is loose and will accept instanceof Function. + if (typeof then === 'function') { + // 2.3.3.3 + this.invokeThen_(newValue, then); + return; + } + } - if (webdriver.promise.isPromise(newValue) && newValue !== self) { - var onFulfill = goog.partial(notifyAll, newState); - var onReject = goog.partial( - notifyAll, webdriver.promise.Deferred.State_.REJECTED); - if (newValue instanceof webdriver.promise.Deferred) { - newValue.then(onFulfill, onReject); - } else { - webdriver.promise.asap(newValue, onFulfill, onReject); - } + if (newState === PromiseState.REJECTED && + isError(newValue) && newValue.stack && this.stack_) { + newValue.stack += '\nFrom: ' + (this.stack_.stack || this.stack_); + } - } else { - notifyAll(newState, newValue); + // 2.3.3.4 and 2.3.4 + this.state_ = newState; + this.value_ = newValue; + this.scheduleNotifications_(); +}; + + +/** + * Invokes a thenable's "then" method according to 2.3.3.3 of the promise + * A+ spec. + * @param {!Object} x The thenable object. + * @param {!Function} then The "then" function to invoke. + * @private + */ +promise.Promise.prototype.invokeThen_ = function(x, then) { + var called = false; + var self = this; + + var resolvePromise = function(value) { + if (!called) { // 2.3.3.3.3 + called = true; + // 2.3.3.3.1 + self.unblockAndResolve_(PromiseState.FULFILLED, value); } - } + }; - /** - * Notifies all of the listeners registered with this Deferred that its state - * has changed. - * @param {!webdriver.promise.Deferred.State_} newState The deferred's new - * state. - * @param {*} newValue The deferred's new value. - */ - function notifyAll(newState, newValue) { - if (newState === webdriver.promise.Deferred.State_.REJECTED && - // We cannot check instanceof Error since the object may have been - // created in a different JS context. - goog.isObject(newValue) && goog.isString(newValue.message)) { - newValue = flow.annotateError(/** @type {!Error} */(newValue)); + var rejectPromise = function(reason) { + if (!called) { // 2.3.3.3.3 + called = true; + // 2.3.3.3.2 + self.unblockAndResolve_(PromiseState.REJECTED, reason); } + }; + + try { + // 2.3.3.3 + then.call(x, resolvePromise, rejectPromise); + } catch (e) { + // 2.3.3.3.4.2 + rejectPromise(e); + } +}; + + +/** + * @param {PromiseState} newState The promise's new state. + * @param {*} newValue The promise's new value. + * @private + */ +promise.Promise.prototype.unblockAndResolve_ = function(newState, newValue) { + if (this.state_ === PromiseState.BLOCKED) { + this.state_ = PromiseState.PENDING; + this.resolve_(newState, newValue); + } +}; + - state = newState; - value = newValue; - while (listeners.length) { - notify(listeners.shift()); +/** + * @private + */ +promise.Promise.prototype.scheduleNotifications_ = function() { + if (!this.pendingNotifications_) { + this.pendingNotifications_ = true; + this.flow_.suspend_(); + + var activeFrame; + + if (!this.handled_ && + this.state_ === PromiseState.REJECTED && + !(this.value_ instanceof promise.CancellationError)) { + activeFrame = this.flow_.getActiveFrame_(); + activeFrame.pendingRejection = true; } - if (!handled && state == webdriver.promise.Deferred.State_.REJECTED) { - pendingRejectionKey = propagateError(value); + if (this.callbacks_ && this.callbacks_.length) { + activeFrame = this.flow_.getRunningFrame_(); + var self = this; + this.callbacks_.forEach(function(callback) { + if (!callback.frame_.getParent()) { + activeFrame.addChild(callback.frame_); + } + }); } + + asyncRun(goog.bind(this.notifyAll_, this, activeFrame)); } +}; - /** - * Propagates an unhandled rejection to the parent ControlFlow in a - * future turn of the JavaScript event loop. - * @param {*} error The error value to report. - * @return {number} The key for the registered timeout. - */ - function propagateError(error) { - flow.pendingRejections_ += 1; - return flow.timer.setTimeout(function() { - flow.pendingRejections_ -= 1; - flow.abortFrame_(error); - }, 0); + +/** + * Notifies all of the listeners registered with this promise that its state + * has changed. + * @param {Frame} frame The active frame from when this round of + * notifications were scheduled. + * @private + */ +promise.Promise.prototype.notifyAll_ = function(frame) { + this.flow_.resume_(); + this.pendingNotifications_ = false; + + if (!this.handled_ && + this.state_ === PromiseState.REJECTED && + !(this.value_ instanceof promise.CancellationError)) { + this.flow_.abortFrame_(this.value_, frame); } - /** - * Notifies a single listener of this Deferred's change in state. - * @param {!webdriver.promise.Deferred.Listener_} listener The listener to - * notify. - */ - function notify(listener) { - var func = state == webdriver.promise.Deferred.State_.RESOLVED ? - listener.callback : listener.errback; - if (func) { - flow.runInNewFrame_(goog.partial(func, value), - listener.fulfill, listener.reject); - } else if (state == webdriver.promise.Deferred.State_.REJECTED) { - listener.reject(value); - } else { - listener.fulfill(value); - } + if (this.callbacks_) { + var callbacks = this.callbacks_; + this.callbacks_ = null; + callbacks.forEach(this.notify_, this); } +}; - /** - * The consumer promise for this instance. Provides protected access to the - * callback registering functions. - * @type {!webdriver.promise.Promise} - */ - var promise = new webdriver.promise.Promise(); - /** - * Registers a callback on this Deferred. - * @param {Function=} opt_callback The callback. - * @param {Function=} opt_errback The errback. - * @return {!webdriver.promise.Promise} A new promise representing the result - * of the callback. - * @see webdriver.promise.Promise#then - */ - function then(opt_callback, opt_errback) { - // Avoid unnecessary allocations if we weren't given any callback functions. - if (!opt_callback && !opt_errback) { - return promise; - } +/** + * Notifies a single callback of this promise's change ins tate. + * @param {Callback} callback The callback to notify. + * @private + */ +promise.Promise.prototype.notify_ = function(callback) { + callback.notify(this.state_, this.value_); +}; - // The moment a listener is registered, we consider this deferred to be - // handled; the callback must handle any rejection errors. - handled = true; - if (pendingRejectionKey) { - flow.pendingRejections_ -= 1; - flow.timer.clearTimeout(pendingRejectionKey); - } - var deferred = new webdriver.promise.Deferred(cancel, flow); - var listener = { - callback: opt_callback, - errback: opt_errback, - fulfill: deferred.fulfill, - reject: deferred.reject - }; +/** @override */ +promise.Promise.prototype.cancel = function(opt_reason) { + if (!this.isPending()) { + return; + } - if (state == webdriver.promise.Deferred.State_.PENDING || - state == webdriver.promise.Deferred.State_.BLOCKED) { - listeners.push(listener); - } else { - notify(listener); + if (this.parent_) { + this.parent_.cancel(opt_reason); + } else { + this.resolve_( + PromiseState.REJECTED, + promise.CancellationError.wrap(opt_reason)); + } +}; + + +/** @override */ +promise.Promise.prototype.isPending = function() { + return this.state_ === PromiseState.PENDING; +}; + + +/** @override */ +promise.Promise.prototype.then = function(opt_callback, opt_errback) { + return this.addCallback_( + opt_callback, opt_errback, 'then', promise.Promise.prototype.then); +}; + + +/** @override */ +promise.Promise.prototype.thenCatch = function(errback) { + return this.addCallback_( + null, errback, 'thenCatch', promise.Promise.prototype.thenCatch); +}; + + +/** @override */ +promise.Promise.prototype.thenFinally = function(callback) { + var error; + var mustThrow = false; + return this.then(function() { + return callback(); + }, function(err) { + error = err; + mustThrow = true; + return callback(); + }).then(function() { + if (mustThrow) { + throw error; } + }); +}; + - return deferred.promise; +/** + * Registers a new callback with this promise + * @param {(function(T): (R|IThenable)|null|undefined)} callback The + * fulfillment callback. + * @param {(function(*): (R|IThenable)|null|undefined)} errback The + * rejection callback. + * @param {string} name The callback name. + * @param {!Function} fn The function to use as the top of the stack when + * recording the callback's creation point. + * @return {!promise.Promise} A new promise which will be resolved with the + * esult of the invoked callback. + * @template R + * @private + */ +promise.Promise.prototype.addCallback_ = function(callback, errback, name, fn) { + if (!goog.isFunction(callback) && !goog.isFunction(errback)) { + return this; } - var self = this; + this.handled_ = true; + var cb = new Callback(this, callback, errback, name, fn); - /** - * Resolves this promise with the given value. If the value is itself a - * promise and not a reference to this deferred, this instance will wait for - * it before resolving. - * @param {*=} opt_value The resolved value. - */ - function fulfill(opt_value) { - resolve(webdriver.promise.Deferred.State_.RESOLVED, opt_value); + if (!this.callbacks_) { + this.callbacks_ = []; } + this.callbacks_.push(cb); + + if (this.state_ !== PromiseState.PENDING && + this.state_ !== PromiseState.BLOCKED) { + this.flow_.getSchedulingFrame_().addChild(cb.frame_); + this.scheduleNotifications_(); + } + return cb.promise; +}; + + +/** + * Represents a value that will be resolved at some point in the future. This + * class represents the protected "producer" half of a Promise - each Deferred + * has a {@code promise} property that may be returned to consumers for + * registering callbacks, reserving the ability to resolve the deferred to the + * producer. + * + * If this Deferred is rejected and there are no listeners registered before + * the next turn of the event loop, the rejection will be passed to the + * {@link webdriver.promise.ControlFlow} as an unhandled failure. + * + * @param {promise.ControlFlow=} opt_flow The control flow + * this instance was created under. This should only be provided during + * unit tests. + * @constructor + * @implements {promise.Thenable} + * @template T + */ +promise.Deferred = function(opt_flow) { + var fulfill, reject; + + /** @type {!promise.Promise} */ + this.promise = new promise.Promise(function(f, r) { + fulfill = f; + reject = r; + }, opt_flow); + + var self = this; + var checkNotSelf = function(value) { + if (value === self) { + throw new TypeError('May not resolve a Deferred with itself'); + } + }; /** - * Rejects this promise. If the error is itself a promise, this instance will - * be chained to it and be rejected with the error's resolved value. - * @param {*=} opt_error The rejection reason, typically either a - * {@code Error} or a {@code string}. + * Resolves this deferred with the given value. It is safe to call this as a + * normal function (with no bound "this"). + * @param {(T|IThenable|Thenable)=} opt_value The fulfilled value. */ - function reject(opt_error) { - resolve(webdriver.promise.Deferred.State_.REJECTED, opt_error); - } + this.fulfill = function(opt_value) { + checkNotSelf(opt_value); + fulfill(opt_value); + }; /** - * Attempts to cancel the computation of this instance's value. This attempt - * will silently fail if this instance has already resolved. - * @param {*=} opt_reason The reason for cancelling this promise. + * Rejects this promise with the given reason. It is safe to call this as a + * normal function (with no bound "this"). + * @param {*=} opt_reason The rejection reason. */ - function cancel(opt_reason) { - if (!isPending()) { - return; - } + this.reject = function(opt_reason) { + checkNotSelf(opt_reason); + reject(opt_reason); + }; +}; +promise.Thenable.addImplementation(promise.Deferred); - if (opt_canceller) { - opt_reason = opt_canceller(opt_reason) || opt_reason; - } - reject(opt_reason); - } +/** @override */ +promise.Deferred.prototype.isPending = function() { + return this.promise.isPending(); +}; - this.promise = promise; - this.promise.then = this.then = then; - this.promise.cancel = this.cancel = cancel; - this.promise.isPending = this.isPending = isPending; - this.fulfill = fulfill; - this.reject = this.errback = reject; - // Only expose this function to our internal classes. - // TODO: find a cleaner way of handling this. - if (this instanceof webdriver.promise.Task_) { - this.removeAll = removeAll; - } +/** @override */ +promise.Deferred.prototype.cancel = function(opt_reason) { + this.promise.cancel(opt_reason); +}; - // Export symbols necessary for the contract on this object to work in - // compiled mode. - goog.exportProperty(this, 'then', this.then); - goog.exportProperty(this, 'cancel', cancel); - goog.exportProperty(this, 'fulfill', fulfill); - goog.exportProperty(this, 'reject', reject); - goog.exportProperty(this, 'isPending', isPending); - goog.exportProperty(this, 'promise', this.promise); - goog.exportProperty(this.promise, 'then', this.then); - goog.exportProperty(this.promise, 'cancel', cancel); - goog.exportProperty(this.promise, 'isPending', isPending); + +/** + * @override + * @deprecated Use {@code then} from the promise property directly. + */ +promise.Deferred.prototype.then = function(opt_cb, opt_eb) { + return this.promise.then(opt_cb, opt_eb); }; -goog.inherits(webdriver.promise.Deferred, webdriver.promise.Promise); /** - * Type definition for a listener registered on a Deferred object. - * @typedef {{callback:(Function|undefined), - * errback:(Function|undefined), - * fulfill: function(*), reject: function(*)}} - * @private + * @override + * @deprecated Use {@code thenCatch} from the promise property directly. */ -webdriver.promise.Deferred.Listener_; +promise.Deferred.prototype.thenCatch = function(opt_eb) { + return this.promise.thenCatch(opt_eb); +}; /** - * The three states a {@link webdriver.promise.Deferred} object may be in. - * @enum {number} - * @private + * @override + * @deprecated Use {@code thenFinally} from the promise property directly. */ -webdriver.promise.Deferred.State_ = { - REJECTED: -1, - PENDING: 0, - BLOCKED: 1, - RESOLVED: 2 +promise.Deferred.prototype.thenFinally = function(opt_cb) { + return this.promise.thenFinally(opt_cb); }; @@ -576,12 +756,11 @@ webdriver.promise.Deferred.State_ = { * instanceof check since the value may originate from another context. * @param {*} value The value to test. * @return {boolean} Whether the value is an error. - * @private */ -webdriver.promise.isError_ = function(value) { +function isError(value) { return value instanceof Error || goog.isObject(value) && - (Object.prototype.toString.call(value) === '[object Error]' || + (goog.isString(value.message) || // A special test for goog.testing.JsUnitException. value.isJsUnitException); @@ -595,11 +774,13 @@ webdriver.promise.isError_ = function(value) { * @param {*} value The value to test. * @return {boolean} Whether the value is a promise. */ -webdriver.promise.isPromise = function(value) { +promise.isPromise = function(value) { return !!value && goog.isObject(value) && // Use array notation so the Closure compiler does not obfuscate away our - // contract. - goog.isFunction(value['then']); + // contract. Use typeof rather than goog.isFunction because + // goog.isFunction accepts instanceof Function, which the promise spec + // does not. + typeof value['then'] === 'function'; }; @@ -607,42 +788,46 @@ webdriver.promise.isPromise = function(value) { * Creates a promise that will be resolved at a set time in the future. * @param {number} ms The amount of time, in milliseconds, to wait before * resolving the promise. - * @return {!webdriver.promise.Promise} The promise. + * @return {!promise.Promise} The promise. */ -webdriver.promise.delayed = function(ms) { - var timer = webdriver.promise.controlFlow().timer; +promise.delayed = function(ms) { var key; - var deferred = new webdriver.promise.Deferred(function() { - timer.clearTimeout(key); + return new promise.Promise(function(fulfill) { + key = setTimeout(function() { + key = null; + fulfill(); + }, ms); + }).thenCatch(function(e) { + clearTimeout(key); + key = null; + throw e; }); - key = timer.setTimeout(deferred.fulfill, ms); - return deferred.promise; }; /** * Creates a new deferred object. - * @param {Function=} opt_canceller Function to call when cancelling the - * computation of this instance's value. - * @return {!webdriver.promise.Deferred} The new deferred object. + * @return {!promise.Deferred} The new deferred object. + * @template T */ -webdriver.promise.defer = function(opt_canceller) { - return new webdriver.promise.Deferred(opt_canceller); +promise.defer = function() { + return new promise.Deferred(); }; /** * Creates a promise that has been resolved with the given value. - * @param {*=} opt_value The resolved value. - * @return {!webdriver.promise.Promise} The resolved promise. + * @param {T=} opt_value The resolved value. + * @return {!promise.Promise} The resolved promise. + * @template T */ -webdriver.promise.fulfilled = function(opt_value) { - if (opt_value instanceof webdriver.promise.Promise) { +promise.fulfilled = function(opt_value) { + if (opt_value instanceof promise.Promise) { return opt_value; } - var deferred = new webdriver.promise.Deferred(); - deferred.fulfill(opt_value); - return deferred.promise; + return new promise.Promise(function(fulfill) { + fulfill(opt_value); + }); }; @@ -650,37 +835,42 @@ webdriver.promise.fulfilled = function(opt_value) { * Creates a promise that has been rejected with the given reason. * @param {*=} opt_reason The rejection reason; may be any value, but is * usually an Error or a string. - * @return {!webdriver.promise.Promise} The rejected promise. + * @return {!promise.Promise} The rejected promise. + * @template T */ -webdriver.promise.rejected = function(opt_reason) { - var deferred = new webdriver.promise.Deferred(); - deferred.reject(opt_reason); - return deferred.promise; +promise.rejected = function(opt_reason) { + if (opt_reason instanceof promise.Promise) { + return opt_reason; + } + return new promise.Promise(function(_, reject) { + reject(opt_reason); + }); }; /** - * Wraps a function that is assumed to be a node-style callback as its final - * argument. This callback takes two arguments: an error value (which will be + * Wraps a function that expects a node-style callback as its final + * argument. This callback expects two arguments: an error value (which will be * null if the call succeeded), and the success value as the second argument. - * If the call fails, the returned promise will be rejected, otherwise it will - * be resolved with the result. + * The callback will the resolve or reject the returned promise, based on its arguments. * @param {!Function} fn The function to wrap. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the + * @param {...?} var_args The arguments to apply to the function, excluding the + * final callback. + * @return {!promise.Promise} A promise that will be resolved with the * result of the provided function's callback. */ -webdriver.promise.checkedNodeCall = function(fn) { - var deferred = new webdriver.promise.Deferred(function() { - throw Error('This Deferred may not be cancelled'); +promise.checkedNodeCall = function(fn, var_args) { + var args = Arrays.slice(arguments, 1); + return new promise.Promise(function(fulfill, reject) { + try { + args.push(function(error, value) { + error ? reject(error) : fulfill(value); + }); + fn.apply(undefined, args); + } catch (ex) { + reject(ex); + } }); - try { - fn(function(error, value) { - error ? deferred.reject(error) : deferred.fulfill(value); - }); - } catch (ex) { - deferred.reject(ex); - } - return deferred.promise; }; @@ -693,18 +883,16 @@ webdriver.promise.checkedNodeCall = function(fn) { * resolved successfully. * @param {Function=} opt_errback The function to call when the value is * rejected. - * @return {!webdriver.promise.Promise} A new promise. + * @return {!promise.Promise} A new promise. */ -webdriver.promise.when = function(value, opt_callback, opt_errback) { - if (value instanceof webdriver.promise.Promise) { +promise.when = function(value, opt_callback, opt_errback) { + if (promise.Thenable.isImplementation(value)) { return value.then(opt_callback, opt_errback); } - var deferred = new webdriver.promise.Deferred(); - - webdriver.promise.asap(value, deferred.fulfill, deferred.reject); - - return deferred.then(opt_callback, opt_errback); + return new promise.Promise(function(fulfill, reject) { + promise.asap(value, fulfill, reject); + }).then(opt_callback, opt_errback); }; @@ -718,8 +906,8 @@ webdriver.promise.when = function(value, opt_callback, opt_errback) { * @param {Function=} opt_errback The function to call when the value is * rejected. */ -webdriver.promise.asap = function(value, callback, opt_errback) { - if (webdriver.promise.isPromise(value)) { +promise.asap = function(value, callback, opt_errback) { + if (promise.isPromise(value)) { value.then(callback, opt_errback); // Maybe a Dojo-like deferred object? @@ -740,38 +928,37 @@ webdriver.promise.asap = function(value, callback, opt_errback) { * input array's promises are rejected, the returned promise will be rejected * with the same reason. * - * @param {!Array.<(T|!webdriver.promise.Promise.)>} arr An array of + * @param {!Array<(T|!promise.Promise)>} arr An array of * promises to wait on. - * @return {!webdriver.promise.Promise.>} A promise that is + * @return {!promise.Promise>} A promise that is * fulfilled with an array containing the fulfilled values of the * input array, or rejected with the same reason as the first * rejected value. * @template T */ -webdriver.promise.all = function(arr) { - var n = arr.length; - if (!n) { - return webdriver.promise.fulfilled([]); - } - - var toFulfill = n; - var result = webdriver.promise.defer(); - var values = []; +promise.all = function(arr) { + return new promise.Promise(function(fulfill, reject) { + var n = arr.length; + var values = []; - var onFulfill = function(index, value) { - values[index] = value; - toFulfill--; - if (toFulfill == 0) { - result.fulfill(values); + if (!n) { + fulfill(values); + return; } - }; - for (var i = 0; i < n; ++i) { - webdriver.promise.asap( - arr[i], goog.partial(onFulfill, i), result.reject); - } + var toFulfill = n; + var onFulfilled = function(index, value) { + values[index] = value; + toFulfill--; + if (toFulfill == 0) { + fulfill(values); + } + }; - return result.promise; + for (var i = 0; i < n; ++i) { + promise.asap(arr[i], goog.partial(onFulfilled, i), reject); + } + }); }; @@ -780,27 +967,52 @@ webdriver.promise.all = function(arr) { * new array, which is used as the fulfillment value of the promise returned * by this function. * - *

        If the return value of the mapping function is a promise, this function + * If the return value of the mapping function is a promise, this function * will wait for it to be fulfilled before inserting it into the new array. * - *

        If the mapping function throws or returns a rejected promise, the + * If the mapping function throws or returns a rejected promise, the * promise returned by this function will be rejected with the same reason. * Only the first failure will be reported; all subsequent errors will be * silently ignored. * - * @param {!(Array.|webdriver.promise.Promise.>)} arr The + * @param {!(Array|promise.Promise>)} arr The * array to iterator over, or a promise that will resolve to said array. - * @param {function(this: SELF, TYPE, number, !Array.): ?} fn The + * @param {function(this: SELF, TYPE, number, !Array): ?} fn The * function to call for each element in the array. This function should * expect three arguments (the element, the index, and the array itself. * @param {SELF=} opt_self The object to be used as the value of 'this' within * {@code fn}. * @template TYPE, SELF */ -webdriver.promise.map = function(arr, fn, opt_self) { - return webdriver.promise.when(arr, function(arr) { - var result = goog.array.map(arr, fn, opt_self); - return webdriver.promise.all(result); +promise.map = function(arr, fn, opt_self) { + return promise.fulfilled(arr).then(function(arr) { + goog.asserts.assertNumber(arr.length, 'not an array like value'); + return new promise.Promise(function(fulfill, reject) { + var n = arr.length; + var values = new Array(n); + (function processNext(i) { + for (; i < n; i++) { + if (i in arr) { + break; + } + } + if (i >= n) { + fulfill(values); + return; + } + try { + promise.asap( + fn.call(opt_self, arr[i], i, /** @type {!Array} */(arr)), + function(value) { + values[i] = value; + processNext(i + 1); + }, + reject); + } catch (ex) { + reject(ex); + } + })(0); + }); }); }; @@ -809,31 +1021,54 @@ webdriver.promise.map = function(arr, fn, opt_self) { * Calls a function for each element in an array, and if the function returns * true adds the element to a new array. * - *

        If the return value of the filter function is a promise, this function + * If the return value of the filter function is a promise, this function * will wait for it to be fulfilled before determining whether to insert the * element into the new array. * - *

        If the filter function throws or returns a rejected promise, the promise + * If the filter function throws or returns a rejected promise, the promise * returned by this function will be rejected with the same reason. Only the * first failure will be reported; all subsequent errors will be silently * ignored. * - * @param {!(Array.|webdriver.promise.Promise.>)} arr The + * @param {!(Array|promise.Promise>)} arr The * array to iterator over, or a promise that will resolve to said array. - * @param {function(this: SELF, TYPE, number, !Array.): ( - * boolean|webdriver.promise.Promise.)} fn The function + * @param {function(this: SELF, TYPE, number, !Array): ( + * boolean|promise.Promise)} fn The function * to call for each element in the array. * @param {SELF=} opt_self The object to be used as the value of 'this' within * {@code fn}. * @template TYPE, SELF */ -webdriver.promise.filter = function(arr, fn, opt_self) { - return webdriver.promise.when(arr, function(arr) { - var originalValues = goog.array.clone(arr); - return webdriver.promise.map(arr, fn, opt_self).then(function(include) { - return goog.array.filter(originalValues, function(value, index) { - return include[index]; - }); +promise.filter = function(arr, fn, opt_self) { + return promise.fulfilled(arr).then(function(arr) { + goog.asserts.assertNumber(arr.length, 'not an array like value'); + return new promise.Promise(function(fulfill, reject) { + var n = arr.length; + var values = []; + var valuesLength = 0; + (function processNext(i) { + for (; i < n; i++) { + if (i in arr) { + break; + } + } + if (i >= n) { + fulfill(values); + return; + } + try { + var value = arr[i]; + var include = fn.call(opt_self, value, i, /** @type {!Array} */(arr)); + promise.asap(include, function(include) { + if (include) { + values[valuesLength++] = value; + } + processNext(i + 1); + }, reject); + } catch (ex) { + reject(ex); + } + })(0); }); }); }; @@ -849,45 +1084,42 @@ webdriver.promise.filter = function(arr, fn, opt_self) { * * Warning: This function makes no checks against objects that contain * cyclical references: - *

        
        - *   var value = {};
        - *   value['self'] = value;
        - *   webdriver.promise.fullyResolved(value);  // Stack overflow.
        - * 
        + * + * var value = {}; + * value['self'] = value; + * promise.fullyResolved(value); // Stack overflow. * * @param {*} value The value to fully resolve. - * @return {!webdriver.promise.Promise} A promise for a fully resolved version + * @return {!promise.Promise} A promise for a fully resolved version * of the input value. */ -webdriver.promise.fullyResolved = function(value) { - if (webdriver.promise.isPromise(value)) { - return webdriver.promise.when(value, webdriver.promise.fullyResolveValue_); +promise.fullyResolved = function(value) { + if (promise.isPromise(value)) { + return promise.when(value, fullyResolveValue); } - return webdriver.promise.fullyResolveValue_(value); + return fullyResolveValue(value); }; /** * @param {*} value The value to fully resolve. If a promise, assumed to * already be resolved. - * @return {!webdriver.promise.Promise} A promise for a fully resolved version + * @return {!promise.Promise} A promise for a fully resolved version * of the input value. - * @private */ -webdriver.promise.fullyResolveValue_ = function(value) { + function fullyResolveValue(value) { switch (goog.typeOf(value)) { case 'array': - return webdriver.promise.fullyResolveKeys_( - /** @type {!Array} */ (value)); + return fullyResolveKeys(/** @type {!Array} */ (value)); case 'object': - if (webdriver.promise.isPromise(value)) { + if (promise.isPromise(value)) { // We get here when the original input value is a promise that // resolves to itself. When the user provides us with such a promise, // trust that it counts as a "fully resolved" value and return it. // Of course, since it's already a promise, we can just return it // to the user instead of wrapping it in another promise. - return /** @type {!webdriver.promise.Promise} */ (value); + return /** @type {!promise.Promise} */ (value); } if (goog.isNumber(value.nodeType) && @@ -895,76 +1127,72 @@ webdriver.promise.fullyResolveValue_ = function(value) { goog.isNumber(value.ownerDocument.nodeType)) { // DOM node; return early to avoid infinite recursion. Should we // only support objects with a certain level of nesting? - return webdriver.promise.fulfilled(value); + return promise.fulfilled(value); } - return webdriver.promise.fullyResolveKeys_( - /** @type {!Object} */ (value)); + return fullyResolveKeys(/** @type {!Object} */ (value)); default: // boolean, function, null, number, string, undefined - return webdriver.promise.fulfilled(value); + return promise.fulfilled(value); } }; /** * @param {!(Array|Object)} obj the object to resolve. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the + * @return {!promise.Promise} A promise that will be resolved with the * input object once all of its values have been fully resolved. - * @private */ -webdriver.promise.fullyResolveKeys_ = function(obj) { + function fullyResolveKeys(obj) { var isArray = goog.isArray(obj); - var numKeys = isArray ? obj.length : goog.object.getCount(obj); + var numKeys = isArray ? obj.length : Objects.getCount(obj); if (!numKeys) { - return webdriver.promise.fulfilled(obj); + return promise.fulfilled(obj); } var numResolved = 0; - var deferred = new webdriver.promise.Deferred(); - - // In pre-IE9, goog.array.forEach will not iterate properly over arrays - // containing undefined values because "index in array" returns false - // when array[index] === undefined (even for x = [undefined, 1]). To get - // around this, we need to use our own forEach implementation. - // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be - // reproduced in IE9 by changing the browser/document modes, it requires an - // actual pre-IE9 browser. Yay, IE! - var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) { - var n = arr.length; - for (var i = 0; i < n; ++i) { - fn.call(null, arr[i], i, arr); - } - }; - - forEachKey(obj, function(partialValue, key) { - var type = goog.typeOf(partialValue); - if (type != 'array' && type != 'object') { - maybeResolveValue(); - return; - } + return new promise.Promise(function(fulfill, reject) { + // In pre-IE9, goog.array.forEach will not iterate properly over arrays + // containing undefined values because "index in array" returns false + // when array[index] === undefined (even for x = [undefined, 1]). To get + // around this, we need to use our own forEach implementation. + // DO NOT REMOVE THIS UNTIL WE NO LONGER SUPPORT IE8. This cannot be + // reproduced in IE9 by changing the browser/document modes, it requires an + // actual pre-IE9 browser. Yay, IE! + var forEachKey = !isArray ? Objects.forEach : function(arr, fn) { + var n = arr.length; + for (var i = 0; i < n; ++i) { + fn.call(null, arr[i], i, arr); + } + }; - webdriver.promise.fullyResolved(partialValue).then( - function(resolvedValue) { - obj[key] = resolvedValue; - maybeResolveValue(); - }, - deferred.reject); - }); + forEachKey(obj, function(partialValue, key) { + var type = goog.typeOf(partialValue); + if (type != 'array' && type != 'object') { + maybeResolveValue(); + return; + } - return deferred.promise; + promise.fullyResolved(partialValue).then( + function(resolvedValue) { + obj[key] = resolvedValue; + maybeResolveValue(); + }, + reject); + }); - function maybeResolveValue() { - if (++numResolved == numKeys) { - deferred.fulfill(obj); + function maybeResolveValue() { + if (++numResolved == numKeys) { + fulfill(obj); + } } - } + }); }; ////////////////////////////////////////////////////////////////////////////// // -// webdriver.promise.ControlFlow +// promise.ControlFlow // ////////////////////////////////////////////////////////////////////////////// @@ -976,98 +1204,133 @@ webdriver.promise.fullyResolveKeys_ = function(obj) { * the ordered scheduled, starting each task only once those before it have * completed. * - *

        Each task scheduled within this flow may return a + * Each task scheduled within this flow may return a * {@link webdriver.promise.Promise} to indicate it is an asynchronous * operation. The ControlFlow will wait for such promises to be resolved before * marking the task as completed. * - *

        Tasks and each callback registered on a {@link webdriver.promise.Deferred} + * Tasks and each callback registered on a {@link webdriver.promise.Promise} * will be run in their own ControlFlow frame. Any tasks scheduled within a - * frame will have priority over previously scheduled tasks. Furthermore, if - * any of the tasks in the frame fails, the remainder of the tasks in that frame - * will be discarded and the failure will be propagated to the user through the + * frame will take priority over previously scheduled tasks. Furthermore, if any + * of the tasks in the frame fail, the remainder of the tasks in that frame will + * be discarded and the failure will be propagated to the user through the * callback/task's promised result. * - *

        Each time a ControlFlow empties its task queue, it will fire an - * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Conversely, + * Each time a ControlFlow empties its task queue, it will fire an + * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event. Conversely, * whenever the flow terminates due to an unhandled error, it will remove all * remaining tasks in its queue and fire an - * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION} event. If - * there are no listeners registered with the flow, the error will be - * rethrown to the global error handler. + * {@link webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION + * UNCAUGHT_EXCEPTION} event. If there are no listeners registered with the + * flow, the error will be rethrown to the global error handler. * - * @param {webdriver.promise.ControlFlow.Timer=} opt_timer The timer object - * to use. Should only be set for testing. * @constructor - * @extends {webdriver.EventEmitter} + * @extends {EventEmitter} + * @final */ -webdriver.promise.ControlFlow = function(opt_timer) { - webdriver.EventEmitter.call(this); +promise.ControlFlow = function() { + EventEmitter.call(this); + goog.getUid(this); /** - * The timer used by this instance. - * @type {webdriver.promise.ControlFlow.Timer} + * Tracks the active execution frame for this instance. Lazily initialized + * when the first task is scheduled. + * @private {Frame} */ - this.timer = opt_timer || webdriver.promise.ControlFlow.defaultTimer; + this.activeFrame_ = null; /** - * A list of recent tasks. Each time a new task is started, or a frame is - * completed, the previously recorded task is removed from this list. If - * there are multiple tasks, task N+1 is considered a sub-task of task - * N. - * @private {!Array.} + * A reference to the frame which is currently top of the stack in + * {@link #runInFrame_}. The {@link #activeFrame_} will always be an ancestor + * of the {@link #runningFrame_}, but the two will often not be the same. The + * active frame represents which frame is currently executing a task while the + * running frame represents either the task itself or a promise callback which + * has fired asynchronously. + * @private {Frame} */ - this.history_ = []; -}; -goog.inherits(webdriver.promise.ControlFlow, webdriver.EventEmitter); + this.runningFrame_ = null; + /** + * A reference to the frame in which new tasks should be scheduled. If + * {@code null}, tasks will be scheduled within the active frame. When forcing + * a function to run in the context of a new frame, this pointer is used to + * ensure tasks are scheduled within the newly created frame, even though it + * won't be active yet. + * @private {Frame} + * @see {#runInFrame_} + */ + this.schedulingFrame_ = null; -/** - * @typedef {{clearInterval: function(number), - * clearTimeout: function(number), - * setInterval: function(!Function, number): number, - * setTimeout: function(!Function, number): number}} - */ -webdriver.promise.ControlFlow.Timer; + /** + * Micro task that controls shutting down the control flow. Upon shut down, + * the flow will emit an {@link webdriver.promise.ControlFlow.EventType.IDLE} + * event. Idle events always follow a brief timeout in order to catch latent + * errors from the last completed task. If this task had a callback + * registered, but no errback, and the task fails, the unhandled failure would + * not be reported by the promise system until the next turn of the event + * loop: + * + * // Schedule 1 task that fails. + * var result = promise.controlFlow().schedule('example', + * function() { return promise.rejected('failed'); }); + * // Set a callback on the result. This delays reporting the unhandled + * // failure for 1 turn of the event loop. + * result.then(goog.nullFunction); + * + * @private {MicroTask} + */ + this.shutdownTask_ = null; + /** + * Micro task used to trigger execution of this instance's event loop. + * @private {MicroTask} + */ + this.eventLoopTask_ = null; -/** - * The default timer object, which uses the global timer functions. - * @type {webdriver.promise.ControlFlow.Timer} - */ -webdriver.promise.ControlFlow.defaultTimer = (function() { - // The default timer functions may be defined as free variables for the - // current context, so do not reference them using "window" or - // "goog.global". Also, we must invoke them in a closure, and not using - // bind(), so we do not get "TypeError: Illegal invocation" (WebKit) or - // "Invalid calling object" (IE) errors. - return { - clearInterval: wrap(clearInterval), - clearTimeout: wrap(clearTimeout), - setInterval: wrap(setInterval), - setTimeout: wrap(setTimeout) - }; + /** + * ID for a long running interval used to keep a Node.js process running + * while a control flow's event loop has yielded. This is a cheap hack + * required since the {@link #runEventLoop_} is only scheduled to run when + * there is _actually_ something to run. When a control flow is waiting on + * a task, there will be nothing in the JS event loop and the process would + * terminate without this. + * + * An alternative solution would be to change {@link #runEventLoop_} to run + * as an interval rather than as on-demand micro-tasks. While this approach + * (which was previously used) requires fewer micro-task allocations, it + * results in many unnecessary invocations of {@link #runEventLoop_}. + * + * @private {?number} + */ + this.hold_ = null; - function wrap(fn) { - return function() { - // Cannot use .call() or .apply() since we do not know which variable - // the function is bound to, and using the wrong one will generate - // an error. - return fn(arguments[0], arguments[1]); - }; - } -})(); + /** + * The number of holds placed on this flow. These represent points where the + * flow must not execute any further actions so an asynchronous action may + * run first. One such example are notifications fired by a + * {@link webdriver.promise.Promise}: the Promise spec requires that callbacks + * are invoked in a turn of the event loop after they are scheduled. To ensure + * tasks within a callback are scheduled in the correct frame, a promise will + * make the parent flow yield before its notifications are fired. + * @private {number} + */ + this.yieldCount_ = 0; +}; +goog.inherits(promise.ControlFlow, EventEmitter); /** * Events that may be emitted by an {@link webdriver.promise.ControlFlow}. * @enum {string} */ -webdriver.promise.ControlFlow.EventType = { +promise.ControlFlow.EventType = { /** Emitted when all tasks have been successfully executed. */ IDLE: 'idle', + /** Emitted when a ControlFlow has been reset. */ + RESET: 'reset', + /** Emitted whenever a new task has been scheduled. */ SCHEDULE_TASK: 'scheduleTask', @@ -1082,94 +1345,23 @@ webdriver.promise.ControlFlow.EventType = { /** - * How often, in milliseconds, the event loop should run. - * @type {number} - * @const - */ -webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY = 10; - - -/** - * Tracks the active execution frame for this instance. Lazily initialized - * when the first task is scheduled. - * @private {webdriver.promise.Frame_} + * Returns a string representation of this control flow, which is its current + * {@link #getSchedule() schedule}, sans task stack traces. + * @return {string} The string representation of this contorl flow. + * @override */ -webdriver.promise.ControlFlow.prototype.activeFrame_ = null; - - -/** - * A reference to the frame in which new tasks should be scheduled. If - * {@code null}, tasks will be scheduled within the active frame. When forcing - * a function to run in the context of a new frame, this pointer is used to - * ensure tasks are scheduled within the newly created frame, even though it - * won't be active yet. - * @private {webdriver.promise.Frame_} - * @see {#runInNewFrame_} - */ -webdriver.promise.ControlFlow.prototype.schedulingFrame_ = null; - - -/** - * Timeout ID set when the flow is about to shutdown without any errors - * being detected. Upon shutting down, the flow will emit an - * {@link webdriver.promise.ControlFlow.EventType.IDLE} event. Idle events - * always follow a brief timeout in order to catch latent errors from the last - * completed task. If this task had a callback registered, but no errback, and - * the task fails, the unhandled failure would not be reported by the promise - * system until the next turn of the event loop: - * - * // Schedule 1 task that fails. - * var result = webriver.promise.controlFlow().schedule('example', - * function() { return webdriver.promise.rejected('failed'); }); - * // Set a callback on the result. This delays reporting the unhandled - * // failure for 1 turn of the event loop. - * result.then(goog.nullFunction); - * - * @private {?number} - */ -webdriver.promise.ControlFlow.prototype.shutdownId_ = null; - - -/** - * Interval ID for this instance's event loop. - * @private {?number} - */ -webdriver.promise.ControlFlow.prototype.eventLoopId_ = null; - - -/** - * The number of "pending" promise rejections. - * - *

        Each time a promise is rejected and is not handled by a listener, it will - * schedule a 0-based timeout to check if it is still unrejected in the next - * turn of the JS-event loop. This allows listeners to attach to, and handle, - * the rejected promise at any point in same turn of the event loop that the - * promise was rejected. - * - *

        When this flow's own event loop triggers, it will not run if there - * are any outstanding promise rejections. This allows unhandled promises to - * be reported before a new task is started, ensuring the error is reported to - * the current task queue. - * - * @private {number} - */ -webdriver.promise.ControlFlow.prototype.pendingRejections_ = 0; - - -/** - * The number of aborted frames since the last time a task was executed or a - * frame completed successfully. - * @private {number} - */ -webdriver.promise.ControlFlow.prototype.numAbortedFrames_ = 0; +promise.ControlFlow.prototype.toString = function() { + return this.getSchedule(); +}; /** * Resets this instance, clearing its queue and removing all event listeners. */ -webdriver.promise.ControlFlow.prototype.reset = function() { +promise.ControlFlow.prototype.reset = function() { this.activeFrame_ = null; - this.clearHistory(); + this.schedulingFrame_ = null; + this.emit(promise.ControlFlow.EventType.RESET); this.removeAllListeners(); this.cancelShutdown_(); this.cancelEventLoop_(); @@ -1177,130 +1369,139 @@ webdriver.promise.ControlFlow.prototype.reset = function() { /** - * Returns a summary of the recent task activity for this instance. This - * includes the most recently completed task, as well as any parent tasks. In - * the returned summary, the task at index N is considered a sub-task of the - * task at index N+1. - * @return {!Array.} A summary of this instance's recent task - * activity. + * Generates an annotated string describing the internal state of this control + * flow, including the currently executing as well as pending tasks. If + * {@code opt_includeStackTraces === true}, the string will include the + * stack trace from when each task was scheduled. + * @param {string=} opt_includeStackTraces Whether to include the stack traces + * from when each task was scheduled. Defaults to false. + * @return {string} String representation of this flow's internal state. */ -webdriver.promise.ControlFlow.prototype.getHistory = function() { - var pendingTasks = []; - var currentFrame = this.activeFrame_; - while (currentFrame) { - var task = currentFrame.getPendingTask(); - if (task) { - pendingTasks.push(task); - } - // A frame's parent node will always be another frame. - currentFrame = - /** @type {webdriver.promise.Frame_} */ (currentFrame.getParent()); +promise.ControlFlow.prototype.getSchedule = function(opt_includeStackTraces) { + var ret = 'ControlFlow::' + goog.getUid(this); + var activeFrame = this.activeFrame_; + var runningFrame = this.runningFrame_; + if (!activeFrame) { + return ret; } + var childIndent = '| '; + return ret + '\n' + toStringHelper(activeFrame.getRoot(), childIndent); - var fullHistory = goog.array.concat(this.history_, pendingTasks); - return goog.array.map(fullHistory, function(task) { - return task.toString(); - }); -}; - - -/** Clears this instance's task history. */ -webdriver.promise.ControlFlow.prototype.clearHistory = function() { - this.history_ = []; + /** + * @param {!(Frame|Task)} node . + * @param {string} indent . + * @param {boolean=} opt_isPending . + * @return {string} . + */ + function toStringHelper(node, indent, opt_isPending) { + var ret = node.toString(); + if (opt_isPending) { + ret = '(pending) ' + ret; + } + if (node === activeFrame) { + ret = '(active) ' + ret; + } + if (node === runningFrame) { + ret = '(running) ' + ret; + } + if (node instanceof Frame) { + if (node.getPendingTask()) { + ret += '\n' + toStringHelper( + /** @type {!Task} */(node.getPendingTask()), + childIndent, + true); + } + if (node.children_) { + node.children_.forEach(function(child) { + if (!node.getPendingTask() || + node.getPendingTask().getFrame() !== child) { + ret += '\n' + toStringHelper(child, childIndent); + } + }); + } + } else { + var task = /** @type {!Task} */(node); + if (opt_includeStackTraces && task.promise.stack_) { + ret += '\n' + childIndent + + (task.promise.stack_.stack || task.promise.stack_). + replace(/\n/g, '\n' + childIndent); + } + if (task.getFrame()) { + ret += '\n' + toStringHelper( + /** @type {!Frame} */(task.getFrame()), + childIndent); + } + } + return indent + ret.replace(/\n/g, '\n' + indent); + } }; /** - * Removes a completed task from this instance's history record. If any - * tasks remain from aborted frames, those will be removed as well. + * @return {!Frame} The active frame for this flow. * @private */ -webdriver.promise.ControlFlow.prototype.trimHistory_ = function() { - if (this.numAbortedFrames_) { - goog.array.splice(this.history_, - this.history_.length - this.numAbortedFrames_, - this.numAbortedFrames_); - this.numAbortedFrames_ = 0; +promise.ControlFlow.prototype.getActiveFrame_ = function() { + this.cancelShutdown_(); + if (!this.activeFrame_) { + this.activeFrame_ = new Frame(this); + this.activeFrame_.once(Frame.ERROR_EVENT, this.abortNow_, this); + this.scheduleEventLoopStart_(); } - this.history_.pop(); + return this.activeFrame_; }; /** - * Property used to track whether an error has been annotated by - * {@link webdriver.promise.ControlFlow#annotateError}. - * @private {string} - * @const - */ -webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_ = - 'webdriver_promise_error_'; - - -/** - * Appends a summary of this instance's recent task history to the given - * error's stack trace. This function will also ensure the error's stack trace - * is in canonical form. - * @param {!(Error|goog.testing.JsUnitException)} e The error to annotate. - * @return {!(Error|goog.testing.JsUnitException)} The annotated error. + * @return {!Frame} The frame that new items should be added to. + * @private */ -webdriver.promise.ControlFlow.prototype.annotateError = function(e) { - if (!!e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_]) { - return e; - } - - var history = this.getHistory(); - if (history.length) { - e = webdriver.stacktrace.format(e); - - /** @type {!Error} */(e).stack += [ - '\n==== async task ====\n', - history.join('\n==== async task ====\n') - ].join(''); - - e[webdriver.promise.ControlFlow.ANNOTATION_PROPERTY_] = true; - } - - return e; +promise.ControlFlow.prototype.getSchedulingFrame_ = function() { + return this.schedulingFrame_ || this.getActiveFrame_(); }; /** - * @return {string} The scheduled tasks still pending with this instance. + * @return {!Frame} The frame that is current executing. + * @private */ -webdriver.promise.ControlFlow.prototype.getSchedule = function() { - return this.activeFrame_ ? this.activeFrame_.getRoot().toString() : '[]'; +promise.ControlFlow.prototype.getRunningFrame_ = function() { + return this.runningFrame_ || this.getActiveFrame_(); }; /** * Schedules a task for execution. If there is nothing currently in the - * queue, the task will be executed in the next turn of the event loop. + * queue, the task will be executed in the next turn of the event loop. If + * the task function is a generator, the task will be executed using + * {@link webdriver.promise.consume}. * - * @param {!Function} fn The function to call to start the task. If the - * function returns a {@link webdriver.promise.Promise}, this instance - * will wait for it to be resolved before starting the next task. + * @param {function(): (T|promise.Promise)} fn The function to + * call to start the task. If the function returns a + * {@link webdriver.promise.Promise}, this instance will wait for it to be + * resolved before starting the next task. * @param {string=} opt_description A description of the task. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * the result of the action. + * @return {!promise.Promise} A promise that will be resolved + * with the result of the action. + * @template T */ -webdriver.promise.ControlFlow.prototype.execute = function( - fn, opt_description) { - this.cancelShutdown_(); - - if (!this.activeFrame_) { - this.activeFrame_ = new webdriver.promise.Frame_(this); +promise.ControlFlow.prototype.execute = function(fn, opt_description) { + if (promise.isGenerator(fn)) { + fn = goog.partial(promise.consume, fn); } - // Trim an extra frame off the generated stack trace for the call to this - // function. - var snapshot = new webdriver.stacktrace.Snapshot(1); - var task = new webdriver.promise.Task_( - this, fn, opt_description || '', snapshot); - var scheduleIn = this.schedulingFrame_ || this.activeFrame_; - scheduleIn.addChild(task); + if (!this.hold_) { + var holdIntervalMs = 2147483647; // 2^31-1; max timer length for Node.js + this.hold_ = setInterval(goog.nullFunction, holdIntervalMs); + } - this.emit(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK); + var description = opt_description || ''; + var task = new Task(this, fn, description); + task.promise.stack_ = promise.captureStackTrace('Task', description, + promise.ControlFlow.prototype.execute); + this.getSchedulingFrame_().addChild(task); + this.emit(promise.ControlFlow.EventType.SCHEDULE_TASK, opt_description); this.scheduleEventLoopStart_(); return task.promise; }; @@ -1312,13 +1513,12 @@ webdriver.promise.ControlFlow.prototype.execute = function( * * @param {number} ms The timeout delay, in milliseconds. * @param {string=} opt_description A description to accompany the timeout. - * @return {!webdriver.promise.Promise} A promise that will be resolved with + * @return {!promise.Promise} A promise that will be resolved with * the result of the action. */ -webdriver.promise.ControlFlow.prototype.timeout = function( - ms, opt_description) { +promise.ControlFlow.prototype.timeout = function(ms, opt_description) { return this.execute(function() { - return webdriver.promise.delayed(ms); + return promise.delayed(ms); }, opt_description); }; @@ -1327,92 +1527,153 @@ webdriver.promise.ControlFlow.prototype.timeout = function( * Schedules a task that shall wait for a condition to hold. Each condition * function may return any value, but it will always be evaluated as a boolean. * - *

        Condition functions may schedule sub-tasks with this instance, however, + * Condition functions may schedule sub-tasks with this instance, however, * their execution time will be factored into whether a wait has timed out. * - *

        In the event a condition returns a Promise, the polling loop will wait for + * In the event a condition returns a Promise, the polling loop will wait for * it to be resolved before evaluating whether the condition has been satisfied. * The resolution time for a promise is factored into whether a wait has timed * out. * - *

        If the condition function throws, or returns a rejected promise, the + * If the condition function throws, or returns a rejected promise, the * wait task will fail. * - * @param {!Function} condition The condition function to poll. - * @param {number} timeout How long to wait, in milliseconds, for the condition - * to hold before timing out. + * If the condition is defined as a promise, the flow will wait for it to + * settle. If the timeout expires before the promise settles, the promise + * returned by this function will be rejected. + * + * If this function is invoked with `timeout === 0`, or the timeout is omitted, + * the flow will wait indefinitely for the condition to be satisfied. + * + * @param {(!promise.Promise|function())} condition The condition to poll, + * or a promise to wait on. + * @param {number=} opt_timeout How long to wait, in milliseconds, for the + * condition to hold before timing out. If omitted, the flow will wait + * indefinitely. * @param {string=} opt_message An optional error message to include if the * wait times out; defaults to the empty string. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * condition has been satisified. The promise shall be rejected if the wait - * times out waiting for the condition. + * @return {!promise.Promise} A promise that will be fulfilled + * when the condition has been satisified. The promise shall be rejected if + * the wait times out waiting for the condition. + * @throws {TypeError} If condition is not a function or promise or if timeout + * is not a number >= 0. + * @template T */ -webdriver.promise.ControlFlow.prototype.wait = function( - condition, timeout, opt_message) { - var sleep = Math.min(timeout, 100); - var self = this; +promise.ControlFlow.prototype.wait = function( + condition, opt_timeout, opt_message) { + var timeout = opt_timeout || 0; + if (!goog.isNumber(timeout) || timeout < 0) { + throw TypeError('timeout must be a number >= 0: ' + timeout); + } + + if (promise.isPromise(condition)) { + return this.execute(function() { + if (!timeout) { + return condition; + } + return new promise.Promise(function(fulfill, reject) { + var start = goog.now(); + var timer = setTimeout(function() { + timer = null; + reject(Error((opt_message ? opt_message + '\n' : '') + + 'Timed out waiting for promise to resolve after ' + + (goog.now() - start) + 'ms')); + }, timeout); + + /** @type {Thenable} */(condition).then( + function(value) { + timer && clearTimeout(timer); + fulfill(value); + }, + function(error) { + timer && clearTimeout(timer); + reject(error); + }); + }); + }, opt_message || ''); + } + + if (!goog.isFunction(condition)) { + throw TypeError('Invalid condition; must be a function or promise: ' + + goog.typeOf(condition)); + } + + if (promise.isGenerator(condition)) { + condition = goog.partial(promise.consume, condition); + } + var self = this; return this.execute(function() { var startTime = goog.now(); - var waitResult = new webdriver.promise.Deferred(); - var waitFrame = self.activeFrame_; - waitFrame.isWaiting = true; - pollCondition(); - return waitResult.promise; - - function pollCondition() { - self.runInNewFrame_(condition, function(value) { - var elapsed = goog.now() - startTime; - if (!!value) { - waitFrame.isWaiting = false; - waitResult.fulfill(value); - } else if (elapsed >= timeout) { - waitResult.reject(new Error((opt_message ? opt_message + '\n' : '') + - 'Wait timed out after ' + elapsed + 'ms')); - } else { - self.timer.setTimeout(pollCondition, sleep); - } - }, waitResult.reject, true); - } - }, opt_message); + return new promise.Promise(function(fulfill, reject) { + self.suspend_(); + pollCondition(); + + function pollCondition() { + self.resume_(); + self.execute(/**@type {function()}*/(condition)).then(function(value) { + var elapsed = goog.now() - startTime; + if (!!value) { + fulfill(value); + } else if (timeout && elapsed >= timeout) { + reject(new Error((opt_message ? opt_message + '\n' : '') + + 'Wait timed out after ' + elapsed + 'ms')); + } else { + self.suspend_(); + // Do not use asyncRun here because we need a non-micro yield + // here so the UI thread is given a chance when running in a + // browser. + setTimeout(pollCondition, 0); + } + }, reject); + } + }); + }, opt_message || ''); }; /** - * Schedules a task that will wait for another promise to resolve. The resolved - * promise's value will be returned as the task result. - * @param {!webdriver.promise.Promise} promise The promise to wait on. - * @return {!webdriver.promise.Promise} A promise that will resolve when the - * task has completed. + * Schedules the interval for this instance's event loop, if necessary. + * @private */ -webdriver.promise.ControlFlow.prototype.await = function(promise) { - return this.execute(function() { - return promise; - }); +promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() { + if (!this.eventLoopTask_ && !this.yieldCount_ && this.activeFrame_ && + !this.activeFrame_.getPendingTask()) { + this.eventLoopTask_ = new MicroTask(this.runEventLoop_, this); + } }; /** - * Schedules the interval for this instance's event loop, if necessary. + * Cancels the event loop, if necessary. * @private */ -webdriver.promise.ControlFlow.prototype.scheduleEventLoopStart_ = function() { - if (!this.eventLoopId_) { - this.eventLoopId_ = this.timer.setInterval( - goog.bind(this.runEventLoop_, this), - webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY); +promise.ControlFlow.prototype.cancelEventLoop_ = function() { + if (this.eventLoopTask_) { + this.eventLoopTask_.cancel(); + this.eventLoopTask_ = null; } }; /** - * Cancels the event loop, if necessary. + * Suspends this control flow, preventing it from executing any more tasks. + * @private + */ +promise.ControlFlow.prototype.suspend_ = function() { + this.yieldCount_ += 1; + this.cancelEventLoop_(); +}; + + +/** + * Resumes execution of tasks scheduled within this control flow. * @private */ -webdriver.promise.ControlFlow.prototype.cancelEventLoop_ = function() { - if (this.eventLoopId_) { - this.timer.clearInterval(this.eventLoopId_); - this.eventLoopId_ = null; +promise.ControlFlow.prototype.resume_ = function() { + this.yieldCount_ -= 1; + if (!this.yieldCount_ && this.activeFrame_) { + this.scheduleEventLoopStart_(); } }; @@ -1424,102 +1685,99 @@ webdriver.promise.ControlFlow.prototype.cancelEventLoop_ = function() { * the top of the stack. * @private */ -webdriver.promise.ControlFlow.prototype.runEventLoop_ = function() { - // If we get here and there are pending promise rejections, then those - // promises are queued up to run as soon as this (JS) event loop terminates. - // Short-circuit our loop to give those promises a chance to run. Otherwise, - // we might start a new task only to have it fail because of one of these - // pending rejections. - if (this.pendingRejections_) { +promise.ControlFlow.prototype.runEventLoop_ = function() { + this.eventLoopTask_ = null; + + if (this.yieldCount_) { return; } - // If the flow aborts due to an unhandled exception after we've scheduled - // another turn of the execution loop, we can end up in here with no tasks - // left. This is OK, just quietly return. if (!this.activeFrame_) { this.commenceShutdown_(); return; } - var task; - if (this.activeFrame_.getPendingTask() || !(task = this.getNextTask_())) { - // Either the current frame is blocked on a pending task, or we don't have - // a task to finish because we've completed a frame. When completing a - // frame, we must abort the event loop to allow the frame's promise's - // callbacks to execute. + if (this.activeFrame_.getPendingTask()) { + return; + } + + var task = this.getNextTask_(); + if (!task) { return; } var activeFrame = this.activeFrame_; - activeFrame.setPendingTask(task); - var markTaskComplete = goog.bind(function() { - this.history_.push(/** @type {!webdriver.promise.Task_} */ (task)); + var scheduleEventLoop = goog.bind(this.scheduleEventLoopStart_, this); + + var onSuccess = function(value) { activeFrame.setPendingTask(null); - }, this); + task.setFrame(null); + task.fulfill(value); + scheduleEventLoop(); + }; - this.trimHistory_(); - var self = this; - this.runInNewFrame_(task.execute, function(result) { - markTaskComplete(); - task.fulfill(result); - }, function(error) { - markTaskComplete(); - - if (!webdriver.promise.isError_(error) && - !webdriver.promise.isPromise(error)) { - error = Error(error); - } + var onFailure = function(reason) { + activeFrame.setPendingTask(null); + task.setFrame(null); + task.reject(reason); + scheduleEventLoop(); + }; - task.reject(self.annotateError(/** @type {!Error} */ (error))); - }, true); + activeFrame.setPendingTask(task); + var frame = new Frame(this); + task.setFrame(frame); + this.runInFrame_(frame, task.execute, function(result) { + promise.asap(result, onSuccess, onFailure); + }, onFailure, true); }; /** - * @return {webdriver.promise.Task_} The next task to execute, or + * @return {Task} The next task to execute, or * {@code null} if a frame was resolved. * @private */ -webdriver.promise.ControlFlow.prototype.getNextTask_ = function() { - var firstChild = this.activeFrame_.getFirstChild(); +promise.ControlFlow.prototype.getNextTask_ = function() { + var frame = this.activeFrame_; + var firstChild = frame.getFirstChild(); if (!firstChild) { - if (!this.activeFrame_.isWaiting) { - this.resolveFrame_(this.activeFrame_); + if (!frame.pendingCallback && !frame.isBlocked_) { + this.resolveFrame_(frame); } return null; } - if (firstChild instanceof webdriver.promise.Frame_) { + if (firstChild instanceof Frame) { this.activeFrame_ = firstChild; return this.getNextTask_(); } - firstChild.getParent().removeChild(firstChild); + frame.removeChild(firstChild); + if (!firstChild.isPending()) { + return this.getNextTask_(); + } return firstChild; }; /** - * @param {!webdriver.promise.Frame_} frame The frame to resolve. + * @param {!Frame} frame The frame to resolve. * @private */ -webdriver.promise.ControlFlow.prototype.resolveFrame_ = function(frame) { +promise.ControlFlow.prototype.resolveFrame_ = function(frame) { if (this.activeFrame_ === frame) { - // Frame parent is always another frame, but the compiler is not smart - // enough to recognize this. - this.activeFrame_ = - /** @type {webdriver.promise.Frame_} */ (frame.getParent()); + this.activeFrame_ = frame.getParent(); } if (frame.getParent()) { frame.getParent().removeChild(frame); } - this.trimHistory_(); - frame.fulfill(); + frame.emit(Frame.CLOSE_EVENT); if (!this.activeFrame_) { this.commenceShutdown_(); + } else { + this.scheduleEventLoopStart_(); } }; @@ -1530,15 +1788,11 @@ webdriver.promise.ControlFlow.prototype.resolveFrame_ = function(frame) { * immediately terminate all execution. * @param {*} error The reason the frame is being aborted; typically either * an Error or string. + * @param {Frame=} opt_frame The frame to abort; will use the + * currently active frame if not specified. * @private */ -webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) { - // Annotate the error value if it is Error-like. - if (webdriver.promise.isError_(error)) { - this.annotateError(/** @type {!Error} */ (error)); - } - this.numAbortedFrames_++; - +promise.ControlFlow.prototype.abortFrame_ = function(error, opt_frame) { if (!this.activeFrame_) { this.abortNow_(error); return; @@ -1546,7 +1800,7 @@ webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) { // Frame parent is always another frame, but the compiler is not smart // enough to recognize this. - var parent = /** @type {webdriver.promise.Frame_} */ ( + var parent = /** @type {Frame} */ ( this.activeFrame_.getParent()); if (parent) { parent.removeChild(this.activeFrame_); @@ -1554,88 +1808,141 @@ webdriver.promise.ControlFlow.prototype.abortFrame_ = function(error) { var frame = this.activeFrame_; this.activeFrame_ = parent; - frame.reject(error); + frame.abort(error); }; /** - * Executes a function in a new frame. If the function does not schedule any new - * tasks, the frame will be discarded and the function's result returned - * immediately. Otherwise, a promise will be returned. This promise will be - * resolved with the function's result once all of the tasks scheduled within - * the function have been completed. If the function's frame is aborted, the - * returned promise will be rejected. + * Executes a function within a specific frame. If the function does not + * schedule any new tasks, the frame will be discarded and the function's result + * returned passed to the given callback immediately. Otherwise, the callback + * will be invoked once all of the tasks scheduled within the function have been + * completed. If the frame is aborted, the `errback` will be invoked with the + * offending error. * + * @param {!Frame} newFrame The frame to use. * @param {!Function} fn The function to execute. - * @param {function(*)} callback The function to call with a successful result. + * @param {function(T)} callback The function to call with a successful result. * @param {function(*)} errback The function to call if there is an error. - * @param {boolean=} opt_activate Whether the active frame should be updated to - * the newly created frame so tasks are treated as sub-tasks. + * @param {boolean=} opt_isTask Whether the function is a task and the frame + * should be immediately activated to capture subtasks and errors. + * @throws {Error} If this function is invoked while another call to this + * function is present on the stack. + * @template T * @private */ -webdriver.promise.ControlFlow.prototype.runInNewFrame_ = function( - fn, callback, errback, opt_activate) { - var newFrame = new webdriver.promise.Frame_(this), - self = this, +promise.ControlFlow.prototype.runInFrame_ = function( + newFrame, fn, callback, errback, opt_isTask) { + asserts.assert( + !this.runningFrame_, 'unexpected recursive call to runInFrame'); + + var self = this, oldFrame = this.activeFrame_; try { - if (!this.activeFrame_) { - this.activeFrame_ = newFrame; - } else { + if (this.activeFrame_ !== newFrame && !newFrame.getParent()) { this.activeFrame_.addChild(newFrame); } // Activate the new frame to force tasks to be treated as sub-tasks of // the parent frame. - if (opt_activate) { + if (opt_isTask) { this.activeFrame_ = newFrame; } try { + this.runningFrame_ = newFrame; this.schedulingFrame_ = newFrame; - webdriver.promise.pushFlow_(this); + activeFlows.push(this); var result = fn(); } finally { - webdriver.promise.popFlow_(); + activeFlows.pop(); this.schedulingFrame_ = null; + + // `newFrame` should only be considered the running frame when it is + // actually executing. After it finishes, set top of stack to its parent + // so any failures/interrupts that occur while processing newFrame's + // result are handled there. + this.runningFrame_ = newFrame.parent_; } - newFrame.lockFrame(); + newFrame.isLocked_ = true; // If there was nothing scheduled in the new frame we can discard the // frame and return immediately. - if (!newFrame.children_.length) { + if (isCloseable(newFrame) && (!opt_isTask || !promise.isPromise(result))) { removeNewFrame(); - webdriver.promise.asap(result, callback, errback); + callback(result); return; } - newFrame.then(function() { - webdriver.promise.asap(result, callback, errback); - }, function(e) { - if (result instanceof webdriver.promise.Promise && result.isPending()) { - result.cancel(e); - e = result; + // If the executed function returned a promise, wait for it to resolve. If + // there is nothing scheduled in the frame, go ahead and discard it. + // Otherwise, we wait for the frame to be closed out by the event loop. + var shortCircuitTask; + if (promise.isPromise(result)) { + newFrame.isBlocked_ = true; + var onResolve = function() { + newFrame.isBlocked_ = false; + shortCircuitTask = new MicroTask(function() { + if (isCloseable(newFrame)) { + removeNewFrame(); + callback(result); + } + }); + }; + /** @type {Thenable} */(result).then(onResolve, onResolve); + + // If the result is a thenable, attach a listener to silence any unhandled + // rejection warnings. This is safe because we *will* handle it once the + // frame has completed. + } else if (promise.Thenable.isImplementation(result)) { + /** @type {!promise.Thenable} */(result).thenCatch(goog.nullFunction); + } + + newFrame.once(Frame.CLOSE_EVENT, function() { + shortCircuitTask && shortCircuitTask.cancel(); + if (isCloseable(newFrame)) { + removeNewFrame(); } - errback(e); + callback(result); + }).once(Frame.ERROR_EVENT, function(reason) { + shortCircuitTask && shortCircuitTask.cancel(); + if (promise.Thenable.isImplementation(result) && result.isPending()) { + result.cancel(reason); + } + errback(reason); }); } catch (ex) { - removeNewFrame(new webdriver.promise.CanceledTaskError_(ex)); + removeNewFrame(ex); errback(ex); + } finally { + // No longer running anything, clear the reference. + this.runningFrame_ = null; + } + + function isCloseable(frame) { + return (!frame.children_ || !frame.children_.length) + && !frame.pendingRejection; } /** - * @param {webdriver.promise.CanceledTaskError_=} opt_err If provided, the - * error that triggered the removal of this frame. + * @param {*=} opt_err If provided, the reason that the frame was removed. */ function removeNewFrame(opt_err) { var parent = newFrame.getParent(); if (parent) { parent.removeChild(newFrame); + asyncRun(function() { + if (isCloseable(parent) && parent !== self.activeFrame_) { + parent.emit(Frame.CLOSE_EVENT); + } + }); + self.scheduleEventLoopStart_(); } if (opt_err) { - newFrame.cancelRemainingTasks(opt_err); + newFrame.cancelRemainingTasks(promise.CancellationError.wrap( + opt_err, 'Tasks cancelled due to uncaught error')); } self.activeFrame_ = oldFrame; } @@ -1645,13 +1952,13 @@ webdriver.promise.ControlFlow.prototype.runInNewFrame_ = function( /** * Commences the shutdown sequence for this instance. After one turn of the * event loop, this object will emit the - * {@link webdriver.promise.ControlFlow.EventType.IDLE} event to signal + * {@link webdriver.promise.ControlFlow.EventType.IDLE IDLE} event to signal * listeners that it has completed. During this wait, if another task is * scheduled, the shutdown will be aborted. * @private */ -webdriver.promise.ControlFlow.prototype.commenceShutdown_ = function() { - if (!this.shutdownId_) { +promise.ControlFlow.prototype.commenceShutdown_ = function() { + if (!this.shutdownTask_) { // Go ahead and stop the event loop now. If we're in here, then there are // no more frames with tasks to execute. If we waited to cancel the event // loop in our timeout below, the event loop could trigger *before* the @@ -1659,24 +1966,36 @@ webdriver.promise.ControlFlow.prototype.commenceShutdown_ = function() { // If #execute is called before the timeout below fires, it will cancel // the timeout and restart the event loop. this.cancelEventLoop_(); + this.shutdownTask_ = new MicroTask(this.shutdown_, this); + } +}; - var self = this; - self.shutdownId_ = self.timer.setTimeout(function() { - self.shutdownId_ = null; - self.emit(webdriver.promise.ControlFlow.EventType.IDLE); - }, 0); + +/** @private */ +promise.ControlFlow.prototype.cancelHold_ = function() { + if (this.hold_) { + clearInterval(this.hold_); + this.hold_ = null; } }; +/** @private */ +promise.ControlFlow.prototype.shutdown_ = function() { + this.cancelHold_(); + this.shutdownTask_ = null; + this.emit(promise.ControlFlow.EventType.IDLE); +}; + + /** * Cancels the shutdown sequence if it is currently scheduled. * @private */ -webdriver.promise.ControlFlow.prototype.cancelShutdown_ = function() { - if (this.shutdownId_) { - this.timer.clearTimeout(this.shutdownId_); - this.shutdownId_ = null; +promise.ControlFlow.prototype.cancelShutdown_ = function() { + if (this.shutdownTask_) { + this.shutdownTask_.cancel(); + this.shutdownTask_ = null; } }; @@ -1690,364 +2009,462 @@ webdriver.promise.ControlFlow.prototype.cancelShutdown_ = function() { * abort; usually either an Error or string value. * @private */ -webdriver.promise.ControlFlow.prototype.abortNow_ = function(error) { +promise.ControlFlow.prototype.abortNow_ = function(error) { this.activeFrame_ = null; this.cancelShutdown_(); this.cancelEventLoop_(); + this.cancelHold_(); var listeners = this.listeners( - webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); if (!listeners.length) { - this.timer.setTimeout(function() { - throw error; - }, 0); + throwException(error); } else { - this.emit(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, - error); + this.emit(promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, error); } }; - -/** - * A single node in an {@link webdriver.promise.ControlFlow}'s task tree. - * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs - * to. - * @constructor - * @extends {webdriver.promise.Deferred} - * @private - */ -webdriver.promise.Node_ = function(flow) { - webdriver.promise.Deferred.call(this, null, flow); -}; -goog.inherits(webdriver.promise.Node_, webdriver.promise.Deferred); - - -/** - * This node's parent. - * @private {webdriver.promise.Node_} - */ -webdriver.promise.Node_.prototype.parent_ = null; - - -/** @return {webdriver.promise.Node_} This node's parent. */ -webdriver.promise.Node_.prototype.getParent = function() { - return this.parent_; -}; - - /** - * @param {webdriver.promise.Node_} parent This node's new parent. + * Wraps a function to execute as a cancellable micro task. + * @final */ -webdriver.promise.Node_.prototype.setParent = function(parent) { - this.parent_ = parent; -}; - +var MicroTask = goog.defineClass(null, { + /** + * @param {function(this: THIS)} fn The function to run as a micro task. + * @param {THIS=} opt_scope The scope to run the function in. + * @template THIS + */ + constructor: function(fn, opt_scope) { + /** @private {boolean} */ + this.cancelled_ = false; + asyncRun(function() { + if (!this.cancelled_) { + fn.call(opt_scope); + } + }, this); + }, -/** - * @return {!webdriver.promise.Node_} The root of this node's tree. - */ -webdriver.promise.Node_.prototype.getRoot = function() { - var root = this; - while (root.parent_) { - root = root.parent_; + /** + * Cancels the execution of this task. Note: this will not prevent the task + * timer from firing, just the invocation of the wrapped function. + */ + cancel: function() { + this.cancelled_ = true; } - return root; -}; - +}); /** * An execution frame within a {@link webdriver.promise.ControlFlow}. Each * frame represents the execution context for either a - * {@link webdriver.promise.Task_} or a callback on a - * {@link webdriver.promise.Deferred}. + * {@link webdriver.Task} or a callback on a + * {@link webdriver.promise.Promise}. * - *

        Each frame may contain sub-frames. If child N is a sub-frame, then the + * Each frame may contain sub-frames. If child N is a sub-frame, then the * items queued within it are given priority over child N+1. * - * @param {!webdriver.promise.ControlFlow} flow The flow this instance belongs - * to. - * @constructor - * @extends {webdriver.promise.Node_} + * @unrestricted + * @final * @private */ -webdriver.promise.Frame_ = function(flow) { - webdriver.promise.Node_.call(this, flow); - - var reject = goog.bind(this.reject, this); - var cancelRemainingTasks = goog.bind(this.cancelRemainingTasks, this); - - /** @override */ - this.reject = function(e) { - cancelRemainingTasks(new webdriver.promise.CanceledTaskError_(e)); - reject(e); - }; - +var Frame = goog.defineClass(EventEmitter, { /** - * @private {!Array.} + * @param {!promise.ControlFlow} flow The flow this instance belongs to. */ - this.children_ = []; -}; -goog.inherits(webdriver.promise.Frame_, webdriver.promise.Node_); + constructor: function(flow) { + EventEmitter.call(this); + goog.getUid(this); + /** @private {!promise.ControlFlow} */ + this.flow_ = flow; -/** - * The task currently being executed within this frame. - * @private {webdriver.promise.Task_} - */ -webdriver.promise.Frame_.prototype.pendingTask_ = null; + /** @private {Frame} */ + this.parent_ = null; + /** @private {Array} */ + this.children_ = null; -/** - * Whether this frame is active. A frame is considered active once one of its - * descendants has been removed for execution. - * - * Adding a sub-frame as a child to an active frame is an indication that - * a callback to a {@link webdriver.promise.Deferred} is being invoked and any - * tasks scheduled within it should have priority over previously scheduled - * tasks: - *

        - *   var flow = webdriver.promise.controlFlow();
        - *   flow.execute('start here', goog.nullFunction).then(function() {
        - *     flow.execute('this should execute 2nd', goog.nullFunction);
        - *   });
        - *   flow.execute('this should execute last', goog.nullFunction);
        - * 
        - * - * @private {boolean} - */ -webdriver.promise.Frame_.prototype.isActive_ = false; + /** @private {(Frame|Task)} */ + this.lastInsertedChild_ = null; + /** + * The task currently being executed within this frame. + * @private {Task} + */ + this.pendingTask_ = null; + + /** + * Whether this frame is currently locked. A locked frame represents an + * executed function that has scheduled all of its tasks. + * + * Once a frame becomes locked, any new frames which are added as children + * represent interrupts (such as a {@link webdriver.promise.Promise} + * callback) whose tasks must be given priority over those already scheduled + * within this frame. For example: + * + * var flow = promise.controlFlow(); + * flow.execute('start here', goog.nullFunction).then(function() { + * flow.execute('this should execute 2nd', goog.nullFunction); + * }); + * flow.execute('this should execute last', goog.nullFunction); + * + * @private {boolean} + */ + this.isLocked_ = false; + + /** + * Whether this frame's completion is blocked on the resolution of a promise + * returned by its main function. + * @private + */ + this.isBlocked_ = false; + + /** + * Whether this frame represents a pending callback attached to a + * {@link webdriver.promise.Promise}. + * @private {boolean} + */ + this.pendingCallback = false; + + /** + * Whether there are pending unhandled rejections detected within this frame. + * @private {boolean} + */ + this.pendingRejection = false; + + /** @private {promise.CancellationError} */ + this.cancellationError_ = null; + }, + + statics: { + /** @const */ + CLOSE_EVENT: 'close', + + /** @const */ + ERROR_EVENT: 'error', + + /** + * @param {!promise.CancellationError} error The cancellation error. + * @param {!(Frame|Task)} child The child to cancel. + * @private + */ + cancelChild_: function(error, child) { + if (child instanceof Frame) { + child.cancelRemainingTasks(error); + } else { + child.promise.callbacks_ = null; + child.cancel(error); + } + } + }, + + /** @return {Frame} This frame's parent, if any. */ + getParent: function() { + return this.parent_; + }, + + /** @param {Frame} parent This frame's new parent. */ + setParent: function(parent) { + this.parent_ = parent; + }, + + /** @return {!Frame} The root of this frame's tree. */ + getRoot: function() { + var root = this; + while (root.parent_) { + root = root.parent_; + } + return root; + }, -/** - * Whether this frame is currently locked. A locked frame represents a callback - * or task function which has run to completion and scheduled all of its tasks. - * - *

        Once a frame becomes {@link #isActive_ active}, any new frames which are - * added represent callbacks on a {@link webdriver.promise.Deferred}, whose - * tasks must be given priority over previously scheduled tasks. - * - * @private {boolean} - */ -webdriver.promise.Frame_.prototype.isLocked_ = false; + /** + * Aborts the execution of this frame, cancelling all outstanding tasks + * scheduled within this frame. + * + * @param {*} error The error that triggered this abortion. + */ + abort: function(error) { + this.cancellationError_ = promise.CancellationError.wrap( + error, 'Task discarded due to a previous task failure'); + this.cancelRemainingTasks(this.cancellationError_); + if (!this.pendingCallback) { + this.emit(Frame.ERROR_EVENT, error); + } + }, + /** + * Marks all of the tasks that are descendants of this frame in the execution + * tree as cancelled. This is necessary for callbacks scheduled asynchronous. + * For example: + * + * var someResult; + * promise.createFlow(function(flow) { + * someResult = flow.execute(function() {}); + * throw Error(); + * }).thenCatch(function(err) { + * console.log('flow failed: ' + err); + * someResult.then(function() { + * console.log('task succeeded!'); + * }, function(err) { + * console.log('task failed! ' + err); + * }); + * }); + * // flow failed: Error: boom + * // task failed! CancelledTaskError: Task discarded due to a previous + * // task failure: Error: boom + * + * @param {!promise.CancellationError} reason The cancellation reason. + */ + cancelRemainingTasks: function(reason) { + if (this.children_) { + this.children_.forEach(function(child) { + Frame.cancelChild_(reason, child); + }); + } + }, -/** - * A reference to the last node inserted in this frame. - * @private {webdriver.promise.Node_} - */ -webdriver.promise.Frame_.prototype.lastInsertedChild_ = null; + /** + * @return {Task} The task currently executing + * within this frame, if any. + */ + getPendingTask: function() { + return this.pendingTask_; + }, + /** + * @param {Task} task The task currently + * executing within this frame, if any. + */ + setPendingTask: function(task) { + this.pendingTask_ = task; + }, -/** - * Marks all of the tasks that are descendants of this frame in the execution - * tree as cancelled. This is necessary for callbacks scheduled asynchronous. - * For example: - * - * var someResult; - * webdriver.promise.createFlow(function(flow) { - * someResult = flow.execute(function() {}); - * throw Error(); - * }).addErrback(function(err) { - * console.log('flow failed: ' + err); - * someResult.then(function() { - * console.log('task succeeded!'); - * }, function(err) { - * console.log('task failed! ' + err); - * }); - * }); - * // flow failed: Error: boom - * // task failed! CanceledTaskError: Task discarded due to a previous - * // task failure: Error: boom - * - * @param {!webdriver.promise.CanceledTaskError_} error The cancellation - * error. - */ -webdriver.promise.Frame_.prototype.cancelRemainingTasks = function(error) { - goog.array.forEach(this.children_, function(child) { - if (child instanceof webdriver.promise.Frame_) { - child.cancelRemainingTasks(error); - } else { - // None of the previously registered listeners should be notified that - // the task is being canceled, however, we need at least one errback - // to prevent the cancellation from bubbling up. - child.removeAll(); - child.thenCatch(goog.nullFunction); - child.cancel(error); + /** + * @return {boolean} Whether this frame is empty (has no scheduled tasks or + * pending callback frames). + */ + isEmpty: function() { + return !this.children_ || !this.children_.length; + }, + + /** + * Adds a new node to this frame. + * @param {!(Frame|Task)} node The node to insert. + */ + addChild: function(node) { + if (this.cancellationError_) { + Frame.cancelChild_(this.cancellationError_, node); + return; // Child will never run, no point keeping a reference. } - }); -}; + if (!this.children_) { + this.children_ = []; + } -/** - * @return {webdriver.promise.Task_} The task currently executing - * within this frame, if any. - */ -webdriver.promise.Frame_.prototype.getPendingTask = function() { - return this.pendingTask_; -}; + node.setParent(this); + if (this.isLocked_ && node instanceof Frame) { + var index = 0; + if (this.lastInsertedChild_ instanceof Frame) { + index = this.children_.indexOf(this.lastInsertedChild_); + // If the last inserted child into a locked frame is a pending callback, + // it is an interrupt and the new interrupt must come after it. Otherwise, + // we have our first interrupt for this frame and it shoudl go before the + // last inserted child. + index += (this.lastInsertedChild_.pendingCallback) ? 1 : -1; + } + this.children_.splice(Math.max(index, 0), 0, node); + this.lastInsertedChild_ = node; + return; + } + this.lastInsertedChild_ = node; + this.children_.push(node); + }, -/** - * @param {webdriver.promise.Task_} task The task currently - * executing within this frame, if any. - */ -webdriver.promise.Frame_.prototype.setPendingTask = function(task) { - this.pendingTask_ = task; -}; + /** + * @return {(Frame|Task)} This frame's fist child. + */ + getFirstChild: function() { + this.isLocked_ = true; + return this.children_ && this.children_[0]; + }, + /** + * Removes a child from this frame. + * @param {!(Frame|Task)} child The child to remove. + */ + removeChild: function(child) { + goog.asserts.assert(child.parent_ === this, 'not a child of this frame'); + goog.asserts.assert(this.children_ !== null, 'frame has no children!'); + var index = this.children_.indexOf(child); + child.setParent(null); + this.children_.splice(index, 1); + if (this.lastInsertedChild_ === child) { + this.lastInsertedChild_ = this.children_[index - 1] || null; + } + if (!this.children_.length) { + this.children_ = null; + } + }, -/** Locks this frame. */ -webdriver.promise.Frame_.prototype.lockFrame = function() { - this.isLocked_ = true; -}; + /** @override */ + toString: function() { + return 'Frame::' + goog.getUid(this); + } +}); /** - * Adds a new node to this frame. - * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} node - * The node to insert. + * A task to be executed by a {@link webdriver.promise.ControlFlow}. + * + * @unrestricted + * @final */ -webdriver.promise.Frame_.prototype.addChild = function(node) { - if (this.lastInsertedChild_ && - this.lastInsertedChild_ instanceof webdriver.promise.Frame_ && - !this.lastInsertedChild_.isLocked_) { - this.lastInsertedChild_.addChild(node); - return; - } - - node.setParent(this); +var Task = goog.defineClass(promise.Deferred, { + /** + * @param {!promise.ControlFlow} flow The flow this instances belongs + * to. + * @param {function(): (T|!promise.Promise)} fn The function to + * call when the task executes. If it returns a + * {@link webdriver.promise.Promise}, the flow will wait for it to be + * resolved before starting the next task. + * @param {string} description A description of the task for debugging. + * @constructor + * @extends {promise.Deferred} + * @template T + */ + constructor: function(flow, fn, description) { + Task.base(this, 'constructor', flow); + goog.getUid(this); - if (this.isActive_ && node instanceof webdriver.promise.Frame_) { - var index = 0; - if (this.lastInsertedChild_ instanceof - webdriver.promise.Frame_) { - index = goog.array.indexOf(this.children_, this.lastInsertedChild_) + 1; - } - goog.array.insertAt(this.children_, node, index); - this.lastInsertedChild_ = node; - return; - } + /** + * @type {function(): (T|!promise.Promise)} + */ + this.execute = fn; - this.lastInsertedChild_ = node; - this.children_.push(node); -}; + /** @private {string} */ + this.description_ = description; + /** @private {Frame} */ + this.parent_ = null; -/** - * @return {(webdriver.promise.Frame_|webdriver.promise.Task_)} This frame's - * fist child. - */ -webdriver.promise.Frame_.prototype.getFirstChild = function() { - this.isActive_ = true; - this.lastInsertedChild_ = null; - return this.children_[0]; -}; + /** @private {Frame} */ + this.frame_ = null; + }, + /** + * @return {Frame} frame The frame used to run this task's + * {@link #execute} method. + */ + getFrame: function() { + return this.frame_; + }, -/** - * Removes a child from this frame. - * @param {!(webdriver.promise.Frame_|webdriver.promise.Task_)} child - * The child to remove. - */ -webdriver.promise.Frame_.prototype.removeChild = function(child) { - var index = goog.array.indexOf(this.children_, child); - child.setParent(null); - goog.array.removeAt(this.children_, index); - if (this.lastInsertedChild_ === child) { - this.lastInsertedChild_ = null; - } -}; + /** + * @param {Frame} frame The frame used to run this task's + * {@link #execute} method. + */ + setFrame: function(frame) { + this.frame_ = frame; + }, + /** + * @param {Frame} frame The frame this task is scheduled in. + */ + setParent: function(frame) { + goog.asserts.assert(goog.isNull(this.parent_) || goog.isNull(frame), + 'parent already set'); + this.parent_ = frame; + }, -/** @override */ -webdriver.promise.Frame_.prototype.toString = function() { - return '[' + goog.array.map(this.children_, function(child) { - return child.toString(); - }).join(', ') + ']'; -}; + /** @return {string} This task's description. */ + getDescription: function() { + return this.description_; + }, + /** @override */ + toString: function() { + return 'Task::' + goog.getUid(this) + '<' + this.description_ + '>'; + } +}); /** - * A task to be executed by a {@link webdriver.promise.ControlFlow}. + * Manages a callback attached to a {@link webdriver.promise.Promise}. When the + * promise is resolved, this callback will invoke the appropriate callback + * function based on the promise's resolved value. * - * @param {!webdriver.promise.ControlFlow} flow The flow this instances belongs - * to. - * @param {!Function} fn The function to call when the task executes. If it - * returns a {@code webdriver.promise.Promise}, the flow will wait - * for it to be resolved before starting the next task. - * @param {string} description A description of the task for debugging. - * @param {!webdriver.stacktrace.Snapshot} snapshot A snapshot of the stack - * when this task was scheduled. - * @constructor - * @extends {webdriver.promise.Node_} - * @private + * @unrestricted + * @final */ -webdriver.promise.Task_ = function(flow, fn, description, snapshot) { - webdriver.promise.Node_.call(this, flow); - +var Callback = goog.defineClass(promise.Deferred, { /** - * Executes this task. - * @type {!Function} + * @param {!promise.Promise} parent The promise this callback is attached to. + * @param {(function(T): (IThenable|R)|null|undefined)} callback + * The fulfillment callback. + * @param {(function(*): (IThenable|R)|null|undefined)} errback + * The rejection callback. + * @param {string} name The callback name. + * @param {!Function} fn The function to use as the top of the stack when + * recording the callback's creation point. + * @extends {promise.Deferred} + * @template T, R */ - this.execute = fn; + constructor: function(parent, callback, errback, name, fn) { + Callback.base(this, 'constructor', parent.flow_); - /** @private {string} */ - this.description_ = description; + /** @private {(function(T): (IThenable|R)|null|undefined)} */ + this.callback_ = callback; - /** @private {!webdriver.stacktrace.Snapshot} */ - this.snapshot_ = snapshot; -}; -goog.inherits(webdriver.promise.Task_, webdriver.promise.Node_); + /** @private {(function(*): (IThenable|R)|null|undefined)} */ + this.errback_ = errback; + /** @private {!Frame} */ + this.frame_ = new Frame(parent.flow_); + this.frame_.pendingCallback = true; -/** @return {string} This task's description. */ -webdriver.promise.Task_.prototype.getDescription = function() { - return this.description_; -}; + this.promise.parent_ = parent; + if (promise.LONG_STACK_TRACES) { + this.promise.stack_ = promise.captureStackTrace('Promise', name, fn); + } + }, + /** + * Called by the parent promise when it has been resolved. + * @param {!PromiseState} state The parent's new state. + * @param {*} value The parent's new value. + */ + notify: function(state, value) { + var callback = this.callback_; + var fallback = this.fulfill; + if (state === PromiseState.REJECTED) { + callback = this.errback_; + fallback = this.reject; + } -/** @override */ -webdriver.promise.Task_.prototype.toString = function() { - var stack = this.snapshot_.getStacktrace(); - var ret = this.description_; - if (stack.length) { - if (this.description_) { - ret += '\n'; + this.frame_.pendingCallback = false; + if (goog.isFunction(callback)) { + this.frame_.flow_.runInFrame_( + this.frame_, + goog.bind(callback, undefined, value), + this.fulfill, this.reject); + } else { + if (this.frame_.getParent()) { + this.frame_.getParent().removeChild(this.frame_); + } + fallback(value); } - ret += stack.join('\n'); } - return ret; -}; - - - -/** - * Special error used to signal when a task is canceled because a previous - * task in the same frame failed. - * @param {*} err The error that caused the task cancellation. - * @constructor - * @extends {goog.debug.Error} - * @private - */ -webdriver.promise.CanceledTaskError_ = function(err) { - goog.base(this, 'Task discarded due to a previous task failure: ' + err); -}; -goog.inherits(webdriver.promise.CanceledTaskError_, goog.debug.Error); - - -/** @override */ -webdriver.promise.CanceledTaskError_.prototype.name = 'CanceledTaskError'; +}); /** * The default flow to use if no others are active. - * @private {!webdriver.promise.ControlFlow} + * @type {!promise.ControlFlow} */ -webdriver.promise.defaultFlow_ = new webdriver.promise.ControlFlow(); +var defaultFlow = new promise.ControlFlow(); /** @@ -2055,46 +2472,30 @@ webdriver.promise.defaultFlow_ = new webdriver.promise.ControlFlow(); * commands. When there are multiple flows on the stack, the flow at index N * represents a callback triggered within a task owned by the flow at index * N-1. - * @private {!Array.} + * @type {!Array} */ -webdriver.promise.activeFlows_ = []; +var activeFlows = []; /** * Changes the default flow to use when no others are active. - * @param {!webdriver.promise.ControlFlow} flow The new default flow. + * @param {!promise.ControlFlow} flow The new default flow. * @throws {Error} If the default flow is not currently active. */ -webdriver.promise.setDefaultFlow = function(flow) { - if (webdriver.promise.activeFlows_.length) { +promise.setDefaultFlow = function(flow) { + if (activeFlows.length) { throw Error('You may only change the default flow while it is active'); } - webdriver.promise.defaultFlow_ = flow; -}; - - -/** - * @return {!webdriver.promise.ControlFlow} The currently active control flow. - */ -webdriver.promise.controlFlow = function() { - return /** @type {!webdriver.promise.ControlFlow} */ ( - goog.array.peek(webdriver.promise.activeFlows_) || - webdriver.promise.defaultFlow_); + defaultFlow = flow; }; /** - * @param {!webdriver.promise.ControlFlow} flow The new flow. - * @private + * @return {!promise.ControlFlow} The currently active control flow. */ -webdriver.promise.pushFlow_ = function(flow) { - webdriver.promise.activeFlows_.push(flow); -}; - - -/** @private */ -webdriver.promise.popFlow_ = function() { - webdriver.promise.activeFlows_.pop(); +promise.controlFlow = function() { + return /** @type {!promise.ControlFlow} */ ( + Arrays.peek(activeFlows) || defaultFlow); }; @@ -2102,15 +2503,107 @@ webdriver.promise.popFlow_ = function() { * Creates a new control flow. The provided callback will be invoked as the * first task within the new flow, with the flow as its sole argument. Returns * a promise that resolves to the callback result. - * @param {function(!webdriver.promise.ControlFlow)} callback The entry point + * @param {function(!promise.ControlFlow)} callback The entry point * to the newly created flow. - * @return {!webdriver.promise.Promise} A promise that resolves to the callback + * @return {!promise.Promise} A promise that resolves to the callback * result. */ -webdriver.promise.createFlow = function(callback) { - var flow = new webdriver.promise.ControlFlow( - webdriver.promise.defaultFlow_.timer); +promise.createFlow = function(callback) { + var flow = new promise.ControlFlow; return flow.execute(function() { return callback(flow); }); }; + + +/** + * Tests is a function is a generator. + * @param {!Function} fn The function to test. + * @return {boolean} Whether the function is a generator. + */ +promise.isGenerator = function(fn) { + return fn.constructor.name === 'GeneratorFunction'; +}; + + +/** + * Consumes a {@code GeneratorFunction}. Each time the generator yields a + * promise, this function will wait for it to be fulfilled before feeding the + * fulfilled value back into {@code next}. Likewise, if a yielded promise is + * rejected, the rejection error will be passed to {@code throw}. + * + * __Example 1:__ the Fibonacci Sequence. + * + * promise.consume(function* fibonacci() { + * var n1 = 1, n2 = 1; + * for (var i = 0; i < 4; ++i) { + * var tmp = yield n1 + n2; + * n1 = n2; + * n2 = tmp; + * } + * return n1 + n2; + * }).then(function(result) { + * console.log(result); // 13 + * }); + * + * __Example 2:__ a generator that throws. + * + * promise.consume(function* () { + * yield promise.delayed(250).then(function() { + * throw Error('boom'); + * }); + * }).thenCatch(function(e) { + * console.log(e.toString()); // Error: boom + * }); + * + * @param {!Function} generatorFn The generator function to execute. + * @param {Object=} opt_self The object to use as "this" when invoking the + * initial generator. + * @param {...*} var_args Any arguments to pass to the initial generator. + * @return {!promise.Promise} A promise that will resolve to the + * generator's final result. + * @throws {TypeError} If the given function is not a generator. + */ +promise.consume = function(generatorFn, opt_self, var_args) { + if (!promise.isGenerator(generatorFn)) { + throw new TypeError('Input is not a GeneratorFunction: ' + + generatorFn.constructor.name); + } + + var deferred = promise.defer(); + var generator = generatorFn.apply(opt_self, Arrays.slice(arguments, 2)); + callNext(); + return deferred.promise; + + /** @param {*=} opt_value . */ + function callNext(opt_value) { + pump(generator.next, opt_value); + } + + /** @param {*=} opt_error . */ + function callThrow(opt_error) { + // Dictionary lookup required because Closure compiler's built-in + // externs does not include GeneratorFunction.prototype.throw. + pump(generator['throw'], opt_error); + } + + function pump(fn, opt_arg) { + if (!deferred.isPending()) { + return; // Defererd was cancelled; silently abort. + } + + try { + var result = fn.call(generator, opt_arg); + } catch (ex) { + deferred.reject(ex); + return; + } + + if (result.done) { + deferred.fulfill(result.value); + return; + } + + promise.asap(result.value, callNext, callThrow); + } +}; diff --git a/lib/webdriver/serializable.js b/lib/webdriver/serializable.js new file mode 100644 index 0000000..16db027 --- /dev/null +++ b/lib/webdriver/serializable.js @@ -0,0 +1,41 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.Serializable'); + + + +/** + * Defines an object that can be asynchronously serialized to its WebDriver + * wire representation. + * + * @constructor + * @template T + */ +webdriver.Serializable = function() {}; + + +/** + * Returns either this instance's serialized represention, if immediately + * available, or a promise for its serialized representation. This function is + * conceptually equivalent to objects that have a {@code toJSON()} property, + * except the serialize() result may be a promise or an object containing a + * promise (which are not directly JSON friendly). + * + * @return {!(T|IThenable.)} This instance's serialized wire format. + */ +webdriver.Serializable.prototype.serialize = goog.abstractMethod; diff --git a/lib/webdriver/session.js b/lib/webdriver/session.js index 03d3219..1550226 100644 --- a/lib/webdriver/session.js +++ b/lib/webdriver/session.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. goog.provide('webdriver.Session'); diff --git a/lib/webdriver/stacktrace.js b/lib/webdriver/stacktrace.js index a0cec8f..3b34435 100644 --- a/lib/webdriver/stacktrace.js +++ b/lib/webdriver/stacktrace.js @@ -1,18 +1,19 @@ -// Copyright 2009 The Closure Library Authors. All Rights Reserved. -// Copyright 2012 Selenium comitters -// Copyright 2012 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Tools for parsing and pretty printing error stack traces. This @@ -60,11 +61,10 @@ webdriver.stacktrace.Snapshot = function(opt_slice) { } /** - * The error's stacktrace. This must be accessed immediately to ensure Opera - * computes the context correctly. + * The error's stacktrace. * @private {string} */ - this.stack_ = webdriver.stacktrace.getStack_(error); + this.stack_ = webdriver.stacktrace.getStack(error); }; @@ -353,7 +353,9 @@ webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))'; * @private {!RegExp} * @const */ -webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^ at' + +webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^\\s+at' + + // Prevent intersections with IE10 stack frame regex. + '(?! (?:Anonymous function|Global code|eval code) )' + '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' + webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$'); @@ -390,49 +392,18 @@ webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' + '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$'); -/** - * RegExp pattern for an anonymous function call in an Opera stack frame. - * Creates 2 (optional) submatches: the context object and function name. - * @private {string} - * @const - */ -webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ = - ''; - - -/** - * RegExp pattern for a function call in an Opera stack frame. - * Creates 3 (optional) submatches: the function name (if not anonymous), - * the aliased context object and the function name (if anonymous). - * @private {string} - * @const - */ -webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ = - '(?:(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')|' + - webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ + - ')(?:\\(.*\\)))?@'; - - -/** - * Regular expression for parsing on stack frame in Opera 11.68+ - * @private {!RegExp} - * @const - */ -webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp('^' + - webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ + - webdriver.stacktrace.URL_PATTERN_ + '?$'); - - /** * RegExp pattern for function call in a Chakra (IE) stack trace. This - * expression allows for identifiers like 'Anonymous function', 'eval code', - * and 'Global code'. + * expression creates 2 submatches on the (optional) context and function name, + * matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function', + * 'eval code', and 'Global code'. * @private {string} * @const */ -webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ = '(' + - webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)'; +webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ = + '(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + + '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' + + '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)'; /** @@ -511,14 +482,9 @@ webdriver.stacktrace.parseStackFrame_ = function(frameStr) { return new webdriver.stacktrace.Frame('', m[1], '', m[2]); } - m = frameStr.match(webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_); - if (m) { - return new webdriver.stacktrace.Frame(m[2], m[1] || m[3], '', m[4]); - } - m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_); if (m) { - return new webdriver.stacktrace.Frame('', m[1], '', m[2]); + return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]); } if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ || @@ -562,11 +528,13 @@ webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) { * V8 prepends the string representation of an error to its stack trace. * This function trims the string so that the stack trace can be parsed * consistently with the other JS engines. - * @param {!(Error|goog.testing.JsUnitException)} error The error. + * @param {(Error|goog.testing.JsUnitException)} error The error. * @return {string} The stack trace string. - * @private */ -webdriver.stacktrace.getStack_ = function(error) { +webdriver.stacktrace.getStack = function(error) { + if (!error) { + return ''; + } var stack = error.stack || error.stackTrace || ''; var errorStr = error + '\n'; if (goog.string.startsWith(stack, errorStr)) { @@ -582,9 +550,21 @@ webdriver.stacktrace.getStack_ = function(error) { * @return {!(Error|goog.testing.JsUnitException)} The formatted error. */ webdriver.stacktrace.format = function(error) { - var stack = webdriver.stacktrace.getStack_(error); + var stack = webdriver.stacktrace.getStack(error); var frames = webdriver.stacktrace.parse_(stack); + // If the original stack is in an unexpected format, our formatted stack + // trace will be a bunch of " at " lines. If this is the case, + // just return the error unmodified to avoid losing information. This is + // necessary since the user may have defined a custom stack formatter in + // V8 via Error.prepareStackTrace. See issue 7994. + var isAnonymousFrame = function(frame) { + return frame.toString() === ' at '; + }; + if (frames.length && goog.array.every(frames, isAnonymousFrame)) { + return error; + } + // Older versions of IE simply return [object Error] for toString(), so // only use that as a last resort. var errorStr = ''; @@ -622,12 +602,7 @@ webdriver.stacktrace.parse_ = function(stack) { // The first two frames will be: // webdriver.stacktrace.Snapshot() // webdriver.stacktrace.get() - // In the case of Opera, sometimes an extra frame is injected in the next - // frame with a reported line number of zero. The next line detects that - // case and skips that frame. - if (!(goog.userAgent.OPERA && i == 2 && frame.getLine() == 0)) { - frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_); - } + frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_); } return frames; }; diff --git a/lib/webdriver/test/builder_test.js b/lib/webdriver/test/builder_test.js new file mode 100644 index 0000000..be88026 --- /dev/null +++ b/lib/webdriver/test/builder_test.js @@ -0,0 +1,53 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.Builder'); + + + +function testInitializeSessionIdFromQueryString_notSet() { + var builder = new webdriver.Builder({ + location: '/somelocation' + }); + assertUndefined(builder.getSession()); +} + + +function testInitializeSessionIdfromQueryString_set() { + var builder = new webdriver.Builder({ + location: '/somelocation?wdsid=foo' + }); + assertEquals('foo', builder.getSession()); +} + + +function testInitializeServerUrlFromQueryString_notSet() { + var builder = new webdriver.Builder({ + location: '/somelocation' + }); + assertEquals(webdriver.Builder.DEFAULT_SERVER_URL, + builder.getServerUrl()); +} + + +function testInitializeServerUrlFromQueryString_set() { + var builder = new webdriver.Builder({ + location: '/somelocation?wdurl=http://www.example.com' + }); + assertEquals('http://www.example.com', builder.getServerUrl()); +} diff --git a/lib/webdriver/test/capabilities_test.js b/lib/webdriver/test/capabilities_test.js new file mode 100644 index 0000000..2278f1f --- /dev/null +++ b/lib/webdriver/test/capabilities_test.js @@ -0,0 +1,72 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.Capabilities'); + +function testSettingAndUnsettingACapability() { + var caps = new webdriver.Capabilities(); + assertNull(caps.get('foo')); + + caps.set('foo', 'bar'); + assertEquals('bar', caps.get('foo')); + + caps.set('foo', null); + assertNull(caps.get('foo')); +} + + +function testCheckingIfACapabilityIsSet() { + var caps = new webdriver.Capabilities(); + assertFalse(caps.has('foo')); + assertNull(caps.get('foo')); + + caps.set('foo', 'bar'); + assertTrue(caps.has('foo')); + + caps.set('foo', true); + assertTrue(caps.has('foo')); + + caps.set('foo', false); + assertFalse(caps.has('foo')); + assertFalse(caps.get('foo')); + + caps.set('foo', null); + assertFalse(caps.has('foo')); + assertNull(caps.get('foo')); +} + + +function testMergingCapabilities() { + var caps1 = new webdriver.Capabilities(). + set('foo', 'bar'). + set('color', 'red'); + + var caps2 = new webdriver.Capabilities(). + set('color', 'green'); + + assertEquals('bar', caps1.get('foo')); + assertEquals('red', caps1.get('color')); + assertEquals('green', caps2.get('color')); + assertNull(caps2.get('foo')); + + caps2.merge(caps1); + assertEquals('bar', caps1.get('foo')); + assertEquals('red', caps1.get('color')); + assertEquals('bar', caps2.get('foo')); + assertEquals('red', caps2.get('color')); +} diff --git a/lib/webdriver/test/events_test.js b/lib/webdriver/test/events_test.js new file mode 100644 index 0000000..41a86de --- /dev/null +++ b/lib/webdriver/test/events_test.js @@ -0,0 +1,203 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.EventEmitter'); + +function testEmittingWhenNothingIsRegistered() { + var emitter = new webdriver.EventEmitter(); + emitter.emit('foo'); + // Ok if no errors are thrown. +} + +function testPassesArgsToListenersOnEmit() { + var emitter = new webdriver.EventEmitter(); + var now = goog.now(); + + var messages = []; + emitter.addListener('foo', function(arg) { messages.push(arg); }); + + emitter.emit('foo', now); + assertEquals(1, messages.length); + assertEquals(now, messages[0]); + + emitter.emit('foo', now + 15); + assertEquals(2, messages.length); + assertEquals(now, messages[0]); + assertEquals(now + 15, messages[1]); +} + +function testAddingListeners() { + var emitter = new webdriver.EventEmitter(); + var count = 0; + emitter.addListener('foo', function() { count++; }); + emitter.addListener('foo', function() { count++; }); + emitter.addListener('foo', function() { count++; }); + emitter.emit('foo'); + assertEquals(3, count); +} + +function testAddingOneShotListeners() { + var emitter = new webdriver.EventEmitter(); + var count = 0; + emitter.once('foo', function() { count++; }); + emitter.once('foo', function() { count++; }); + emitter.once('foo', function() { count++; }); + + emitter.emit('foo'); + assertEquals(3, count); + + emitter.emit('foo'); + assertEquals(3, count); +} + +function testCanOnlyAddAListenerConfigOnce() { + var emitter = new webdriver.EventEmitter(); + var count = 0; + function onFoo() { count++; } + + emitter.on('foo', onFoo); + emitter.on('foo', onFoo); + + emitter.emit('foo'); + assertEquals(1, count); + + emitter.emit('foo'); + assertEquals(2, count); +} + +function testAddingListenersWithCustomScope() { + var obj = { + count: 0, + inc: function() { + this.count++; + } + }; + var emitter = new webdriver.EventEmitter(); + emitter.addListener('foo', obj.inc, obj); + + emitter.emit('foo'); + assertEquals(1, obj.count); + + emitter.emit('foo'); + assertEquals(2, obj.count); + + emitter.once('bar', obj.inc, obj); + emitter.emit('bar'); + assertEquals(3, obj.count); + + emitter.emit('bar'); + assertEquals(3, obj.count); +} + +function testRemovingListeners() { + var emitter = new webdriver.EventEmitter(); + var count = 0; + emitter.addListener('foo', function() { count++; }); + emitter.addListener('foo', function() { count++; }); + + var toRemove = function() { count++; }; + emitter.addListener('foo', toRemove); + + emitter.emit('foo'); + assertEquals(3, count); + + emitter.removeListener('foo', toRemove); + emitter.emit('foo'); + assertEquals(5, count); +} + +function testRemovingAllListeners() { + var emitter = new webdriver.EventEmitter(); + var count = 0; + emitter.addListener('foo', function() { count++; }); + emitter.addListener('foo', function() { count++; }); + emitter.addListener('foo', function() { count++; }); + + emitter.emit('foo'); + assertEquals(3, count); + + emitter.removeAllListeners('foo'); + + emitter.emit('foo'); + assertEquals(3, count); +} + +function testRemovingAbsolutelyAllListeners() { + var emitter = new webdriver.EventEmitter(); + var messages = []; + emitter.addListener('foo', function() { messages.push('foo'); }); + emitter.addListener('bar', function() { messages.push('bar'); }); + + emitter.emit('foo'); + assertArrayEquals(['foo'], messages); + + emitter.emit('bar'); + assertArrayEquals(['foo', 'bar'], messages); + + emitter.emit('bar'); + emitter.emit('foo'); + assertArrayEquals(['foo', 'bar', 'bar', 'foo'], messages); + + emitter.removeAllListeners(); + assertArrayEquals(['foo', 'bar', 'bar', 'foo'], messages); +} + +function testListenerThatRemovesItself() { + var emitter = new webdriver.EventEmitter(); + emitter.on('foo', listener); + emitter.emit('foo'); + + function listener() { + emitter.removeListener('foo', listener); + } +} + +function testListenerThatRemovesItself_whenThereAreMultipleListeners() { + var emitter = new webdriver.EventEmitter(); + var messages = []; + emitter.on('foo', one); + emitter.on('foo', two); + emitter.emit('foo'); + assertArrayEquals(['one', 'two'], messages); + + function one() { + messages.push('one'); + emitter.removeListener('foo', one); + } + + function two() { + messages.push('two'); + } +} + +function testListenerAddsAnotherForTheSameEventType() { + var emitter = new webdriver.EventEmitter(); + var messages = []; + emitter.on('foo', one); + emitter.emit('foo'); + assertArrayEquals(['one', 'two'], messages); + + function one() { + messages.push('one'); + emitter.on('foo', two); + } + + function two() { + messages.push('two'); + } +} diff --git a/lib/webdriver/test/http/corsclient_test.js b/lib/webdriver/test/http/corsclient_test.js new file mode 100644 index 0000000..6289b24 --- /dev/null +++ b/lib/webdriver/test/http/corsclient_test.js @@ -0,0 +1,151 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.http.CorsClient'); +goog.require('webdriver.http.Request'); +goog.require('webdriver.test.testutil'); + +// Alias for readability. +var callbackHelper = webdriver.test.testutil.callbackHelper; + +function FakeXhr() {} +FakeXhr.prototype.status = 200; +FakeXhr.prototype.responseText = ''; +FakeXhr.prototype.withCredentials = false; +FakeXhr.prototype.open = function() {}; +FakeXhr.prototype.send = function() {}; +FakeXhr.prototype.setRequestHeader = function() {}; + +var URL = 'http://localhost:4444/wd/hub'; +var REQUEST = new webdriver.http.Request('GET', '/foo'); + +var control = new goog.testing.MockControl(); +var stubs = new goog.testing.PropertyReplacer(); +var mockClient, mockXhr; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + +function setUp() { + mockClient = control.createStrictMock(webdriver.http.Client); + mockXhr = control.createStrictMock(FakeXhr); + mockXhr.status = 200; + mockXhr.responseText = ''; + mockXhr.withCredentials = false; + setXhr(mockXhr); +} + +function tearDown() { + control.$tearDown(); + stubs.reset(); +} + +function setXhr(value) { + stubs.set(goog.global, 'XMLHttpRequest', function() { + return value; + }); + setXdr(); +} + +function setXdr(opt_value) { + stubs.set(goog.global, 'XDomainRequest', opt_value); +} + +function expectRequest(mockXhr) { + mockXhr.open('POST', URL + '/xdrpc', true); + return mockXhr.send(JSON.stringify({ + 'method': REQUEST.method, + 'path': REQUEST.path, + 'data': REQUEST.data + })); +} + +function testDetectsWhenCorsIsAvailable() { + setXhr(undefined); + assertFalse(webdriver.http.CorsClient.isAvailable()); + setXhr(); + assertFalse(webdriver.http.CorsClient.isAvailable()); + setXhr({withCredentials: null}); + assertFalse(webdriver.http.CorsClient.isAvailable()); + setXhr({withCredentials: true}); + assertTrue(webdriver.http.CorsClient.isAvailable()); + setXhr(); + setXdr(goog.nullFunction); + assertTrue(webdriver.http.CorsClient.isAvailable()); +} + +function testCorsClient_whenUnableToSendARequest() { + expectRequest(mockXhr).$does(function() { + mockXhr.onerror(); + }); + control.$replayAll(); + + var callback; + new webdriver.http.CorsClient(URL).send(REQUEST, + callback = callbackHelper(function(error) { + assertNotNullNorUndefined(error); + assertEquals(1, arguments.length); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testCorsClient_handlesResponsesWithNoHeaders() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = ''; + mockXhr.onload(); + }); + control.$replayAll(); + + var callback; + new webdriver.http.CorsClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({}, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testCorsClient_stripsNullCharactersFromResponseBody() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = '\x00foo\x00\x00bar\x00'; + mockXhr.onload(); + }); + control.$replayAll(); + + var callback; + new webdriver.http.CorsClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + assertEquals(200, response.status); + assertEquals('foobar', response.body); + webdriver.test.testutil.assertObjectEquals({}, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} diff --git a/lib/webdriver/test/http/http_test.js b/lib/webdriver/test/http/http_test.js new file mode 100644 index 0000000..1b37ca3 --- /dev/null +++ b/lib/webdriver/test/http/http_test.js @@ -0,0 +1,428 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('bot.ErrorCode'); +goog.require('goog.Uri'); +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.Command'); +goog.require('webdriver.http.Client'); +goog.require('webdriver.http.Executor'); +goog.require('webdriver.promise'); +goog.require('webdriver.test.testutil'); + +// Alias for readability. +var callbackHelper = webdriver.test.testutil.callbackHelper; + +var control = new goog.testing.MockControl(); +var mockClient, executor, onCallback, onErrback; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + +function setUp() { + mockClient = control.createStrictMock(webdriver.http.Client); + + executor = new webdriver.http.Executor(mockClient); +} + +function tearDown() { + control.$tearDown(); +} + +function assertSuccess() { + onErrback.assertNotCalled('Did not expect errback'); + onCallback.assertCalled('Expected callback'); +} + +function assertFailure() { + onCallback.assertNotCalled('Did not expect callback'); + onErrback.assertCalled('Expected errback'); +} + +function headersToString(headers) { + var str = []; + for (var key in headers) { + str.push(key + ': ' + headers[key]); + } + return str.join('\n'); +} + +function expectRequest(method, path, data, headers) { + var description = method + ' ' + path + '\n' + headersToString(headers) + + '\n' + JSON.stringify(data); + + return mockClient.send(new goog.testing.mockmatchers.ArgumentMatcher( + function(request) { + assertEquals('wrong method', method, request.method); + assertEquals('wrong path', path + '', request.path); + webdriver.test.testutil.assertObjectEquals(data, request.data); + assertNull( + 'Wrong headers for request:\n' + description + + '\n Actual headers were:\n' + headersToString(request.headers), + goog.testing.asserts.findDifferences(headers, request.headers)); + return true; + }, description), + goog.testing.mockmatchers.isFunction); +} + +function response(status, headers, body) { + return new webdriver.http.Response(status, headers, body); +} + +function respondsWith(error, opt_response) { + return function(request, callback) { + callback(error, opt_response); + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Tests +// +/////////////////////////////////////////////////////////////////////////////// + +function testBuildPath() { + var parameters = {'sessionId':'foo', 'url':'http://www.google.com'}; + var finalPath = webdriver.http.Executor.buildPath_( + '/session/:sessionId/url', parameters); + assertEquals('/session/foo/url', finalPath); + webdriver.test.testutil.assertObjectEquals({'url':'http://www.google.com'}, + parameters); +} + +function testBuildPath_withWebElement() { + var parameters = {'sessionId':'foo', 'id': {}}; + parameters['id']['ELEMENT'] = 'bar'; + + var finalPath = webdriver.http.Executor.buildPath_( + '/session/:sessionId/element/:id/click', parameters); + assertEquals('/session/foo/element/bar/click', finalPath); + webdriver.test.testutil.assertObjectEquals({}, parameters); +} + +function testBuildPath_throwsIfMissingParameter() { + assertThrows(goog.partial(webdriver.http.Executor.buildPath_, + '/session/:sessionId', {})); + + assertThrows(goog.partial(webdriver.http.Executor.buildPath_, + '/session/:sessionId/element/:id', {'sessionId': 'foo'})); +} + +function testBuildPath_doesNotMatchOnSegmentsThatDoNotStartWithColon() { + assertEquals('/session/foo:bar/baz', + webdriver.http.Executor.buildPath_('/session/foo:bar/baz', {})); +} + +function testExecute_rejectsUnrecognisedCommands() { + assertThrows(goog.bind(executor.execute, executor, + new webdriver.Command('fake-command-name'), goog.nullFunction)); +} + +/** + * @param {!webdriver.Command} command The command to send. + * @param {!Function=} opt_onSuccess The function to check the response with. + */ +function assertSendsSuccessfully(command, opt_onSuccess) { + var callback; + executor.execute(command, callback = callbackHelper(function(e, response) { + assertNull(e); + assertNotNullNorUndefined(response); + if (opt_onSuccess) { + opt_onSuccess(response); + } + })); + callback.assertCalled(); + control.$verifyAll(); +} + +/** + * @param {!webdriver.Command} command The command to send. + * @param {!Function=} opt_onError The function to check the error with. + */ +function assertFailsToSend(command, opt_onError) { + var callback; + executor.execute(command, callback = callbackHelper(function(e, response) { + assertNotNullNorUndefined(e); + assertUndefined(response); + if (opt_onError) { + opt_onError(e); + } + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testExecute_clientFailsToSendRequest() { + var error = new Error('boom'); + expectRequest('POST', '/session', {}, { + 'Accept': 'application/json; charset=utf-8' + }). + $does(respondsWith(error)); + control.$replayAll(); + + assertFailsToSend(new webdriver.Command(webdriver.CommandName.NEW_SESSION), + function(e) { + assertEquals(error, e); + }); +} + +function testExecute_commandWithNoUrlParameters() { + expectRequest('POST', '/session', {}, { + 'Accept': 'application/json; charset=utf-8' + }). + $does(respondsWith(null, response(200, {}, ''))); + control.$replayAll(); + + assertSendsSuccessfully( + new webdriver.Command(webdriver.CommandName.NEW_SESSION)); +} + +function testExecute_rejectsCommandsMissingUrlParameters() { + var command = + new webdriver.Command(webdriver.CommandName.FIND_CHILD_ELEMENT). + setParameter('sessionId', 's123'). + // Let this be missing: setParameter('id', {'ELEMENT': 'e456'}). + setParameter('using', 'id'). + setParameter('value', 'foo'); + + control.$replayAll(); + assertThrows(goog.bind(executor.execute, executor, command)); + control.$verifyAll(); +} + +function testExecute_replacesUrlParametersWithCommandParameters() { + var command = + new webdriver.Command(webdriver.CommandName.GET). + setParameter('sessionId', 's123'). + setParameter('url', 'http://www.google.com'); + + expectRequest('POST', '/session/s123/url', + {'url': 'http://www.google.com'}, + {'Accept': 'application/json; charset=utf-8'}). + $does(respondsWith(null, response(200, {}, ''))); + control.$replayAll(); + + assertSendsSuccessfully(command); +} + +function testExecute_returnsParsedJsonResponse() { + var responseObj = { + 'status': bot.ErrorCode.SUCCESS, + 'value': 'http://www.google.com' + }; + var command = new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL). + setParameter('sessionId', 's123'); + + expectRequest('GET', '/session/s123/url', {}, { + 'Accept': 'application/json; charset=utf-8' + }).$does(respondsWith(null, + response(200, {'Content-Type': 'application/json'}, + JSON.stringify(responseObj)))); + control.$replayAll(); + + assertSendsSuccessfully(command, function(response) { + webdriver.test.testutil.assertObjectEquals(responseObj, response); + }); +} + +function testExecute_returnsSuccessFor2xxWithBodyAsValueWhenNotJson() { + var command = new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL). + setParameter('sessionId', 's123'); + + expectRequest('GET', '/session/s123/url', {}, { + 'Accept': 'application/json; charset=utf-8' + }).$does(respondsWith(null, + response(200, {}, 'hello, world\r\ngoodbye, world!'))); + control.$replayAll(); + + assertSendsSuccessfully(command, function(response) { + webdriver.test.testutil.assertObjectEquals({ + 'status': bot.ErrorCode.SUCCESS, + 'value': 'hello, world\ngoodbye, world!' + }, response); + }); +} + +function testExecute_returnsSuccessFor2xxInvalidJsonBody() { + var invalidJson = '['; + expectRequest('POST', '/session', {}, { + 'Accept': 'application/json; charset=utf-8' + }). + $does(respondsWith(null, response(200, { + 'Content-Type': 'application/json' + }, invalidJson))); + control.$replayAll(); + + assertSendsSuccessfully( + new webdriver.Command(webdriver.CommandName.NEW_SESSION), + function(response) { + webdriver.test.testutil.assertObjectEquals({ + 'status': bot.ErrorCode.SUCCESS, + 'value': invalidJson + }, response); + }); +} + +function testExecute_returnsUnknownCommandFor404WithBodyAsValueWhenNotJson() { + var command = new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL). + setParameter('sessionId', 's123'); + + expectRequest('GET', '/session/s123/url', {}, { + 'Accept': 'application/json; charset=utf-8' + }).$does(respondsWith(null, + response(404, {}, 'hello, world\r\ngoodbye, world!'))); + control.$replayAll(); + + assertSendsSuccessfully(command, function(response) { + webdriver.test.testutil.assertObjectEquals({ + 'status': bot.ErrorCode.UNKNOWN_COMMAND, + 'value': 'hello, world\ngoodbye, world!' + }, response); + }); +} + +function testExecute_returnsUnknownErrorForGenericErrorCodeWithBodyAsValueWhenNotJson() { + var command = new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL). + setParameter('sessionId', 's123'); + + expectRequest('GET', '/session/s123/url', {}, { + 'Accept': 'application/json; charset=utf-8' + }).$does(respondsWith(null, + response(500, {}, 'hello, world\r\ngoodbye, world!'))); + control.$replayAll(); + + assertSendsSuccessfully(command, function(response) { + webdriver.test.testutil.assertObjectEquals({ + 'status': bot.ErrorCode.UNKNOWN_ERROR, + 'value': 'hello, world\ngoodbye, world!' + }, response); + }); +} + +function testExecute_attemptsToParseBodyWhenNoContentTypeSpecified() { + var responseObj = { + 'status': bot.ErrorCode.SUCCESS, + 'value': 'http://www.google.com' + }; + var command = new webdriver.Command(webdriver.CommandName.GET_CURRENT_URL). + setParameter('sessionId', 's123'); + + expectRequest('GET', '/session/s123/url', {}, { + 'Accept': 'application/json; charset=utf-8' + }).$does(respondsWith(null, + response(200, {}, JSON.stringify(responseObj)))); + control.$replayAll(); + + assertSendsSuccessfully(command, function(response) { + webdriver.test.testutil.assertObjectEquals(responseObj, response); + }); +} + +function testCanDefineNewCommands() { + executor.defineCommand('greet', 'GET', '/person/:name'); + + var command = new webdriver.Command('greet'). + setParameter('name', 'Bob'); + + expectRequest('GET', '/person/Bob', {}, + {'Accept': 'application/json; charset=utf-8'}). + $does(respondsWith(null, response(200, {}, ''))); + control.$replayAll(); + + assertSendsSuccessfully(command); +} + +function testCanRedefineStandardCommands() { + executor.defineCommand(webdriver.CommandName.GO_BACK, + 'POST', '/custom/back'); + + var command = new webdriver.Command(webdriver.CommandName.GO_BACK). + setParameter('times', 3); + + expectRequest('POST', '/custom/back', + {'times': 3}, + {'Accept': 'application/json; charset=utf-8'}). + $does(respondsWith(null, response(200, {}, ''))); + control.$replayAll(); + + assertSendsSuccessfully(command); +} + +function FakeXmlHttpRequest(headers, status, responseText) { + return { + getAllResponseHeaders: function() { return headers; }, + status: status, + responseText: responseText + }; +} + +function testXmlHttpRequestToHttpResponse_parseHeaders_windows() { + var response = webdriver.http.Response.fromXmlHttpRequest( + FakeXmlHttpRequest([ + 'a:b', + 'c: d', + 'e :f', + 'g : h' + ].join('\r\n'), 200, '')); + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({ + 'a': 'b', + 'c': 'd', + 'e': 'f', + 'g': 'h' + }, response.headers); +} + +function testXmlHttpRequestToHttpResponse_parseHeaders_unix() { + var response = webdriver.http.Response.fromXmlHttpRequest( + FakeXmlHttpRequest([ + 'a:b', + 'c: d', + 'e :f', + 'g : h' + ].join('\n'), 200, '')); + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({ + 'a': 'b', + 'c': 'd', + 'e': 'f', + 'g': 'h' + }, response.headers); +} + +function testXmlHttpRequestToHttpResponse_noHeaders() { + var response = webdriver.http.Response.fromXmlHttpRequest( + FakeXmlHttpRequest('', 200, '')); + assertEquals(200, response.status); + assertEquals('', response.body); + webdriver.test.testutil.assertObjectEquals({}, response.headers); +} + +function testXmlHttpRequestToHttpResponse_stripsNullCharactersFromBody() { + var response = webdriver.http.Response.fromXmlHttpRequest( + FakeXmlHttpRequest('', 200, '\x00\0foo\x00\x00bar\x00\0')); + assertEquals(200, response.status); + assertEquals('foobar', response.body); + webdriver.test.testutil.assertObjectEquals({}, response.headers); +} diff --git a/lib/webdriver/test/http/xhrclient_test.js b/lib/webdriver/test/http/xhrclient_test.js new file mode 100644 index 0000000..9f2c3db --- /dev/null +++ b/lib/webdriver/test/http/xhrclient_test.js @@ -0,0 +1,196 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.http.Request'); +goog.require('webdriver.http.XhrClient'); +goog.require('webdriver.promise'); +goog.require('webdriver.test.testutil'); + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + +// Alias for readability. +var callbackHelper = webdriver.test.testutil.callbackHelper; + +function FakeXhr() {} +FakeXhr.prototype.status = 200; +FakeXhr.prototype.responseText = ''; +FakeXhr.prototype.open = function() {}; +FakeXhr.prototype.send = function() {}; +FakeXhr.prototype.getAllResponseHeaders = function() {}; +FakeXhr.prototype.setRequestHeader = function() {}; + +var URL = 'http://localhost:4444/wd/hub'; +var REQUEST = new webdriver.http.Request('GET', '/foo'); + +var control = new goog.testing.MockControl(); +var stubs = new goog.testing.PropertyReplacer(); +var mockClient, mockXhr; + +function setUp() { + mockClient = control.createStrictMock(webdriver.http.Client); + mockXhr = control.createStrictMock(FakeXhr); + stubs.set(goog.global, 'XMLHttpRequest', function() { + return mockXhr; + }); +} + +function tearDown() { + control.$tearDown(); + stubs.reset(); +} + +function setXhr(value) { + stubs.set(goog.global, 'XMLHttpRequest', value); +} + +function expectRequest(mockXhr) { + mockXhr.open(REQUEST.method, URL + REQUEST.path, true); + for (var header in REQUEST.headers) { + mockXhr.setRequestHeader(header, REQUEST.headers[header]); + } + return mockXhr.send(JSON.stringify(REQUEST.data)); +} + +function testXhrClient_whenUnableToSendARequest() { + expectRequest(mockXhr).$does(function() { + mockXhr.onerror(); + }); + control.$replayAll(); + + var callback; + new webdriver.http.XhrClient(URL).send(REQUEST, + callback = callbackHelper(function(error) { + assertNotNullNorUndefined(error); + assertEquals(1, arguments.length); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testXhrClient_parsesResponseHeaders_windows() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = ''; + mockXhr.onload(); + }); + mockXhr.getAllResponseHeaders().$returns([ + 'a:b', + 'c: d', + 'e :f', + 'g : h' + ].join('\r\n')); + control.$replayAll(); + + var callback; + new webdriver.http.XhrClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({ + 'a': 'b', + 'c': 'd', + 'e': 'f', + 'g': 'h' + }, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testXhrClient_parsesResponseHeaders_unix() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = ''; + mockXhr.onload(); + }); + mockXhr.getAllResponseHeaders().$returns([ + 'a:b', + 'c: d', + 'e :f', + 'g : h' + ].join('\n')); + control.$replayAll(); + + var callback; + new webdriver.http.XhrClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({ + 'a': 'b', + 'c': 'd', + 'e': 'f', + 'g': 'h' + }, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testXhrClient_handlesResponsesWithNoHeaders() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = ''; + mockXhr.onload(); + }); + mockXhr.getAllResponseHeaders().$returns(''); + control.$replayAll(); + + var callback; + new webdriver.http.XhrClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + assertEquals(200, response.status); + assertEquals('', response.body); + + webdriver.test.testutil.assertObjectEquals({}, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} + +function testXhrClient_stripsNullCharactersFromResponseBody() { + expectRequest(mockXhr).$does(function() { + mockXhr.status = 200; + mockXhr.responseText = '\x00foo\x00\x00bar\x00'; + mockXhr.onload(); + }); + mockXhr.getAllResponseHeaders().$returns(''); + control.$replayAll(); + + var callback; + new webdriver.http.XhrClient(URL).send(REQUEST, + callback = callbackHelper(function(e, response) { + assertNull(e); + assertEquals(200, response.status); + assertEquals('foobar', response.body); + webdriver.test.testutil.assertObjectEquals({}, response.headers); + })); + callback.assertCalled(); + control.$verifyAll(); +} diff --git a/lib/webdriver/test/locators_test.js b/lib/webdriver/test/locators_test.js new file mode 100644 index 0000000..21e6479 --- /dev/null +++ b/lib/webdriver/test/locators_test.js @@ -0,0 +1,59 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.By'); +goog.require('webdriver.Locator'); +goog.require('webdriver.Locator.Strategy'); +goog.require('webdriver.test.testutil'); + +// By is exported by webdriver.By, but IDEs don't recognize +// goog.exportSymbol. Explicitly define it here to make the +// IDE stop complaining. +var By = webdriver.By; + +var TARGET = 'some-value'; + +function testCheckLocator() { + function assertLocatorTypeAndTarget(expectedLocator, locator) { + assertEquals('Wrong type', expectedLocator.using, locator.using); + assertEquals('Wrong target', expectedLocator.value, locator.value); + } + + + for (var prop in webdriver.Locator.Strategy) { + var obj = {}; + obj[prop] = TARGET; + assertLocatorTypeAndTarget( + webdriver.Locator.Strategy[prop](TARGET), + webdriver.Locator.checkLocator(obj)); + assertLocatorTypeAndTarget( + webdriver.Locator.Strategy[prop](TARGET), + webdriver.Locator.checkLocator(By[prop](TARGET))); + } + + assertEquals( + 'Should accept custom locator functions', + goog.nullFunction, + webdriver.Locator.checkLocator(goog.nullFunction)); +} + +function testToString() { + assertEquals('By.id("foo")', By.id('foo').toString()); + assertEquals('By.className("foo")', By.className('foo').toString()); + assertEquals('By.linkText("foo")', By.linkText('foo').toString()); +} diff --git a/lib/webdriver/test/logging_test.js b/lib/webdriver/test/logging_test.js new file mode 100644 index 0000000..bb542f9 --- /dev/null +++ b/lib/webdriver/test/logging_test.js @@ -0,0 +1,85 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.debug.LogRecord'); +goog.require('goog.debug.Logger'); +goog.require('goog.testing.jsunit'); +goog.require('webdriver.logging'); + +function convert(level, msg, name, time) { + var recordIn = new webdriver.logging.LogRecord(level, msg, name, time); + return webdriver.logging.Entry.fromClosureLogRecord(recordIn); +} + +function checkRecord(record, level, msg, time) { + assertEquals('wrong level', level.value, record.level.value); + assertEquals('wrong message', msg, record.message); + assertEquals('wrong time', time, record.timestamp); +} + +function testPreferencesToJSON() { + var prefs = new webdriver.logging.Preferences(); + assertObjectEquals({}, prefs.toJSON()); + + prefs.setLevel('foo', webdriver.logging.Level.DEBUG); + assertObjectEquals({'foo': 'DEBUG'}, prefs.toJSON()); + + prefs.setLevel('bar', webdriver.logging.Level.OFF); + prefs.setLevel('baz', webdriver.logging.Level.WARNING); + assertObjectEquals( + {'foo': 'DEBUG', 'bar': 'OFF', 'baz': 'WARNING'}, + prefs.toJSON()); + + // CONFIG should always map to DEBUG. + prefs.setLevel('quux', webdriver.logging.Level.CONFIG); + assertObjectEquals( + {'foo': 'DEBUG', 'bar': 'OFF', 'baz': 'WARNING', 'quux': 'DEBUG'}, + prefs.toJSON()); + + prefs.setLevel('quot', webdriver.logging.Level.ALL); + assertObjectEquals( + {'foo': 'DEBUG', 'bar': 'OFF', 'baz': 'WARNING', 'quux': 'DEBUG', + 'quot': 'ALL'}, + prefs.toJSON()); +} + +function testConvertingLogRecords() { + checkRecord( + convert(goog.debug.Logger.Level.SHOUT, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.SEVERE, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.SEVERE, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.SEVERE, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.WARNING, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.WARNING, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.INFO, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.INFO, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.CONFIG, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.DEBUG, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.FINE, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.DEBUG, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.FINER, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.DEBUG, '[the.name] foo bar', 1234); + checkRecord( + convert(goog.debug.Logger.Level.FINEST, 'foo bar', 'the.name', 1234), + webdriver.logging.Level.DEBUG, '[the.name] foo bar', 1234); +} diff --git a/lib/webdriver/test/process_test.js b/lib/webdriver/test/process_test.js new file mode 100644 index 0000000..c8f7bf6 --- /dev/null +++ b/lib/webdriver/test/process_test.js @@ -0,0 +1,72 @@ +// Copyright 2014 Software Freedom Conservancy. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.jsunit'); +goog.require('webdriver.process'); +goog.require('webdriver.test.testutil'); + +var stubs = new goog.testing.PropertyReplacer(); + +function tearDown() { + stubs.reset(); +} + +function initProcess(windowObject) { + stubs.set(webdriver.process, 'PROCESS_', + webdriver.process.initBrowserProcess_(windowObject)); +} + +function testInitializesEnvironmentVariablesFromLocation() { + initProcess({location: '?a&b=123&c=456&c=789'}); + assertEquals('', webdriver.process.getEnv('a')); + assertEquals('123', webdriver.process.getEnv('b')); + assertEquals('["456","789"]', webdriver.process.getEnv('c')); + assertUndefined(webdriver.process.getEnv('not-there')); +} + +function testSettingEnvironmentVariables() { + initProcess({}); + assertUndefined(webdriver.process.getEnv('foo')); + webdriver.process.setEnv('foo', 'bar'); + assertEquals('bar', webdriver.process.getEnv('foo')); +} + +function testCoercesNewEnvironmentVariablesToAString() { + initProcess({}); + assertUndefined(webdriver.process.getEnv('foo')); + webdriver.process.setEnv('foo', goog.nullFunction); + assertEquals(goog.nullFunction + '', webdriver.process.getEnv('foo')); + + assertUndefined(webdriver.process.getEnv('bar')); + webdriver.process.setEnv('bar', 123); + assertEquals('123', webdriver.process.getEnv('bar')); +} + +function testCanUnsetEnvironmentVariables() { + initProcess({}); + assertUndefined(webdriver.process.getEnv('foo')); + + webdriver.process.setEnv('foo', 'one'); + assertEquals('one', webdriver.process.getEnv('foo')); + + webdriver.process.setEnv('foo'); + assertUndefined(webdriver.process.getEnv('foo')); + + webdriver.process.setEnv('foo', 'two'); + assertEquals('two', webdriver.process.getEnv('foo')); + + webdriver.process.setEnv('foo', null); + assertUndefined(webdriver.process.getEnv('foo')); +} diff --git a/lib/webdriver/test/promise_error_test.js b/lib/webdriver/test/promise_error_test.js new file mode 100644 index 0000000..879045f --- /dev/null +++ b/lib/webdriver/test/promise_error_test.js @@ -0,0 +1,719 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Contains tests against promise error handling. Many tests use + * goog.Promise to control test termination independent of webdriver.promise + * (and notably webdriver.promise.ControlFlow). + */ + +'use strict'; + +goog.require('goog.Promise'); +goog.require('goog.async.run'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('goog.userAgent.product'); +goog.require('webdriver.promise'); +goog.require('webdriver.test.testutil'); + + + +var StubError = webdriver.test.testutil.StubError, + throwStubError = webdriver.test.testutil.throwStubError, + assertIsStubError = webdriver.test.testutil.assertIsStubError; + + +var flow, uncaughtExceptions; + + +function longStackTracesAreBroken() { + // Safari 8.0 is "Safari/538.35.8" in the user agent. + return goog.userAgent.product.SAFARI && + !goog.userAgent.isVersionOrHigher(538); +} + + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + flow = webdriver.promise.controlFlow(); + uncaughtExceptions = []; + flow.on('uncaughtException', onUncaughtException); +} + + +function tearDown() { + return waitForIdle(flow).then(function() { + assertArrayEquals('There were uncaught exceptions', + [], uncaughtExceptions); + flow.reset(); + }); +} + + +function onUncaughtException(e) { + uncaughtExceptions.push(e); +} + + +function waitForAbort(opt_flow, opt_n) { + var n = opt_n || 1; + var theFlow = opt_flow || flow; + theFlow.removeAllListeners( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new goog.Promise(function(fulfill, reject) { + theFlow.once('idle', function() { + reject(Error('expected flow to report an unhandled error')); + }); + + var errors = []; + theFlow.on('uncaughtException', onError); + function onError(e) { + errors.push(e); + if (errors.length === n) { + theFlow.removeListener('uncaughtException', onError); + fulfill(n === 1 ? errors[0] : errors); + } + } + }); +} + + +function waitForIdle(opt_flow) { + var theFlow = opt_flow || flow; + return new goog.Promise(function(fulfill, reject) { + if (!theFlow.activeFrame_ && !theFlow.yieldCount_) { + fulfill(); + return; + } + theFlow.once('idle', fulfill); + theFlow.once('uncaughtException', reject); + }); +} + + +function testRejectedPromiseTriggersErrorCallback() { + return webdriver.promise.rejected(new StubError). + then(fail, assertIsStubError); +} + + +function testCallbackThrowsTriggersSubsequentErrorCallback_fulfilledPromise() { + return webdriver.promise.fulfilled(). + then(throwStubError). + then(fail, assertIsStubError); +} + + +function testCallbackThrowsTriggersSubsequentErrorCallback_rejectedPromise() { + var e = Error('not the droids you are looking for'); + return webdriver.promise.rejected(e). + then(fail, throwStubError). + then(fail, assertIsStubError); +} + + +function +testCallbackReturnsRejectedPromiseTriggersSubsequentErrback_fulfilled() { + return webdriver.promise.fulfilled().then(function() { + return webdriver.promise.rejected(new StubError); + }).then(fail, assertIsStubError); +} + + +function +testCallbackReturnsRejectedPromiseTriggersSubsequentErrback_rejected() { + var e = Error('not the droids you are looking for'); + return webdriver.promise.rejected(e). + then(fail, function() { + return webdriver.promise.rejected(new StubError); + }). + then(fail, assertIsStubError); +} + + +function testReportsUnhandledRejectionsThroughTheControlFlow() { + webdriver.promise.rejected(new StubError); + return waitForAbort().then(assertIsStubError); +} + + +function testMultipleUnhandledRejectionsOutsideATask_reportedInOrderOccurred() { + var e1 = Error('error 1'); + var e2 = Error('error 2'); + + webdriver.promise.rejected(e1); + webdriver.promise.rejected(e2); + + return waitForAbort(flow, 2).then(function(errors) { + assertArrayEquals([e1, e2], errors); + }); +} + + +function testDoesNotReportUnhandledErrorIfHandlerAddedBeforeNextTick() { + var promise = webdriver.promise.rejected(new StubError); + promise.then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testDoesNotReportUnhandledErrorIfHandlerAddedAsyncBeforeReport() { + var called = false; + return new goog.Promise(function(fulfill, reject) { + var promise; + goog.async.run(function() { + promise.then(fail, function(e) { + called = true; + assertIsStubError(e); + }); + waitForIdle().then(fulfill, reject); + }); + promise = webdriver.promise.rejected(new StubError); + }).then(function() { + assertTrue(called); + }) +} + + +function testTaskThrows() { + return flow.execute(throwStubError).then(fail, assertIsStubError); +} + + +function testTaskReturnsRejectedPromise() { + return flow.execute(function() { + return webdriver.promise.rejected(new StubError) + }).then(fail, assertIsStubError); +} + + +function testTaskHasUnhandledRejection() { + return flow.execute(function() { + webdriver.promise.rejected(new StubError) + }).then(fail, assertIsStubError); +} + + +function testTaskFails_returnedPromiseIsUnhandled() { + flow.execute(throwStubError); + return waitForAbort().then(assertIsStubError); +} + + +function testSubTaskFails_caughtByParentTask() { + return flow.execute(function() { + flow.execute(throwStubError); + }).then(fail, assertIsStubError); +} + + +function testSubTaskFails_uncaughtErrorBubblesUpToFlow() { + flow.execute(function() { + flow.execute(throwStubError); + }); + return waitForAbort().then(assertIsStubError); +} + + +function testNestedTaskFails_returnsUpToParent() { + return flow.execute(function() { + return flow.execute(function() { + return flow.execute(throwStubError); + }); + }).then(fail, assertIsStubError); +} + + +function testNestedTaskFails_uncaughtErrorBubblesUp_taskThrows() { + flow.execute(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }); + return waitForAbort().then(assertIsStubError); +} + + +function testNestedTaskFails_uncaughtErrorBubblesUp_taskThrows_caughtAtRoot() { + flow.execute(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }).then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testNestedTaskFails_uncaughtErrorBubblesUp_promise() { + flow.execute(function() { + flow.execute(function() { + flow.execute(function() { + webdriver.promise.rejected(new StubError); + }); + }); + }); + return waitForAbort().then(assertIsStubError); +} + + +function testNestedTaskFails_uncaughtErrorBubblesUp_promise_caughtAtRoot() { + flow.execute(function() { + flow.execute(function() { + webdriver.promise.rejected(new StubError); + }); + }).then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testNestedTaskFails_mixtureOfHangingAndFreeSubTasks() { + flow.execute(function() { + return flow.execute(function() { + flow.execute(throwStubError); + }); + }); + return waitForAbort().then(assertIsStubError); +} + + +function testTaskReturnsPromiseLikeObjectThatInvokesErrback() { + return flow.execute(function() { + return { + 'then': function(_, errback) { + errback('abc123'); + } + }; + }).then(fail, function(value) { + assertEquals('abc123', value); + }); +} + + +function testWaitConditionThrows_waitFailureIsCaught() { + return flow.wait(throwStubError, 50).then(fail, assertIsStubError); +} + + +function testWaitConditionThrows_waitFailureIsNotCaught() { + flow.wait(throwStubError, 50); + return waitForAbort().then(assertIsStubError); +} + + +function testWaitConditionReturnsRejectedPromise_waitFailureIsCaught() { + return flow.wait(function() { + return webdriver.promise.rejected(new StubError); + }, 50).then(fail, assertIsStubError); +} + + +function testWaitConditionReturnsRejectedPromise_waitFailureIsNotCaught() { + flow.wait(function() { + return webdriver.promise.rejected(new StubError); + }, 50); + return waitForAbort().then(assertIsStubError); +} + + +function testWaitConditionHasUnhandledPromiseRejection_waitFailureCaught() { + return flow.wait(function() { + webdriver.promise.rejected(new StubError); + }, 50).then(fail, assertIsStubError); +} + + +function testWaitConditionHasUnhandledPromiseRejection_waitFailureNotCaught() { + flow.wait(function() { + webdriver.promise.rejected(new StubError); + }, 50); + return waitForAbort().then(assertIsStubError); +} + + +function testWaitConditionHasSubTaskFailure_caughtByWait() { + return flow.wait(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }, 50).then(fail, assertIsStubError); +} + + +function testWaitConditionHasSubTaskFailure_notCaughtByWait() { + flow.wait(function() { + flow.execute(function() { + flow.execute(throwStubError); + }); + }, 50); + return waitForAbort().then(assertIsStubError); +} + + +function testErrbackMayThrowANewError_startWithNormalPromise() { + var error = Error('an error'); + return webdriver.promise.rejected(error). + thenCatch(function(e) { + assertEquals(e, error); + throw new StubError; + }). + thenCatch(assertIsStubError); +} + + +function testErrbackMayThrowANewError_startWithTaskResult() { + var error = Error('an error'); + return flow.execute(function() { + throw error; + }). + thenCatch(function(e) { + assertEquals(e, error); + throw new StubError; + }). + thenCatch(assertIsStubError); +} + + +function testErrbackMayThrowANewError_uncaught_startWithNormalPromise() { + var error = Error('an error'); + webdriver.promise.rejected(error). + thenCatch(function(e) { + assertEquals(e, error); + throw new StubError; + }); + return waitForAbort().then(assertIsStubError); +} + + +function testErrbackMayThrowANewError_uncaught_startWithTaskResult() { + var error = Error('an error'); + flow.execute(function() { + throw error; + }). + thenCatch(function(e) { + assertEquals(e, error); + throw new StubError; + }); + return waitForAbort().then(assertIsStubError); +} + + +function testThrownPromiseIsHandledSameAsReturningPromise_promiseIsFulfilled() { + return webdriver.promise.fulfilled().then(function() { + throw webdriver.promise.fulfilled(1234); + }).then(function(value) { + assertEquals(1234, value); + }); +} + + +function testThrownPromiseIsHandledSameAsReturningPromise_promiseIsRejected() { + return webdriver.promise.fulfilled().then(function() { + throw webdriver.promise.rejected(new StubError); + }).then(fail, assertIsStubError); +} + + +function testTaskThrowsPromise_taskSucceedsIfPromiseIsFulfilled() { + flow.execute(function() { + throw webdriver.promise.fulfilled(1234); + }).then(function(value) { + assertEquals(1234, value); + }); + return waitForIdle(); +} + + +function testTaskThrowsPromise_taskFailsIfPromiseIsRejected() { + flow.execute(function() { + throw webdriver.promise.rejected(new StubError); + }).then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testFailsTaskIfThereIsAnUnhandledErrorWhileWaitingOnTaskResult() { + var d = webdriver.promise.defer(); + flow.execute(function() { + setTimeout(function() { + webdriver.promise.rejected(new StubError); + }, 10); + return d.promise; + }).then(fail, assertIsStubError); + + return waitForIdle().then(function() { + return d.promise; + }).then(fail, function(e) { + assertEquals('CancellationError: StubError', e.toString()); + }); +} + + +function testFailsParentTaskIfAsyncScheduledTaskFails() { + var d = webdriver.promise.defer(); + flow.execute(function() { + setTimeout(function() { + flow.execute(throwStubError); + }, 10); + return d.promise; + }).then(fail, assertIsStubError); + + return waitForIdle().then(function() { + return d.promise; + }).then(fail, function(e) { + assertEquals('CancellationError: StubError', e.toString()); + }); +} + + +function testLongStackTraces_alwaysIncludesTaskStacksInFailures() { + if (longStackTracesAreBroken()) { + return; + } + + webdriver.promise.LONG_STACK_TRACES = false; + flow.execute(function() { + flow.execute(function() { + flow.execute(throwStubError, 'throw error'); + }, 'two'); + }, 'three'). + then(fail, function(e) { + assertIsStubError(e); + if (!goog.isString(e.stack)) { + return; + } + var messages = goog.array.filter( + webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) { + return /^From: /.test(line); + }); + assertArrayEquals([ + 'From: Task: throw error', + 'From: Task: two', + 'From: Task: three' + ], messages); + }); + return waitForIdle(); +} + + +function testLongStackTraces_doesNotIncludeCompletedTasks() { + if (longStackTracesAreBroken()) { + return; + } + + flow.execute(goog.nullFunction, 'succeeds'); + flow.execute(throwStubError, 'kaboom').then(fail, function(e) { + assertIsStubError(e); + if (!goog.isString(e.stack)) { + return; + } + var messages = goog.array.filter( + webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) { + return /^From: /.test(line); + }); + assertArrayEquals(['From: Task: kaboom'], messages); + }); + return waitForIdle(); +} + + +function testLongStackTraces_doesNotIncludePromiseChainWhenDisabled() { + if (longStackTracesAreBroken()) { + return; + } + + webdriver.promise.LONG_STACK_TRACES = false; + flow.execute(function() { + flow.execute(function() { + return webdriver.promise.fulfilled(). + then(goog.nullFunction). + then(goog.nullFunction). + then(throwStubError); + }, 'eventually fails'); + }, 'start'). + then(fail, function(e) { + assertIsStubError(e); + if (!goog.isString(e.stack)) { + return; + } + var messages = goog.array.filter( + webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) { + return /^From: /.test(line); + }); + assertArrayEquals([ + 'From: Task: eventually fails', + 'From: Task: start' + ], messages); + }); + return waitForIdle(); +} + + +function testLongStackTraces_includesPromiseChainWhenEnabled() { + if (longStackTracesAreBroken()) { + return; + } + + webdriver.promise.LONG_STACK_TRACES = true; + flow.execute(function() { + flow.execute(function() { + return webdriver.promise.fulfilled(). + then(goog.nullFunction). + then(goog.nullFunction). + then(throwStubError); + }, 'eventually fails'); + }, 'start'). + then(fail, function(e) { + assertIsStubError(e); + if (!goog.isString(e.stack)) { + return; + } + var messages = goog.array.filter( + webdriver.stacktrace.getStack(e).split(/\n/), function(line, index) { + return /^From: /.test(line); + }); + assertArrayEquals([ + 'From: Promise: then', + 'From: Task: eventually fails', + 'From: Task: start' + ], messages); + }); + return waitForIdle(); +} + + +function testFrameCancelsRemainingTasks_onUnhandledTaskFailure() { + var run = false; + return flow.execute(function() { + flow.execute(throwStubError); + flow.execute(function() { run = true; }); + }).then(fail, function(e) { + assertIsStubError(e); + assertFalse(run); + }); +} + + +function testFrameCancelsRemainingTasks_onUnhandledPromiseRejection() { + var run = false; + return flow.execute(function() { + webdriver.promise.rejected(new StubError); + flow.execute(function() { run = true; }); + }).then(fail, function(e) { + assertIsStubError(e); + assertFalse(run); + }); +} + + +function testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_return() { + var seen = []; + return flow.execute(function() { + flow.execute(throwStubError); + + flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); + }); + }).then(fail, function(e) { + assertIsStubError(e); + assertArrayEquals([], seen); + }); +} + + +function testRegisteredTaskCallbacksAreDroppedWhenTaskIsCancelled_withReturn() { + var seen = []; + return flow.execute(function() { + flow.execute(throwStubError); + + return flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); + }); + }).then(fail, function(e) { + assertIsStubError(e); + assertArrayEquals([], seen); + }); +} + + +function testTasksWithinQueuedCallbackInAFrameAreDroppedIfFrameAborts() { + var seen = []; + return flow.execute(function() { + flow.execute(throwStubError); + webdriver.promise.fulfilled().then(function() { + seen.push(1); + + return flow.execute(function() { + seen.push(2); + }); + + // This callback depends on the result of a cancelled task, so it will never + // be invoked. + }).thenFinally(function() { + seen.push(3); + }); + }).then(fail, function(e) { + assertIsStubError(e); + assertArrayEquals([1], seen); + }); +} + + +function testTaskIsCancelledAfterWaitTimeout() { + var seen = []; + return flow.execute(function() { + flow.wait(function() { + webdriver.promies.delayed(100).then(goog.nullFunction); + }, 5); + + return flow.execute(function() { + seen.push(1); + }).then(function() { + seen.push(2); + }, function() { + seen.push(3); + }); + }).then(fail, function(e) { + assertArrayEquals([], seen); + }); +} + + +function testTaskCallbacksGetCancellationErrorIfRegisteredAfterTaskIsCancelled() { + var task; + flow.execute(function() { + flow.execute(throwStubError); + task = flow.execute(goog.nullFunction); + }).then(fail, assertIsStubError); + return waitForIdle().then(function() { + return task.then(fail, function(e) { + assertTrue(e instanceof webdriver.promise.CancellationError); + }); + }); +} diff --git a/lib/webdriver/test/promise_flow_test.js b/lib/webdriver/test/promise_flow_test.js new file mode 100644 index 0000000..9c00a61 --- /dev/null +++ b/lib/webdriver/test/promise_flow_test.js @@ -0,0 +1,1957 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.promise'); +goog.require('webdriver.stacktrace.Snapshot'); +goog.require('webdriver.stacktrace'); +goog.require('webdriver.test.testutil'); + +// Aliases for readability. +var StubError = webdriver.test.testutil.StubError, + throwStubError = webdriver.test.testutil.throwStubError, + assertIsStubError = webdriver.test.testutil.assertIsStubError, + assertingMessages = webdriver.test.testutil.assertingMessages, + callbackHelper = webdriver.test.testutil.callbackHelper, + callbackPair = webdriver.test.testutil.callbackPair; + +var flow, flowHistory, uncaughtExceptions; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + webdriver.promise.LONG_STACK_TRACES = false; + flow = new webdriver.promise.ControlFlow(); + webdriver.promise.setDefaultFlow(flow); + webdriver.test.testutil.messages = []; + flowHistory = []; + + uncaughtExceptions = []; + flow.on(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + onUncaughtException); +} + + +function tearDown() { + flow.removeAllListeners( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + assertArrayEquals('There were uncaught exceptions', [], uncaughtExceptions); + flow.reset(); + webdriver.promise.LONG_STACK_TRACES = false; +} + + +function onUncaughtException(e) { + uncaughtExceptions.push(e); +} + + +function waitForAbort(opt_flow) { + var theFlow = opt_flow || flow; + theFlow.removeAllListeners( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new goog.Promise(function(fulfill, reject) { + theFlow.once(webdriver.promise.ControlFlow.EventType.IDLE, function() { + reject(Error('expected flow to report an unhandled error')); + }); + theFlow.once( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + fulfill); + }); +} + + +function waitForIdle(opt_flow) { + var theFlow = opt_flow || flow; + return new goog.Promise(function(fulfill, reject) { + theFlow.once(webdriver.promise.ControlFlow.EventType.IDLE, fulfill); + theFlow.once( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, reject); + }); +} + +function timeout(ms) { + return new goog.Promise(function(fulfill) { + setTimeout(fulfill, ms); + }); +} + + +function schedule(msg, opt_return) { + return scheduleAction(msg, function() { + return opt_return; + }); +} + +/** + * @param {string} value The value to push. + * @param {webdriver.promise.Promise=} opt_taskPromise Promise to return from + * the task. + * @return {!webdriver.promise.Promise} The result. + */ +function schedulePush(value, opt_taskPromise) { + return scheduleAction(value, function() { + webdriver.test.testutil.messages.push(value); + return opt_taskPromise; + }); +} + +/** + * @param {string} msg Debug message. + * @param {!Function} actionFn The function. + * @return {!webdriver.promise.Promise} The function result. + */ +function scheduleAction(msg, actionFn) { + return webdriver.promise.controlFlow().execute(function() { + flowHistory.push(msg); + return actionFn(); + }, msg); +} + +/** + * @param {!Function} condition The condition function. + * @param {number} timeout The timeout. + * @param {string=} opt_message Optional message. + * @return {!webdriver.promise.Promise} The wait result. + */ +function scheduleWait(condition, timeout, opt_message) { + var msg = opt_message || ''; + // It's not possible to hook into when the wait itself is scheduled, so + // we record each iteration of the wait loop. + var count = 0; + return webdriver.promise.controlFlow().wait(function() { + flowHistory.push((count++) + ': ' + msg); + return condition(); + }, timeout, msg); +} + + +function assertFlowHistory(var_args) { + var expected = goog.array.slice(arguments, 0); + assertArrayEquals(expected, flowHistory); +} + + +/** + * @param {string=} opt_description A description of the task for debugging. + * @return {!webdriver.promise.Task_} The new task. + */ +function createTask(opt_description) { + return new webdriver.promise.Task_( + webdriver.promise.controlFlow(), + goog.nullFunction, + opt_description || '', + new webdriver.stacktrace.Snapshot()); +} + +/** + * @return {!webdriver.promise.Frame_} + */ +function createFrame() { + return new webdriver.promise.Frame_(webdriver.promise.controlFlow()); +} + + +function testScheduling_aSimpleFunction() { + schedule('go'); + return waitForIdle().then(function() { + assertFlowHistory('go'); + }); +} + + +function testScheduling_aSimpleFunctionWithANonPromiseReturnValue() { + schedule('go', 123).then(function(value) { + assertEquals(123, value); + }); + return waitForIdle().then(function() { + assertFlowHistory('go'); + }); +} + + +function testScheduling_aSimpleSequence() { + schedule('a'); + schedule('b'); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testScheduling_invokesCallbacksWhenTaskIsDone() { + var d = new webdriver.promise.Deferred(); + var called = false; + var done = schedule('a', d.promise).then(function(value) { + called = true; + assertEquals(123, value); + }); + return timeout(5).then(function() { + assertFalse(called); + d.fulfill(123); + return done; + }). + then(waitForIdle). + then(function() { + assertFlowHistory('a'); + }); +} + + +function testScheduling_blocksUntilPromiseReturnedByTaskIsResolved() { + var done = webdriver.promise.defer(); + schedulePush('a', done.promise); + schedulePush('b'); + setTimeout(function() { + done.fulfill(); + webdriver.test.testutil.messages.push('c'); + }, 25); + return waitForIdle().then(assertingMessages('a', 'c', 'b')); +} + + +function testScheduling_waitsForReturnedPromisesToResolve() { + var d1 = new webdriver.promise.Deferred(); + var d2 = new webdriver.promise.Deferred(); + + var callback; + schedule('a', d1.promise).then(callback = callbackHelper(function(value) { + assertEquals('fluffy bunny', value); + })); + + return timeout(5).then(function() { + callback.assertNotCalled('d1 not resolved yet'); + d1.fulfill(d2); + return timeout(5); + }).then(function() { + callback.assertNotCalled('d2 not resolved yet'); + d2.fulfill('fluffy bunny'); + return waitForIdle(); + }).then(function() { + callback.assertCalled('d2 has been resolved'); + assertFlowHistory('a'); + }); +} + + +function testScheduling_executesTasksInAFutureTurnAfterTheyAreScheduled() { + var count = 0; + function incr() { count++; } + + scheduleAction('', incr); + assertEquals(0, count); + return waitForIdle().then(function() { + assertEquals(1, count); + }); +} + + +function testScheduling_executesOneTaskPerTurnOfTheEventLoop() { + var order = []; + function go() { + order.push(order.length / 2); + goog.async.run(function() { + order.push('-'); + }); + } + + scheduleAction('', go); + scheduleAction('', go); + return waitForIdle().then(function() { + assertArrayEquals([0, '-', 1, '-'], order); + }) +} + + +function testScheduling_firstScheduledTaskIsWithinACallback() { + webdriver.promise.fulfilled().then(function() { + schedule('a'); + schedule('b'); + schedule('c'); + }).then(function() { + assertFlowHistory('a', 'b', 'c'); + }); + return waitForIdle(); +} + + +function testScheduling_newTasksAddedWhileWaitingOnTaskReturnedPromise() { + scheduleAction('a', function() { + var d = webdriver.promise.defer(); + setTimeout(function() { + schedule('c'); + d.fulfill(); + }, 10); + return d.promise; + }); + schedule('b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); +} + + +function testFraming_callbacksRunInANewFrame() { + schedule('a').then(function() { + schedule('c'); + }); + schedule('b'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); +} + + +function testFraming_lotsOfNesting() { + schedule('a').then(function() { + schedule('c').then(function() { + schedule('e').then(function() { + schedule('g'); + }); + schedule('f'); + }); + schedule('d'); + }); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'e', 'g', 'f', 'd', 'b'); + }); +} + + +function testFrame_callbackReturnsPromiseThatDependsOnATask_1() { + schedule('a').then(function() { + schedule('b'); + return webdriver.promise.delayed(5).then(function() { + return schedule('c'); + }); + }); + schedule('d'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); +} + + +function testFrame_callbackReturnsPromiseThatDependsOnATask_2() { + schedule('a').then(function() { + schedule('b'); + return webdriver.promise.delayed(5). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return schedule('c'); }); + }); + schedule('d'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); +} + + +function testFraming_eachCallbackWaitsForAllScheduledTasksToComplete() { + schedule('a'). + then(function() { + schedule('b'); + schedule('c'); + }). + then(function() { + schedule('d'); + }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); +} + + +function testFraming_eachCallbackWaitsForReturnTasksToComplete() { + schedule('a'). + then(function() { + schedule('b'); + return schedule('c'); + }). + then(function() { + schedule('d'); + }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); +} + + +function testFraming_callbacksOnAResolvedPromiseInsertIntoTheCurrentFlow() { + webdriver.promise.fulfilled().then(function() { + schedule('b'); + }); + schedule('a'); + + return waitForIdle().then(function() { + assertFlowHistory('b', 'a'); + }); +} + + +function testFraming_callbacksInterruptTheFlowWhenPromiseIsResolved() { + schedule('a').then(function() { + schedule('c'); + }) + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'c', 'b'); + }); +} + + +function testFraming_allCallbacksInAFrameAreScheduledWhenPromiseIsResolved() { + var a = schedule('a'); + a.then(function() { schedule('b'); }); + schedule('c'); + a.then(function() { schedule('d'); }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'd', 'c', 'e'); + }); +} + + +function testFraming_tasksScheduledInInActiveFrameDoNotGetPrecedence() { + var d = webdriver.promise.fulfilled(); + schedule('a'); + schedule('b'); + d.then(function() { schedule('c'); }); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testFraming_tasksScheduledInAFrameGetPrecedence_1() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { + schedule('c'); + schedule('d'); + }); + var e = schedule('e'); + a.then(function() { + // When this function runs, |e| will not be resolved yet, so |f| and + // |h| will be resolved first. After |e| is resolved, |g| will be + // scheduled in a new frame, resulting in: [j][f, h, i][g], so |g| is + // expected to execute first. + schedule('f'); + e.then(function() { + schedule('g'); + }); + schedule('h'); + }); + schedule('i'); + }); + schedule('j'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'g', 'f', 'h', 'i', 'j'); + }); +} + + +function testErrorHandling_thrownErrorsArePassedToTaskErrback() { + scheduleAction('function that throws', throwStubError). + then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testErrorHandling_thrownErrorsPropagateThroughPromiseChain() { + scheduleAction('function that throws', throwStubError). + then(fail). + then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testErrorHandling_catchesErrorsFromFailedTasksInAFrame() { + schedule('a').then(function() { + schedule('b'); + scheduleAction('function that throws', throwStubError); + }). + then(fail, assertIsStubError); + return waitForIdle(); +} + + +function testErrorHandling_abortsIfOnlyTaskReturnsAnUnhandledRejection() { + scheduleAction('function that returns rejected promise', function() { + return webdriver.promise.rejected(new StubError); + }); + return waitForAbort().then(assertIsStubError); +} + + +function testErrorHandling_abortsIfThereIsAnUnhandledRejection() { + webdriver.promise.rejected(new StubError); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory(/* none */); + }); +} + + +function testErrorHandling_abortsSequenceIfATaskFails() { + schedule('a'); + schedule('b'); + scheduleAction('c', throwStubError); + schedule('d'); // Should never execute. + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testErrorHandling_abortsFromUnhandledFramedTaskFailures_1() { + schedule('outer task').then(function() { + scheduleAction('inner task', throwStubError); + }); + schedule('this should not run'); + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('outer task', 'inner task'); + }); +} + + +function testErrorHandling_abortsFromUnhandledFramedTaskFailures_2() { + schedule('a').then(function() { + schedule('b').then(function() { + scheduleAction('c', throwStubError); + // This should not execute. + schedule('d'); + }); + }); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testErrorHandling_abortsWhenErrorBubblesUpFromFullyResolvingAnObject() { + var callback = callbackHelper(function() { + return webdriver.promise.rejected('rejected 2'); + }); + + scheduleAction('', function() { + var obj = {'foo': webdriver.promise.rejected(new StubError)}; + return webdriver.promise.fullyResolved(obj).then(callback); + }); + + return waitForAbort(). + then(assertIsStubError). + then(callback.assertNotCalled); +} + + +function testErrorHandling_abortsWhenErrorBubblesUpFromFullyResolvingAnObject_withCallback() { + var callback1 = callbackHelper(function() { + return webdriver.promise.rejected('rejected 2'); + }); + var callback2 = callbackHelper(); + + scheduleAction('', function() { + var obj = {'foo': webdriver.promise.rejected(new StubError)}; + return webdriver.promise.fullyResolved(obj).then(callback1); + }).then(callback2); + + return waitForAbort(). + then(assertIsStubError). + then(callback1.assertNotCalled). + then(callback2.assertNotCalled); +} + + +function testErrorHandling_canCatchErrorsFromNestedTasks() { + var errback; + schedule('a'). + then(function() { + return scheduleAction('b', throwStubError); + }). + thenCatch(errback = callbackHelper(assertIsStubError)); + return waitForIdle().then(errback.assertCalled); +} + + +function testErrorHandling_nestedCommandFailuresCanBeCaughtAndSuppressed() { + var errback; + schedule('a').then(function() { + return schedule('b').then(function() { + return schedule('c').then(function() { + throw new StubError; + }); + }); + }).thenCatch(errback = callbackHelper(assertIsStubError)); + schedule('d'); + return waitForIdle(). + then(errback.assertCalled). + then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); +} + + +function testErrorHandling_aTaskWithAnUnhandledPromiseRejection() { + schedule('a'); + scheduleAction('sub-tasks', function() { + webdriver.promise.rejected(new StubError); + }); + schedule('should never run'); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); +} + +function testErrorHandling_aTaskThatReutrnsARejectedPromise() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return webdriver.promise.rejected(new StubError); + }); + schedule('should never run'); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); +} + + +function testErrorHandling_discardsSubtasksIfTaskThrows() { + var pair = callbackPair(null, assertIsStubError); + scheduleAction('a', function() { + schedule('b'); + schedule('c'); + throwStubError(); + }).then(pair.callback, pair.errback); + schedule('d'); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'd'); + }); +} + + +function testErrorHandling_discardsRemainingSubtasksIfASubtaskFails() { + var pair = callbackPair(null, assertIsStubError); + scheduleAction('a', function() { + schedule('b'); + scheduleAction('c', throwStubError); + schedule('d'); + }).then(pair.callback, pair.errback); + schedule('e'); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory('a', 'b', 'c', 'e'); + }); +} + + +function testTryFinally_happyPath() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + schedulePush('foo'). + then(goog.partial(schedulePush, 'bar')). + thenFinally(goog.partial(schedulePush, 'baz')); + return waitForIdle().then(assertingMessages('foo', 'bar', 'baz')); +} + + +function testTryFinally_firstTryFails() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + + scheduleAction('doFoo and throw', function() { + webdriver.test.testutil.messages.push('foo'); + throw new StubError; + }). + then(function() { schedulePush('bar'); }). + thenFinally(function() { schedulePush('baz'); }); + + return waitForAbort(). + then(assertIsStubError). + then(assertingMessages('foo', 'baz')); +} + + +function testTryFinally_secondTryFails() { + /* Model: + try { + doFoo(); + doBar(); + } finally { + doBaz(); + } + */ + + schedulePush('foo'). + then(function() { + return scheduleAction('doBar and throw', function() { + webdriver.test.testutil.messages.push('bar'); + throw new StubError; + }); + }). + thenFinally(function() { + return schedulePush('baz'); + }); + return waitForAbort(). + then(assertIsStubError). + then(assertingMessages('foo', 'bar', 'baz')); +} + + +function testTaskCallbacksInterruptFlow() { + schedule('a').then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function +testTaskCallbacksInterruptFlow_taskDependsOnImmediatelyFulfilledPromise() { + scheduleAction('a', function() { + return webdriver.promise.fulfilled(); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testTaskCallbacksInterruptFlow_taskDependsOnPreviouslyFulfilledPromise() { + var promise = webdriver.promise.fulfilled(123); + scheduleAction('a', function() { + return promise; + }).then(function(value) { + assertEquals(123, value); + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testTaskCallbacksInterruptFlow_taskDependsOnAsyncPromise() { + scheduleAction('a', function() { + return webdriver.promise.delayed(25); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testPromiseChainedToTaskInterruptFlow() { + schedule('a').then(function() { + return webdriver.promise.fulfilled(); + }).then(function() { + return webdriver.promise.fulfilled(); + }).then(function() { + schedule('b'); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testNestedTaskCallbacksInterruptFlowWhenResolved() { + schedule('a').then(function() { + schedule('b').then(function() { + schedule('c'); + }); + }); + schedule('d'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd'); + }); +} + + +function testDelayedNesting_1() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + schedule('d'); + }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); +} + + +function testDelayedNesting_2() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + schedule('d'); + a.then(function() { schedule('e'); }); + }); + schedule('f'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + }); +} + + +function testDelayedNesting_3() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }); + a.then(function() { schedule('d'); }); + }); + schedule('e'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e'); + }); +} + + +function testDelayedNesting_4() { + var a = schedule('a'); + schedule('b').then(function() { + a.then(function() { schedule('c'); }).then(function() { + schedule('d'); + }); + a.then(function() { schedule('e'); }); + }); + schedule('f'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f'); + }); +} + + +function testDelayedNesting_5() { + var a = schedule('a'); + schedule('b').then(function() { + var c; + a.then(function() { c = schedule('c'); }).then(function() { + schedule('d'); + a.then(function() { schedule('e'); }); + c.then(function() { schedule('f'); }); + schedule('g'); + }); + a.then(function() { schedule('h'); }); + }); + schedule('i'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'); + }); +} + + +function testWaiting_onAConditionThatIsAlwaysTrue() { + scheduleWait(function() { return true;}, 0, 'waiting on true'); + return waitForIdle().then(function() { + assertFlowHistory('0: waiting on true'); + }); +} + + +function testWaiting_aSimpleCountingCondition() { + var count = 0; + scheduleWait(function() { + return ++count == 3; + }, 200, 'counting to 3'); + + return waitForIdle().then(function() { + assertEquals(3, count); + }); +} + + +function testWaiting_aConditionThatReturnsAPromise() { + var d = new webdriver.promise.Deferred(); + var count = 0; + + scheduleWait(function() { + count += 1; + return d.promise; + }, 0, 'waiting for promise'); + + return timeout(50).then(function() { + assertEquals(1, count); + d.fulfill(123); + return waitForIdle(); + }); +} + + +function testWaiting_aConditionThatReturnsAPromise_2() { + var count = 0; + scheduleWait(function() { + return webdriver.promise.fulfilled(++count == 3); + }, 200, 'waiting for promise'); + + return waitForIdle().then(function() { + assertEquals(3, count); + }); +} + + +function testWaiting_aConditionThatReturnsATaskResult() { + var count = 0; + scheduleWait(function() { + return scheduleAction('increment count', function() { + return ++count == 3; + }); + }, 200, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assertEquals(3, count); + assertFlowHistory( + '0: counting to 3', 'increment count', + '1: counting to 3', 'increment count', + '2: counting to 3', 'increment count', + 'post wait'); + }); +} + + +function testWaiting_conditionContainsASubtask() { + var count = 0; + scheduleWait(function() { + schedule('sub task'); + return ++count == 3; + }, 200, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assertEquals(3, count); + assertFlowHistory( + '0: counting to 3', 'sub task', + '1: counting to 3', 'sub task', + '2: counting to 3', 'sub task', + 'post wait'); + }); +} + + +function testWaiting_cancelsWaitIfScheduledTaskFails() { + var pair = callbackPair(null, assertIsStubError); + scheduleWait(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return true; + }, 200, 'waiting to go boom').then(pair.callback, pair.errback); + schedule('post wait'); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFlowHistory( + '0: waiting to go boom', 'boom', + 'post wait'); + }); +} + + +function testWaiting_failsIfConditionThrows() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(throwStubError, 0, 'goes boom'). + then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); +} + + +function testWaiting_failsIfConditionReturnsARejectedPromise() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(function() { + return webdriver.promise.rejected(new StubError); + }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); +} + + +function testWaiting_failsIfConditionHasUnhandledRejection() { + var callbacks = callbackPair(null, assertIsStubError); + scheduleWait(function() { + webdriver.promise.controlFlow().execute(throwStubError); + }, 0, 'goes boom').then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle(). + then(callbacks.assertErrback). + then(function() { + assertFlowHistory('0: goes boom', 'post wait'); + }); +} + + +function testWaiting_failsIfConditionHasAFailedSubtask() { + var callbacks = callbackPair(null, assertIsStubError); + var count = 0; + scheduleWait(function() { + scheduleAction('maybe throw', function() { + if (++count == 2) { + throw new StubError; + } + }); + }, 200, 'waiting').then(callbacks.callback, callbacks.errback); + schedule('post wait'); + + return waitForIdle().then(function() { + assertEquals(2, count); + assertFlowHistory( + '0: waiting', 'maybe throw', + '1: waiting', 'maybe throw', + 'post wait'); + }); +} + + +function testWaiting_pollingLoopWaitsForAllScheduledTasksInCondition() { + var count = 0; + scheduleWait(function() { + scheduleAction('increment count', function() { ++count; }); + return count >= 3; + }, 350, 'counting to 3'); + schedule('post wait'); + + return waitForIdle().then(function() { + assertEquals(4, count); + assertFlowHistory( + '0: counting to 3', 'increment count', + '1: counting to 3', 'increment count', + '2: counting to 3', 'increment count', + '3: counting to 3', 'increment count', + 'post wait'); + }); +} + + +function testWaiting_waitsForeverOnAZeroTimeout() { + var done = false; + setTimeout(function() { + done = true; + }, 500); + var waitResult = scheduleWait(function() { + return done; + }, 0); + + return timeout(250).then(function() { + assertFalse(done); + return timeout(300); + }).then(function() { + assertTrue(done); + return waitResult; + }); +} + + +function testWaiting_waitsForeverIfTimeoutOmitted() { + var done = false; + setTimeout(function() { + done = true; + }, 500); + var waitResult = scheduleWait(function() { + return done; + }); + + return timeout(250).then(function() { + assertFalse(done); + return timeout(300); + }).then(function() { + assertTrue(done); + return waitResult; + }); +} + + +function testWaiting_timesOut_nonZeroTimeout() { + var count = 0; + scheduleWait(function() { + count += 1; + var ms = count === 2 ? 65 : 5; + var start = goog.now(); + return webdriver.promise.delayed(ms).then(function() { + return false; + }); + }, 60, 'counting to 3'); + return waitForAbort().then(function(e) { + switch (count) { + case 1: + assertFlowHistory('0: counting to 3'); + break; + case 2: + assertFlowHistory('0: counting to 3', '1: counting to 3'); + break; + default: + fail('unexpected polling count: ' + count); + } + assertRegExp(/^counting to 3\nWait timed out after \d+ms$/, e.message); + }); +} + + +function testWaiting_shouldFailIfConditionReturnsARejectedPromise() { + var count = 0; + scheduleWait(function() { + return webdriver.promise.rejected(new StubError); + }, 100, 'counting to 3'); + return waitForAbort().then(assertIsStubError); +} + + +function testWaiting_scheduleWithIntermittentWaits() { + schedule('a'); + scheduleWait(function() { return true; }, 0, 'wait 1'); + schedule('b'); + scheduleWait(function() { return true; }, 0, 'wait 2'); + schedule('c'); + scheduleWait(function() { return true; }, 0, 'wait 3'); + + return waitForIdle().then(function() { + assertFlowHistory('a', '0: wait 1', 'b', '0: wait 2', 'c', '0: wait 3'); + }); +} + + +function testWaiting_scheduleWithIntermittentAndNestedWaits() { + schedule('a'); + scheduleWait(function() { return true; }, 0, 'wait 1'). + then(function() { + schedule('d'); + scheduleWait(function() { return true; }, 0, 'wait 2'); + schedule('e'); + }); + schedule('b'); + scheduleWait(function() { return true; }, 0, 'wait 3'); + schedule('c'); + scheduleWait(function() { return true; }, 0, 'wait 4'); + + return waitForIdle().then(function() { + assertFlowHistory( + 'a', '0: wait 1', 'd', '0: wait 2', 'e', 'b', '0: wait 3', 'c', + '0: wait 4'); + }); +} + + +function testWait_requiresConditionToBeAPromiseOrFunction() { + assertThrows(function() { + flow.wait(1234, 0); + }); + flow.wait(function() { return true;}, 0); + flow.wait(webdriver.promise.fulfilled(), 0); + return waitForIdle(); +} + + +function testWait_promiseThatDoesNotResolveBeforeTimeout() { + var d = webdriver.promise.defer(); + flow.wait(d.promise, 5).then(fail, function(e) { + assertRegExp(/Timed out waiting for promise to resolve after \d+ms/, + e.message); + }); + return waitForIdle().then(function() { + assertTrue('Promise should not be cancelled', d.promise.isPending()); + }); +} + + +function testWait_unboundedWaitOnPromiseResolution() { + var messages = []; + var d = webdriver.promise.defer(); + var waitResult = flow.wait(d.promise).then(function(value) { + messages.push('b'); + assertEquals(1234, value); + }); + setTimeout(function() { + messages.push('a'); + }, 5); + + webdriver.promise.delayed(10).then(function() { + assertArrayEquals(['a'], messages); + assertTrue(waitResult.isPending()); + d.fulfill(1234); + return waitResult; + }).then(function(value) { + assertArrayEquals(['a', 'b'], messages); + }); + + return waitForIdle(); +} + + +function testSubtasks() { + schedule('a'); + scheduleAction('sub-tasks', function() { + schedule('c'); + schedule('d'); + }); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks', 'c', 'd', 'b'); + }); +} + + +function testSubtasks_nesting() { + schedule('a'); + scheduleAction('sub-tasks', function() { + schedule('b'); + scheduleAction('sub-sub-tasks', function() { + schedule('c'); + schedule('d'); + }); + schedule('e'); + }); + schedule('f'); + + return waitForIdle().then(function() { + assertFlowHistory( + 'a', 'sub-tasks', 'b', 'sub-sub-tasks', 'c', 'd', 'e', 'f'); + }); +} + + +function testSubtasks_taskReturnsSubTaskResult_1() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return schedule('c'); + }); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks', 'c', 'b'); + }); +} + + +function testSubtasks_taskReturnsSubTaskResult_2() { + var callback; + schedule('a'); + schedule('sub-tasks', webdriver.promise.fulfilled(123)). + then(callback = callbackHelper(function(value) { + assertEquals(123, value); + })); + schedule('b'); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'sub-tasks','b'); + callback.assertCalled(); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_1() { + scheduleAction('a', function() { + return webdriver.promise.delayed(10).then(function() { + schedule('b'); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_2() { + scheduleAction('a', function() { + return webdriver.promise.fulfilled().then(function() { + schedule('b'); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_3() { + scheduleAction('a', function() { + return webdriver.promise.delayed(10).then(function() { + return schedule('b'); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_4() { + scheduleAction('a', function() { + return webdriver.promise.delayed(5).then(function() { + return webdriver.promise.delayed(5).then(function() { + return schedule('b'); + }); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_5() { + scheduleAction('a', function() { + return webdriver.promise.delayed(5).then(function() { + return webdriver.promise.delayed(5).then(function() { + return webdriver.promise.delayed(5).then(function() { + return webdriver.promise.delayed(5).then(function() { + return schedule('b'); + }); + }); + }); + }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + + +function testSubtasks_taskReturnsPromiseThatDependsOnSubtask_6() { + scheduleAction('a', function() { + return webdriver.promise.delayed(5). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return webdriver.promise.delayed(5) }). + then(function() { return schedule('b'); }); + }); + schedule('c'); + return waitForIdle().then(function() { + assertFlowHistory('a', 'b', 'c'); + }); +} + +function testSubtasks_subTaskFails_1() { + schedule('a'); + scheduleAction('sub-tasks', function() { + scheduleAction('sub-task that fails', throwStubError); + }); + schedule('should never execute'); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks', 'sub-task that fails'); + }); +} + + +function testSubtasks_subTaskFails_2() { + schedule('a'); + scheduleAction('sub-tasks', function() { + return webdriver.promise.rejected(new StubError); + }); + schedule('should never execute'); + + return waitForAbort(). + then(assertIsStubError). + then(function() { + assertFlowHistory('a', 'sub-tasks'); + }); +} + + +function testSubtasks_subTaskFails_3() { + var callbacks = callbackPair(null, assertIsStubError); + + schedule('a'); + scheduleAction('sub-tasks', function() { + return webdriver.promise.rejected(new StubError); + }).then(callbacks.callback, callbacks.errback); + schedule('b'); + + return waitForIdle(). + then(function() { + assertFlowHistory('a', 'sub-tasks', 'b'); + callbacks.assertErrback(); + }); +} + + +function testEventLoopWaitsOnPendingPromiseRejections_oneRejection() { + var d = new webdriver.promise.Deferred; + scheduleAction('one', function() { + return d.promise; + }); + scheduleAction('two', goog.nullFunction); + + return timeout(50).then(function() { + assertFlowHistory('one'); + d.reject(new StubError); + return waitForAbort(); + }). + then(assertIsStubError). + then(function() { + assertFlowHistory('one'); + }); +} + + +function testEventLoopWaitsOnPendingPromiseRejections_multipleRejections() { + var once = Error('once'); + var twice = Error('twice'); + var seen = []; + + scheduleAction('one', function() { + webdriver.promise.rejected(once); + webdriver.promise.rejected(twice); + }); + var twoResult = scheduleAction('two', goog.nullFunction); + + flow.removeAllListeners( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new goog.Promise(function(fulfill, reject) { + setTimeout(function() { + reject(Error('Should have reported the two errors by now: ' + seen)); + }, 500); + flow.on( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + function(e) { + seen.push(e); + if (seen.length === 2) { + fulfill(); + } + }); + }).then(function() { + seen.sort(); + assertArrayEquals([once, twice], seen); + assertFlowHistory('one'); + assertFalse('Did not cancel the second task', twoResult.isPending()); + }); +} + +function testCancelsPromiseReturnedByCallbackIfFrameFails_promiseCallback() { + var chainPair = callbackPair(null, assertIsStubError); + var deferredPair = callbackPair(null, function(e) { + assertEquals('callback result should be cancelled', + 'CancellationError: StubError', + e.toString()); + }); + + var d = new webdriver.promise.Deferred(); + d.then(deferredPair.callback, deferredPair.errback); + + webdriver.promise.fulfilled(). + then(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return d.promise; + }). + then(chainPair.callback, chainPair.errback); + + return waitForIdle().then(function() { + assertFlowHistory('boom'); + chainPair.assertErrback('chain errback not invoked'); + deferredPair.assertErrback('deferred errback not invoked'); + }); +} + +function testCancelsPromiseReturnedByCallbackIfFrameFails_taskCallback() { + var chainPair = callbackPair(null, assertIsStubError); + var deferredPair = callbackPair(null, function(e) { + assertEquals('callback result should be cancelled', + 'CancellationError: StubError', + e.toString()); + }); + + var d = new webdriver.promise.Deferred(); + d.then(deferredPair.callback, deferredPair.errback); + + schedule('a'). + then(function() { + scheduleAction('boom', throwStubError); + schedule('this should not run'); + return d.promise; + }). + then(chainPair.callback, chainPair.errback); + + return waitForIdle().then(function() { + assertFlowHistory('a', 'boom'); + chainPair.assertErrback('chain errback not invoked'); + deferredPair.assertErrback('deferred errback not invoked'); + }); +} + +function testMaintainsOrderInCallbacksWhenATaskReturnsAPromise() { + schedule('__start__', webdriver.promise.fulfilled()). + then(function() { + webdriver.test.testutil.messages.push('a'); + schedulePush('b'); + webdriver.test.testutil.messages.push('c'); + }). + then(function() { + webdriver.test.testutil.messages.push('d'); + }); + schedulePush('e'); + + return waitForIdle().then(function() { + assertFlowHistory('__start__', 'b', 'e'); + webdriver.test.testutil.assertMessages('a', 'c', 'b', 'd', 'e'); + }); +} + + +function assertFlowIs(flow) { + assertEquals(flow, webdriver.promise.controlFlow()); +} + +function testOwningFlowIsActivatedForExecutingTasks() { + var defaultFlow = webdriver.promise.controlFlow(); + var order = []; + + webdriver.promise.createFlow(function(flow) { + assertFlowIs(flow); + order.push(0); + + defaultFlow.execute(function() { + assertFlowIs(defaultFlow); + order.push(1); + }); + }); + + return waitForIdle().then(function() { + assertFlowIs(defaultFlow); + assertArrayEquals([0, 1], order); + }); +} + +function testCreateFlowReturnsPromisePairedWithCreatedFlow() { + return new goog.Promise(function(fulfill, reject) { + var newFlow; + webdriver.promise.createFlow(function(flow) { + newFlow = flow; + assertFlowIs(newFlow); + }).then(function() { + assertFlowIs(newFlow); + waitForIdle(newFlow).then(fulfill, reject); + }); + }); +} + +function testDeferredFactoriesCreateForActiveFlow_defaultFlow() { + var e = Error(); + var defaultFlow = webdriver.promise.controlFlow(); + webdriver.promise.fulfilled().then(function() { + assertFlowIs(defaultFlow); + }); + webdriver.promise.rejected(e).then(null, function(err) { + assertEquals(e, err); + assertFlowIs(defaultFlow); + }); + webdriver.promise.defer().then(function() { + assertFlowIs(defaultFlow); + }); + + return waitForIdle(); +} + + +function testDeferredFactoriesCreateForActiveFlow_newFlow() { + var e = Error(); + var newFlow = new webdriver.promise.ControlFlow; + newFlow.execute(function() { + webdriver.promise.fulfilled().then(function() { + assertFlowIs(newFlow); + }); + + webdriver.promise.rejected(e).then(null, function(err) { + assertEquals(e, err); + assertFlowIs(newFlow); + }); + + webdriver.promise.defer().then(function() { + assertFlowIs(newFlow); + }); + }).then(function() { + assertFlowIs(newFlow); + }); + + return waitForIdle(newFlow); +} + +function testFlowsSynchronizeWithThemselvesNotEachOther() { + var defaultFlow = webdriver.promise.controlFlow(); + schedulePush('a', 'a'); + webdriver.promise.controlFlow().timeout(250); + schedulePush('b', 'b'); + + webdriver.promise.createFlow(function() { + schedulePush('c', 'c'); + schedulePush('d', 'd'); + }); + + return waitForIdle().then(function() { + webdriver.test.testutil.assertMessages('a', 'c', 'd', 'b'); + }); +} + +function testUnhandledErrorsAreReportedToTheOwningFlow() { + var error1 = Error('e1'); + var error2 = Error('e2'); + + var defaultFlow = webdriver.promise.controlFlow(); + defaultFlow.removeAllListeners('uncaughtException'); + + var flow1Error = goog.Promise.withResolver(); + flow1Error.promise.then(function(value) { + assertEquals(error2, value); + }); + + var flow2Error = goog.Promise.withResolver(); + flow2Error.promise.then(function(value) { + assertEquals(error1, value); + }); + + webdriver.promise.createFlow(function(flow) { + flow.once('uncaughtException', flow2Error.resolve); + webdriver.promise.rejected(error1); + + defaultFlow.once('uncaughtException', flow1Error.resolve); + defaultFlow.execute(function() { + webdriver.promise.rejected(error2); + }); + }); + + return goog.Promise.all([flow1Error.promise, flow2Error.promise]); +} + +function testCanSynchronizeFlowsByReturningPromiseFromOneToAnother() { + var flow1 = new webdriver.promise.ControlFlow; + var flow1Done = goog.Promise.withResolver(); + flow1.once('idle', flow1Done.resolve); + flow1.once('uncaughtException', flow1Done.reject); + + var flow2 = new webdriver.promise.ControlFlow; + var flow2Done = goog.Promise.withResolver(); + flow2.once('idle', flow2Done.resolve); + flow2.once('uncaughtException', flow2Done.reject); + + flow1.execute(function() { + schedulePush('a', 'a'); + return webdriver.promise.delayed(25); + }, 'start flow 1'); + + flow2.execute(function() { + schedulePush('b', 'b'); + schedulePush('c', 'c'); + flow2.execute(function() { + return flow1.execute(function() { + schedulePush('d', 'd'); + }, 'flow 1 task'); + }, 'inject flow1 result into flow2'); + schedulePush('e', 'e'); + }, 'start flow 2'); + + return goog.Promise.all([flow1Done.promise, flow2Done.promise]). + then(function() { + webdriver.test.testutil.assertMessages('a', 'b', 'c', 'd', 'e'); + }); +} + +function testFramesWaitToCompleteForPendingRejections() { + return new goog.Promise(function(fulfill, reject) { + + webdriver.promise.controlFlow().execute(function() { + webdriver.promise.rejected(new StubError); + }).then(fulfill, reject); + + }). + then(goog.partial(fail, 'expected to fail'), assertIsStubError). + then(waitForIdle); +} + +function testSynchronizeErrorsPropagateToOuterFlow() { + var outerFlow = new webdriver.promise.ControlFlow; + var innerFlow = new webdriver.promise.ControlFlow; + + var block = goog.Promise.withResolver(); + innerFlow.execute(function() { + return block.promise; + }, 'block inner flow'); + + outerFlow.execute(function() { + block.resolve(); + return innerFlow.execute(function() { + webdriver.promise.rejected(new StubError); + }, 'trigger unhandled rejection error'); + }, 'run test'); + + return goog.Promise.all([ + waitForIdle(innerFlow), + waitForAbort(outerFlow).then(assertIsStubError) + ]); +} + +function testFailsIfErrbackThrows() { + webdriver.promise.rejected('').then(null, throwStubError); + return waitForAbort().then(assertIsStubError); +} + +function testFailsIfCallbackReturnsRejectedPromise() { + webdriver.promise.fulfilled().then(function() { + return webdriver.promise.rejected(new StubError); + }); + return waitForAbort().then(assertIsStubError); +} + +function testAbortsFrameIfTaskFails() { + webdriver.promise.fulfilled().then(function() { + webdriver.promise.controlFlow().execute(throwStubError); + }); + return waitForAbort().then(assertIsStubError); +} + +function testAbortsFramePromisedChainedFromTaskIsNotHandled() { + webdriver.promise.fulfilled().then(function() { + webdriver.promise.controlFlow().execute(goog.nullFunction). + then(throwStubError); + }); + return waitForAbort().then(assertIsStubError); +} + +function testTrapsChainedUnhandledRejectionsWithinAFrame() { + var pair = callbackPair(null, assertIsStubError); + webdriver.promise.fulfilled().then(function() { + webdriver.promise.controlFlow().execute(goog.nullFunction). + then(throwStubError); + }).then(pair.callback, pair.errback); + + return waitForIdle().then(pair.assertErrback); +} + + +function testCancelsRemainingTasksIfFrameThrowsDuringScheduling() { + var task1, task2; + var pair = callbackPair(null, assertIsStubError); + var flow = webdriver.promise.controlFlow(); + flow.execute(function() { + task1 = flow.execute(goog.nullFunction); + task2 = flow.execute(goog.nullFunction); + throw new StubError; + }).then(pair.callback, pair.errback); + + return waitForIdle(). + then(pair.assertErrback). + then(function() { + assertFalse(task1.isPending()); + pair = callbackPair(); + return task1.then(pair.callback, pair.errback); + }). + then(function() { + pair.assertErrback(); + assertFalse(task2.isPending()); + pair = callbackPair(); + return task2.then(pair.callback, pair.errback); + }). + then(function() { + pair.assertErrback(); + }); +} + +function testCancelsRemainingTasksInFrameIfATaskFails() { + var task; + var pair = callbackPair(null, assertIsStubError); + var flow = webdriver.promise.controlFlow(); + flow.execute(function() { + flow.execute(throwStubError); + task = flow.execute(goog.nullFunction); + }).then(pair.callback, pair.errback); + + return waitForIdle().then(pair.assertErrback).then(function() { + assertFalse(task.isPending()); + pair = callbackPair(); + task.then(pair.callback, pair.errback); + }).then(function() { + pair.assertErrback(); + }); +} + +function testDoesNotModifyRejectionErrorIfPromiseNotInsideAFlow() { + var error = Error('original message'); + var originalStack = error.stack; + var originalStr = error.toString(); + + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + assertEquals('original message', e.message); + assertEquals(originalStack, e.stack); + assertEquals(originalStr, e.toString()); + }); + + webdriver.promise.rejected(error).then(pair.callback, pair.errback); + return waitForIdle().then(pair.assertErrback); +} + + +/** See https://github.com/SeleniumHQ/selenium/issues/444 */ +function testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_1() { + var messages = []; + flow.execute(function() { + return webdriver.promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + webdriver.promise.fulfilled(step) + .then(function() { + messages.push(step + '.1'); + }).then(function() { + messages.push(step + '.2'); + }); + }) + }); + return waitForIdle().then(function() { + assertArrayEquals( + ['a.1', 'b.1', 'c.1', 'd.1', 'a.2', 'b.2', 'c.2', 'd.2'], + messages); + }); +} + + +/** See https://github.com/SeleniumHQ/selenium/issues/444 */ +function testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_2() { + var messages = []; + flow.execute(function() { + return webdriver.promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + webdriver.promise.fulfilled(step) + .then(function() { + messages.push(step + '.1'); + }).then(function() { + flow.execute(function() {}, step + '.2').then(function(text) { + messages.push(step + '.2'); + }); + }); + }) + }); + return waitForIdle().then(function() { + assertArrayEquals( + ['a.1', 'b.1', 'c.1', 'd.1', 'a.2', 'b.2', 'c.2', 'd.2'], + messages); + }); +} + + +/** See https://github.com/SeleniumHQ/selenium/issues/444 */ +function testMaintainsOrderWithPromiseChainsCreatedWithinAForeach_3() { + var messages = []; + flow.execute(function() { + return webdriver.promise.fulfilled(['a', 'b', 'c', 'd']); + }, 'start').then(function(steps) { + steps.forEach(function(step) { + webdriver.promise.fulfilled(step) + .then(function(){}) + .then(function() { + messages.push(step + '.1'); + return flow.execute(function() {}, step + '.1'); + }).then(function() { + flow.execute(function() {}, step + '.2').then(function(text) { + messages.push(step + '.2'); + }); + }); + }) + }); + return waitForIdle().then(function() { + assertArrayEquals( + ['a.1', 'b.1', 'c.1', 'd.1', 'a.2', 'b.2', 'c.2', 'd.2'], + messages); + }); +} diff --git a/lib/webdriver/test/promise_generator_test.js b/lib/webdriver/test/promise_generator_test.js new file mode 100644 index 0000000..b98ccd5 --- /dev/null +++ b/lib/webdriver/test/promise_generator_test.js @@ -0,0 +1,251 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.test.promise.generator.test'); +goog.setTestOnly('webdriver.test.promise.generator.test'); + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.promise'); + + +function testRequiresInputsToBeGeneratorFunctions() { + var thrown = assertThrows(function() { + webdriver.promise.consume(function() {}); + }); + assertTrue(thrown instanceof TypeError); +} + + +function testBasicGenerator() { + var values = []; + return webdriver.promise.consume(function* () { + var i = 0; + while (i < 4) { + i = yield i + 1; + values.push(i); + } + }).then(function() { + assertArrayEquals([1, 2, 3, 4], values); + }); +} + + +function testPromiseYieldingGenerator() { + var values = []; + return webdriver.promise.consume(function* () { + var i = 0; + while (i < 4) { + // Test that things are actually async here. + setTimeout(function() { + values.push(i * 2); + }, 10); + + yield webdriver.promise.delayed(10).then(function() { + values.push(i++); + }); + } + }).then(function() { + assertArrayEquals([0, 0, 2, 1, 4, 2, 6, 3], values); + }); +} + + +function testAssignmentsToYieldedPromisesGetFulfilledValue() { + return webdriver.promise.consume(function* () { + var p = webdriver.promise.fulfilled(2); + var x = yield p; + assertEquals(2, x); + }); +} + + +function testCanCancelPromiseGenerator() { + var values = []; + var p = webdriver.promise.consume(function* () { + var i = 0; + while (i < 3) { + yield webdriver.promise.delayed(100).then(function() { + values.push(i++); + }); + } + }); + return webdriver.promise.delayed(75).then(function() { + p.cancel(); + return p.thenCatch(function() { + return webdriver.promise.delayed(300); + }); + }).then(function() { + assertArrayEquals([0], values); + }); +} + + +function testFinalReturnValueIsUsedAsFulfillmentValue() { + return webdriver.promise.consume(function* () { + yield 1; + yield 2; + return 3; + }).then(function(value) { + assertEquals(3, value); + }); +} + + +function testRejectionsAreThrownWithinGenerator() { + var values = []; + return webdriver.promise.consume(function* () { + values.push('a'); + var e = Error('stub error'); + try { + yield webdriver.promise.rejected(e); + values.push('b'); + } catch (ex) { + assertEquals(e, ex); + values.push('c'); + } + values.push('d'); + }).then(function() { + assertArrayEquals(['a', 'c', 'd'], values); + }); +} + + +function testUnhandledRejectionsAbortGenerator() { + var values = []; + var e = Error('stub error'); + return webdriver.promise.consume(function* () { + values.push(1); + yield webdriver.promise.rejected(e); + values.push(2); + }).thenCatch(function() { + assertArrayEquals([1], values); + }); +} + + +function testYieldsWaitForPromises() { + var values = []; + var d = webdriver.promise.defer(); + + setTimeout(function() { + assertArrayEquals([1], values); + d.fulfill(2); + }, 100); + + return webdriver.promise.consume(function* () { + values.push(1); + values.push((yield d.promise), 3); + }).then(function() { + assertArrayEquals([1, 2, 3], values); + }); +} + + +function testCanSpecifyGeneratorScope() { + return webdriver.promise.consume(function* () { + return this.name; + }, {name: 'Bob'}).then(function(value) { + assertEquals('Bob', value); + }); +} + + +function testCanSpecifyGeneratorArgs() { + return webdriver.promise.consume(function* (a, b) { + assertEquals('red', a); + assertEquals('apples', b); + }, null, 'red', 'apples'); +} + + +function testExecuteGeneratorInAFlow() { + var promises = [ + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + var values = []; + + setTimeout(function() { + assertArrayEquals([], values); + promises[0].fulfill(1); + }, 100); + + setTimeout(function() { + assertArrayEquals([1], values); + promises[1].fulfill(2); + }, 200); + + return webdriver.promise.controlFlow().execute(function* () { + values.push(yield promises[0].promise); + values.push(yield promises[1].promise); + values.push('fin'); + }).then(function() { + assertArrayEquals([1, 2, 'fin'], values); + }); +} + + +function testNestedGeneratorsInAFlow() { + var flow = webdriver.promise.controlFlow(); + return flow.execute(function* () { + var x = yield flow.execute(function() { + return webdriver.promise.delayed(10).then(function() { + return 1; + }); + }); + + var y = yield flow.execute(function() { + return 2; + }); + + return x + y; + }).then(function(value) { + assertEquals(3, value); + }); +} + + +function testFlowWaitOnGenerator() { + var values = []; + return webdriver.promise.controlFlow().wait(function* () { + yield values.push(1); + values.push(yield webdriver.promise.delayed(10).then(function() { + return 2; + })); + yield values.push(3); + return values.length === 6; + }, 250).then(function() { + assertArrayEquals([1, 2, 3, 1, 2, 3], values); + }); +} + + +function testFlowWaitingOnGeneratorTimesOut() { + var values = []; + return webdriver.promise.controlFlow().wait(function* () { + var i = 0; + while (i < 3) { + yield webdriver.promise.delayed(100).then(function() { + values.push(i++); + }); + } + }, 75).thenCatch(function() { + assertArrayEquals('Should complete one loop of wait condition', + [0, 1, 2], values); + }); +} + diff --git a/lib/webdriver/test/promise_test.js b/lib/webdriver/test/promise_test.js new file mode 100644 index 0000000..a7e2f1f --- /dev/null +++ b/lib/webdriver/test/promise_test.js @@ -0,0 +1,1981 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.MockClock'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.promise'); +goog.require('webdriver.stacktrace'); +goog.require('webdriver.test.testutil'); + + +// Aliases for readability. +var assertIsPromise = webdriver.test.testutil.assertIsPromise, + assertNotPromise = webdriver.test.testutil.assertNotPromise, + callbackHelper = webdriver.test.testutil.callbackHelper, + callbackPair = webdriver.test.testutil.callbackPair, + assertIsStubError = webdriver.test.testutil.assertIsStubError, + throwStubError = webdriver.test.testutil.throwStubError, + StubError = webdriver.test.testutil.StubError; + +var app, clock, uncaughtExceptions; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + webdriver.promise.LONG_STACK_TRACES = false; + clock = new goog.testing.MockClock(true); + uncaughtExceptions = []; + + app = webdriver.promise.controlFlow(); + app.on(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + goog.bind(uncaughtExceptions.push, uncaughtExceptions)); +} + + +function tearDown() { + clock.tick(Infinity); + clock.dispose(); + app.reset(); + webdriver.promise.setDefaultFlow(new webdriver.promise.ControlFlow); + assertArrayEquals( + 'Did not expect any uncaught exceptions', [], uncaughtExceptions); + webdriver.promise.LONG_STACK_TRACES = false; +} + + +function createRejectedPromise(reason) { + var p = webdriver.promise.rejected(reason); + p.thenCatch(goog.nullFunction); + return p; +} + + +function testCanDetectPromiseLikeObjects() { + assertIsPromise(new webdriver.promise.Promise(function(fulfill) { + fulfill(); + })); + assertIsPromise(new webdriver.promise.Deferred()); + assertIsPromise(new webdriver.promise.Deferred().promise); + assertIsPromise({then:function() {}}); + + assertNotPromise(undefined); + assertNotPromise(null); + assertNotPromise(''); + assertNotPromise(true); + assertNotPromise(false); + assertNotPromise(1); + assertNotPromise({}); + assertNotPromise({then:1}); + assertNotPromise({then:true}); + assertNotPromise({then:''}); +} + + +function testSimpleResolveScenario() { + var callback = callbackHelper(function(value) { + assertEquals(123, value); + }); + + var deferred = webdriver.promise.defer(); + deferred.promise.then(callback); + + callback.assertNotCalled(); + deferred.fulfill(123); + clock.tick(); + callback.assertCalled(); +} + + +function testRegisteringACallbackPostResolution() { + var callback, deferred = new webdriver.promise.Deferred(); + + deferred.then((callback = callbackHelper(function(value) { + assertEquals(123, value); + }))); + deferred.fulfill(123); + clock.tick(); + callback.assertCalled(); + + deferred.then((callback = callbackHelper(function(value) { + assertEquals(123, value); + }))); + callback.assertNotCalled(); + clock.tick(); + callback.assertCalled(); +} + + +function testRegisterACallbackViaDeferredPromise() { + var callback, deferred = new webdriver.promise.Deferred(); + + deferred.promise.then((callback = callbackHelper(function(value) { + assertEquals(123, value); + }))); + deferred.fulfill(123); + clock.tick(); + callback.assertCalled(); + + deferred.promise.then((callback = callbackHelper(function(value) { + assertEquals(123, value); + }))); + clock.tick(); + callback.assertCalled(); +} + + +function testTwoStepResolvedChain() { + var callback, start = new webdriver.promise.Deferred(); + + var next = start.then((callback = callbackHelper(function(value) { + assertEquals(123, value); + return value + 1; + }))); + + assertIsPromise(next); + + callback.assertNotCalled(); + start.fulfill(123); + clock.tick(); + callback.assertCalled(); + + next.then((callback = callbackHelper(function(value) { + assertEquals(124, value); + }))); + clock.tick(); + callback.assertCalled(); +} + + +function testCanResolveOnlyOnce_resolved() { + var deferred = new webdriver.promise.Deferred(); + deferred.fulfill(1); + deferred.fulfill(2); + deferred.reject(3); + + var callback; + deferred.then(callback = callbackHelper(function(value) { + assertEquals(1, value); + })); + clock.tick(); + callback.assertCalled(); +} + + +function testCanResolveOnlyOnce_rejected() { + var deferred = new webdriver.promise.Deferred(); + deferred.reject(new StubError); + deferred.fulfill(1); + deferred.reject(2); + + var callback; + deferred.then(null, callback = callbackHelper(assertIsStubError)); + clock.tick(); + callback.assertCalled(); +} + + +function testIfFulfilledWithOtherPromiseCannotChangeValueWhileWaiting() { + var deferred = webdriver.promise.defer(); + var other = webdriver.promise.defer(); + + deferred.fulfill(other.promise); + deferred.fulfill('different value'); + + var callback = callbackHelper(function(value) { + assertEquals(123, value); + }); + + deferred.then(callback); + callback.assertNotCalled(); + + other.fulfill(123); + clock.tick(); + callback.assertCalled(); +} + + +function testOnlyGoesDownListenerPath_resolved() { + var callback = callbackHelper(); + var errback = callbackHelper(); + + webdriver.promise.fulfilled().then(callback, errback); + clock.tick(); + callback.assertCalled(); + errback.assertNotCalled(); +} + + +function testOnlyGoesDownListenerPath_rejected() { + var callback = callbackHelper(); + var errback = callbackHelper(); + + webdriver.promise.rejected().then(callback, errback); + clock.tick(); + callback.assertNotCalled(); + errback.assertCalled(); +} + + +function testCatchingAndSuppressingRejectionErrors() { + var errback = callbackHelper(assertIsStubError); + var callback = callbackHelper(function() { + assertUndefined(arguments[0]); + }); + + webdriver.promise.rejected(new StubError). + thenCatch(errback). + then(callback); + clock.tick(); + errback.assertCalled(); + callback.assertCalled(); +} + + +function testThrowingNewRejectionErrors() { + var errback1 = callbackHelper(assertIsStubError); + var error2 = Error('hi'); + var errback2 = callbackHelper(function(error) { + assertEquals(error2, error); + }); + + webdriver.promise.rejected(new StubError). + thenCatch(function(error) { + errback1(error); + throw error2; + }). + thenCatch(errback2); + clock.tick(); + errback1.assertCalled(); + errback2.assertCalled(); +} + + +function testThenFinally_nonFailingCallbackDoesNotSuppressOriginalError() { + var done = callbackHelper(assertIsStubError); + webdriver.promise.rejected(new StubError). + thenFinally(goog.nullFunction). + thenCatch(done); + clock.tick(); + done.assertCalled(); +} + + +function testThenFinally_failingCallbackSuppressesOriginalError() { + var done = callbackHelper(assertIsStubError); + webdriver.promise.rejected(new Error('original')). + thenFinally(throwStubError). + thenCatch(done); + clock.tick(); + done.assertCalled(); +} + + +function testThenFinally_callbackThrowsAfterFulfilledPromise() { + var done = callbackHelper(assertIsStubError); + webdriver.promise.fulfilled(). + thenFinally(throwStubError). + thenCatch(done); + clock.tick(); + done.assertCalled(); +} + + +function testThenFinally_callbackReturnsRejectedPromise() { + var done = callbackHelper(assertIsStubError); + webdriver.promise.fulfilled(). + thenFinally(function() { + return webdriver.promise.rejected(new StubError); + }). + thenCatch(done); + clock.tick(); + done.assertCalled(); +} + + +function testChainingThen_AllResolved() { + var callbacks = [ + callbackHelper(function(value) { + assertEquals(128, value); + return value * 2; + }), + callbackHelper(function(value) { + assertEquals(256, value); + return value * 2; + }), + callbackHelper(function(value) { + assertEquals(512, value); + }) + ]; + + var deferred = new webdriver.promise.Deferred(); + deferred. + then(callbacks[0]). + then(callbacks[1]). + then(callbacks[2]); + + callbacks[0].assertNotCalled(); + callbacks[1].assertNotCalled(); + callbacks[2].assertNotCalled(); + + deferred.fulfill(128); + + clock.tick(); + callbacks[0].assertCalled(); + callbacks[1].assertCalled(); + callbacks[2].assertCalled(); +} + + +function testWhen_ReturnsAResolvedPromiseIfGivenANonPromiseValue() { + var ret = webdriver.promise.when('abc'); + assertIsPromise(ret); + + var callback; + ret.then(callback = callbackHelper(function (value) { + assertEquals('abc', value); + })); + clock.tick(); + callback.assertCalled(); +} + + +function testWhen_PassesRawErrorsToCallbacks() { + var error = new Error('boo!'), callback; + webdriver.promise.when(error, callback = callbackHelper(function(value) { + assertEquals(error, value); + })); + clock.tick(); + callback.assertCalled(); +} + + +function testWhen_WaitsForValueToBeResolvedBeforeInvokingCallback() { + var d = new webdriver.promise.Deferred(), callback; + webdriver.promise.when(d, callback = callbackHelper(function(value) { + assertEquals('hi', value); + })); + callback.assertNotCalled(); + d.fulfill('hi'); + clock.tick(); + callback.assertCalled(); +} + + +function testWhen_canCancelReturnedPromise() { + var callbacks = callbackPair(null, function(e) { + assertTrue(e instanceof webdriver.promise.CancellationError); + assertEquals('just because', e.message); + }); + + var promiseLike = { + then: function(cb, eb) { + this.callback = cb; + this.errback = eb; + } + }; + + var promise = webdriver.promise.when(promiseLike, + callbacks.callback, callbacks.errback); + + assertTrue(promise.isPending()); + promise.cancel('just because'); + clock.tick(); + callbacks.assertErrback(); + + // The following should have no effect. + promiseLike.callback(); + promiseLike.errback(); +} + + +function testFiresUncaughtExceptionEventIfRejectionNeverHandled() { + webdriver.promise.rejected(new StubError); + var handler = callbackHelper(assertIsStubError); + + // so tearDown() doesn't throw + app.reset(); + app.on(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); + clock.tick(); + handler.assertCalled(); +} + + +function testWaitsIfCallbackReturnsAPromiseObject() { + var callback1, callback2; + var callback1Return = new webdriver.promise.Deferred(); + + webdriver.promise.fulfilled('hi'). + then(callback1 = callbackHelper(function(value) { + assertEquals('hi', value); + return callback1Return; + })). + then(callback2 = callbackHelper(function(value) { + assertEquals('bye', value); + })); + + clock.tick(); + callback1.assertCalled(); + callback2.assertNotCalled(); + callback1Return.fulfill('bye'); + clock.tick(); + callback2.assertCalled(); +} + + +function testWaitsIfCallbackReturnsAPromiseLikeObject() { + var callback1, callback2; + var callback1Return = { + then: function(callback) { + this.callback = callback; + }, + fulfill: function(value) { + this.callback(value); + } + }; + + webdriver.promise.fulfilled('hi'). + then(callback1 = callbackHelper(function(value) { + assertEquals('hi', value); + return callback1Return; + })). + then(callback2 = callbackHelper(function(value) { + assertEquals('bye', value); + })); + + clock.tick(); + callback1.assertCalled(); + callback2.assertNotCalled(); + callback1Return.fulfill('bye'); + clock.tick(); + callback2.assertCalled(); +} + + +function testResolvingAPromiseWithAnotherPromiseCreatesAChain_ourPromise() { + var d1 = new webdriver.promise.Deferred(); + var d2 = new webdriver.promise.Deferred(); + var callback1, callback2; + + d1.then(callback1 = callbackHelper(function(value) { + assertEquals(4, value); + })); + + var d2promise = d2.then(callback2 = callbackHelper(function(value) { + assertEquals(2, value); + return value * 2; + })); + + callback1.assertNotCalled(); + callback2.assertNotCalled(); + + d2.fulfill(2); + clock.tick(); + callback1.assertNotCalled(); + callback2.assertCalled(); + + d1.fulfill(d2promise); + clock.tick(); + callback1.assertCalled(); + callback2.assertCalled(); +} + + +function testResolvingAPromiseWithAnotherPromiseCreatesAChain_otherPromise() { + var d = new webdriver.promise.Deferred(), callback; + d.then(callback = callbackHelper(function(value) { + assertEquals(4, value); + })); + + var otherPromise = { + then: function(callback) { + this.callback = callback; + }, + fulfill: function(value) { + this.callback(value); + } + }; + + callback.assertNotCalled(); + d.fulfill(otherPromise); + otherPromise.fulfill(4); + clock.tick(); + callback.assertCalled(); +} + + +function testRejectForcesValueToAnError_errorInstance() { + var d = webdriver.promise.defer(); + var callback = callbackHelper(assertIsStubError); + + d.thenCatch(callback); + d.reject(new StubError); + clock.tick(); + callback.assertCalled(); +} + + +function testRejectForcesValueToAnError_errorSubTypeInstance() { + var d = webdriver.promise.defer(); + var e = new TypeError('hi'); + var callback = callbackHelper(function(actual) { + assertEquals(e, actual); + }); + + d.thenCatch(callback); + d.reject(e); + clock.tick(); + callback.assertCalled(); +} + + +function testRejectForcesValueToAnError_customErrorInstance() { + var d = webdriver.promise.defer(); + var e = new goog.debug.Error('hi there'); + var callback = callbackHelper(function(actual) { + assertEquals(e, actual); + }); + + d.thenCatch(callback); + d.reject(e); + clock.tick(); + callback.assertCalled(); +} + + +function testRejectForcesValueToAnError_errorLike() { + var d = webdriver.promise.defer(); + var e = {message: 'yolo'}; + var callback = callbackHelper(function(actual) { + assertEquals(e, actual); + }); + + d.thenCatch(callback); + d.reject(e); + clock.tick(); + callback.assertCalled(); +} + + +function testRejectingAPromiseWithAnotherPromiseCreatesAChain_ourPromise() { + var d1 = new webdriver.promise.Deferred(); + var d2 = new webdriver.promise.Deferred(); + var pair1 = callbackPair(assertIsStubError, null); + var pair2 = callbackPair(function(value) { + assertEquals(2, value); + return new StubError; + }); + + d1.then(pair1.callback, pair1.errback); + var d2promise = d2.then(pair2.callback, pair2.errback); + + pair1.assertNeither(); + pair2.assertNeither(); + + d2.fulfill(2); + clock.tick(); + pair1.assertNeither(); + pair2.assertCallback(); + + d1.reject(d2promise); + clock.tick(); + pair1.assertCallback(); +} + + +function testRejectingAPromiseWithAnotherPromiseCreatesAChain_otherPromise() { + var d = new webdriver.promise.Deferred(), callback, errback; + d.then(callback = callbackHelper(assertIsStubError), + errback = callbackHelper()); + + var otherPromise = { + then: function(callback) { + this.callback = callback; + }, + fulfill: function(value) { + this.callback(value); + } + }; + + d.reject(otherPromise); + clock.tick(); + callback.assertNotCalled(); + errback.assertNotCalled(); + + otherPromise.fulfill(new StubError); + clock.tick(); + callback.assertCalled(); + errback.assertNotCalled(); +} + + +function testResolvingADeferredWithAnotherCopiesTheResolvedValue() { + var d1 = new webdriver.promise.Deferred(); + var d2 = new webdriver.promise.Deferred(); + var callback1, callback2; + + d1.then(callback1 = callbackHelper(function(value) { + assertEquals(2, value); + })); + + d2.then(callback2 = callbackHelper(function(value) { + assertEquals(2, value); + return 4; + })); + + d1.fulfill(d2); + clock.tick(); + callback1.assertNotCalled(); + callback2.assertNotCalled(); + + d2.fulfill(2); + clock.tick(); + callback1.assertCalled(); + callback2.assertCalled(); +} + + +function testCannotResolveAPromiseWithItself() { + assertThrows(function() { + var f, p = new webdriver.promise.Promise(function(fulfill) { + f = fulfill; + }); + f(p); + }); + + assertThrows(function() { + var r, p = new webdriver.promise.Promise(function(_, reject) { + r = reject; + }); + r(p); + }); +} + + +function testCannotResolveADeferredWithItself() { + var deferred = new webdriver.promise.Deferred(); + assertThrows(goog.bind(deferred.fulfill, deferred, deferred)); + assertThrows(goog.bind(deferred.reject, deferred, deferred)); +} + + +function testSkipsNullPointsInPromiseChain_callbacks() { + var errback1, errback2, callback; + webdriver.promise.fulfilled('hi'). + thenCatch(errback1 = callbackHelper()). + thenCatch(errback2 = callbackHelper()). + then(callback = callbackHelper(function(value) { + assertEquals('hi', value); + })); + + clock.tick(); + errback1.assertNotCalled(); + errback2.assertNotCalled(); + callback.assertCalled(); +} + + +function testSkipsNullPointsInPromiseChain_errbacks() { + var errback1, errback2, callback; + webdriver.promise.fulfilled('hi'). + thenCatch(errback1 = callbackHelper()). + thenCatch(errback2 = callbackHelper()). + then(callback = callbackHelper(function(value) { + assertEquals('hi', value); + })); + + clock.tick(); + errback1.assertNotCalled(); + errback2.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_primitives() { + function runTest(value) { + var callback, errback; + webdriver.promise.fullyResolved(value).then( + callback = callbackHelper(function(resolved) { + assertEquals(value, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled( + 'Did not expect errback to be called for: ' + value); + callback.assertCalled('Expected callback to be called for: ' + value); + } + + runTest(true); + runTest(goog.nullFunction); + runTest(null); + runTest(123); + runTest('foo bar'); + runTest(undefined); +} + + +function testFullyResolved_arrayOfPrimitives() { + var array = [true, goog.nullFunction, null, 123, '', undefined, 1]; + var callbacks = callbackPair(function(resolved) { + assertEquals(array, resolved); + assertArrayEquals([true, goog.nullFunction, null, 123, '', undefined, 1], + resolved); + }); + + webdriver.promise.fullyResolved(array).then( + callbacks.callback, callbacks.errback); + + clock.tick(); + callbacks.assertCallback(); +} + +function testFullyResolved_nestedArrayOfPrimitives() { + var array = [true, [goog.nullFunction, null, 123], '', undefined]; + var callback, errback; + webdriver.promise.fullyResolved(array).then( + callback = callbackHelper(function(resolved) { + assertEquals(array, resolved); + assertArrayEquals([true, [goog.nullFunction, null, 123], '', undefined], + resolved); + assertArrayEquals([goog.nullFunction, null, 123], resolved[1]); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_arrayWithPromisedPrimitive() { + var callback, errback; + webdriver.promise.fullyResolved([webdriver.promise.fulfilled(123)]).then( + callback = callbackHelper(function(resolved) { + assertArrayEquals([123], resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToPrimitive() { + var promise = webdriver.promise.fulfilled(123); + var callback, errback; + webdriver.promise.fullyResolved(promise).then( + callback = callbackHelper(function(resolved) { + assertEquals(123, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToArray() { + var array = [true, [goog.nullFunction, null, 123], '', undefined]; + var promise = webdriver.promise.fulfilled(array); + var callback, errback; + + var result = webdriver.promise.fullyResolved(promise); + result.then( + callback = callbackHelper(function(resolved) { + assertEquals(array, resolved); + assertArrayEquals([true, [goog.nullFunction, null, 123], '', undefined], + resolved); + assertArrayEquals([goog.nullFunction, null, 123], resolved[1]); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToArrayWithPromises() { + var nestedPromise = webdriver.promise.fulfilled(123); + var promise = webdriver.promise.fulfilled([true, nestedPromise]); + + var callback, errback; + webdriver.promise.fullyResolved(promise).then( + callback = callbackHelper(function(resolved) { + assertArrayEquals([true, 123], resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_rejectsIfArrayPromiseRejects() { + var nestedPromise = createRejectedPromise(new StubError); + var promise = webdriver.promise.fulfilled([true, nestedPromise]); + + var pair = callbackPair(null, assertIsStubError); + webdriver.promise.fullyResolved(promise).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testFullyResolved_rejectsOnFirstArrayRejection() { + var e1 = new Error('foo'); + var e2 = new Error('bar'); + var promise = webdriver.promise.fulfilled([ + createRejectedPromise(e1), + createRejectedPromise(e2) + ]); + + var pair = callbackPair(null, function(error) { + assertEquals(e1, error); + }); + webdriver.promise.fullyResolved(promise).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testFullyResolved_rejectsIfNestedArrayPromiseRejects() { + var promise = webdriver.promise.fulfilled([ + webdriver.promise.fulfilled([ + createRejectedPromise(new StubError) + ]) + ]); + + var pair = callbackPair(null, assertIsStubError); + webdriver.promise.fullyResolved(promise).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testFullyResolved_simpleHash() { + var hash = {'a': 123}; + + var callback, errback; + webdriver.promise.fullyResolved(hash).then( + callback = callbackHelper(function(resolved) { + assertEquals(hash, resolved); + webdriver.test.testutil.assertObjectEquals({'a': 123}, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_nestedHash() { + var nestedHash = {'foo':'bar'}; + var hash = {'a': 123, 'b': nestedHash}; + + var callback, errback; + webdriver.promise.fullyResolved(hash).then( + callback = callbackHelper(function(resolved) { + assertEquals(hash, resolved); + webdriver.test.testutil.assertObjectEquals( + {'a': 123, 'b': {'foo': 'bar'}}, resolved); + webdriver.test.testutil.assertObjectEquals(nestedHash, resolved['b']); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToSimpleHash() { + var hash = {'a': 123}; + var promise = webdriver.promise.fulfilled(hash); + + var callback, errback; + webdriver.promise.fullyResolved(promise).then( + callback = callbackHelper(function(resolved) { + webdriver.test.testutil.assertObjectEquals(hash, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToNestedHash() { + var nestedHash = {'foo':'bar'}; + var hash = {'a': 123, 'b': nestedHash}; + var promise = webdriver.promise.fulfilled(hash); + + var callback, errback; + webdriver.promise.fullyResolved(promise).then( + callback = callbackHelper(function(resolved) { + webdriver.test.testutil.assertObjectEquals(hash, resolved); + webdriver.test.testutil.assertObjectEquals(nestedHash, resolved['b']); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_promiseResolvesToHashWithPromises() { + var promise = webdriver.promise.fulfilled({ + 'a': webdriver.promise.fulfilled(123) + }); + + var callback, errback; + webdriver.promise.fullyResolved(promise).then( + callback = callbackHelper(function(resolved) { + webdriver.test.testutil.assertObjectEquals({'a': 123}, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_rejectsIfHashPromiseRejects() { + var e = new Error('foo'); + var promise = webdriver.promise.fulfilled({ + 'a': createRejectedPromise(new StubError) + }); + + var pair = callbackPair(null, assertIsStubError); + webdriver.promise.fullyResolved(promise).then( + pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + +function testFullyResolved_rejectsIfNestedHashPromiseRejects() { + var e = new Error('foo'); + var promise = webdriver.promise.fulfilled({ + 'a': {'b': createRejectedPromise(new StubError)} + }); + + var pair = callbackPair(null, assertIsStubError); + webdriver.promise.fullyResolved(promise).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testFullyResolved_instantiatedObject() { + function Foo() { + this.bar = 'baz'; + } + var foo = new Foo; + + var callback, errback; + webdriver.promise.fullyResolved(foo).then( + callback = callbackHelper(function(resolvedFoo) { + assertEquals(foo, resolvedFoo); + assertTrue(resolvedFoo instanceof Foo); + webdriver.test.testutil.assertObjectEquals(new Foo, resolvedFoo); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_withEmptyArray() { + var callback, errback; + webdriver.promise.fullyResolved([]).then( + callback = callbackHelper(function(resolved) { + assertArrayEquals([], resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_withEmptyHash() { + var callback, errback; + webdriver.promise.fullyResolved({}).then( + callback = callbackHelper(function(resolved) { + webdriver.test.testutil.assertObjectEquals({}, resolved); + }), + errback = callbackHelper()); + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_arrayWithPromisedHash() { + var obj = {'foo': 'bar'}; + var promise = webdriver.promise.fulfilled(obj); + var array = [promise]; + + var callback, errback; + webdriver.promise.fullyResolved(array).then( + callback = callbackHelper(function(resolved) { + webdriver.test.testutil.assertObjectEquals(resolved, [obj]); + }), + errback = callbackHelper()); + + + clock.tick(); + errback.assertNotCalled(); + callback.assertCalled(); +} + + +function testFullyResolved_aDomElement() { + var e = document.createElement('div'); + var callbacks = callbackPair(function(resolved) { + assertEquals(e, resolved); + }); + + webdriver.promise.fullyResolved(e). + then(callbacks.callback, callbacks.errback); + + clock.tick(); + callbacks.assertCallback(); +} + + +function testCallbackChain_nonSplit() { + var stage1 = callbackPair(), + stage2 = callbackPair(), + stage3 = callbackPair(); + + webdriver.promise.rejected('foo'). + then(stage1.callback, stage1.errback). + then(stage2.callback, stage2.errback). + then(stage3.callback, stage3.errback); + + clock.tick(); + stage1.assertErrback('Wrong function for stage 1'); + stage2.assertCallback('Wrong function for stage 2'); + stage3.assertCallback('Wrong function for final stage'); +} + + +function testCallbackChain_split() { + var stage1 = callbackPair(), + stage2 = callbackPair(), + stage3 = callbackPair(); + + webdriver.promise.rejected('foo'). + then(stage1.callback). + thenCatch(stage1.errback). + then(stage2.callback). + thenCatch(stage2.errback). + then(stage3.callback, stage3.errback); + + clock.tick(); + stage1.assertErrback('Wrong function for stage 1'); + stage2.assertCallback('Wrong function for stage 2'); + stage3.assertCallback('Wrong function for final stage'); +} + + +function testCheckedNodeCall_functionThrows() { + var error = new Error('boom'); + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + }); + + webdriver.promise.checkedNodeCall(function() { + throw error; + }).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testCheckedNodeCall_functionReturnsAnError() { + var error = new Error('boom'); + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + }); + webdriver.promise.checkedNodeCall(function(callback) { + callback(error); + }).then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testCheckedNodeCall_functionReturnsSuccess() { + var success = 'success!'; + var pair = callbackPair(function(value) { + assertEquals(success, value); + }); + webdriver.promise.checkedNodeCall(function(callback) { + callback(null, success); + }).then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testCheckedNodeCall_functionReturnsAndThrows() { + var error = new Error('boom'); + var error2 = new Error('boom again'); + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + }); + webdriver.promise.checkedNodeCall(function(callback) { + callback(error); + throw error2; + }).then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testCheckedNodeCall_functionThrowsAndReturns() { + var error = new Error('boom'); + var error2 = new Error('boom again'); + var pair = callbackPair(null, function(e) { + assertEquals(error2, e); + }); + webdriver.promise.checkedNodeCall(function(callback) { + setTimeout(goog.partial(callback, error), 10); + throw error2; + }).then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); + pair.reset(); + clock.tick(Infinity); + pair.assertNeither(); +} + + +function testCancel_passesTheCancellationReasonToReject() { + var pair = callbackPair(null, function(e) { + assertTrue(e instanceof webdriver.promise.CancellationError); + assertEquals('because i said so', e.message); + }); + var d = new webdriver.promise.Deferred(); + d.then(pair.callback, pair.errback); + d.cancel('because i said so'); + clock.tick(); + pair.assertErrback(); +} + + +function testCancel_canCancelADeferredFromAChainedPromise() { + var pair1 = callbackPair(null, function(e) { + assertTrue(e instanceof webdriver.promise.CancellationError); + assertEquals('because i said so', e.message); + }); + var pair2 = callbackPair(); + + var d = new webdriver.promise.Deferred(); + var p = d.then(pair1.callback, pair1.errback); + p.then(pair2.callback, pair2.errback); + + p.cancel('because i said so'); + clock.tick(); + pair1.assertErrback('The first errback should have fired.'); + pair2.assertCallback(); +} + + +function testCancel_canCancelATimeout() { + var pair = callbackPair(null, function(e) { + assertTrue(e instanceof webdriver.promise.CancellationError); + }); + var p = webdriver.promise.delayed(250). + then(pair.callback, pair.errback); + p.cancel(); + clock.tick(); + pair.assertErrback(); + clock.tick(250); // Just to make sure nothing happens. + pair.assertErrback(); +} + + +function testCancel_cancelIsANoopOnceAPromiseHasBeenFulfilled() { + var p = webdriver.promise.fulfilled(123); + p.cancel(); + + var pair = callbackPair(goog.partial(assertEquals, 123)); + p.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testCancel_cancelIsANoopOnceAPromiseHasBeenRejected() { + var p = webdriver.promise.rejected(new StubError); + p.cancel(); + + var pair = callbackPair(null, assertIsStubError); + p.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testCancel_noopCancelTriggeredOnCallbackOfResolvedPromise() { + var d = webdriver.promise.defer(); + var p = d.then(); + + d.fulfill(); + p.cancel(); // This should not throw. +} + + +function testCallbackRegistersAnotherListener_callbacksConfiguredPreResolve() { + var messages = []; + var d = new webdriver.promise.Deferred(); + d.promise.then(function() { + messages.push('a'); + d.promise.then(function() { + messages.push('c'); + }); + }); + d.promise.then(function() { + messages.push('b'); + }); + d.fulfill(); + clock.tick(); + assertArrayEquals(['a', 'b', 'c'], messages); +} + + +function testCallbackRegistersAnotherListener_callbacksConfiguredPostResolve() { + var messages = []; + var p = webdriver.promise.fulfilled(); + p.then(function() { + messages.push('a'); + p.then(function() { + messages.push('c'); + }); + }); + p.then(function() { + messages.push('b'); + }); + clock.tick(); + assertArrayEquals(['a', 'b', 'c'], messages); +} + + +function testCallbackRegistersAnotherListener_recursive() { + var order = []; + var promise = webdriver.promise.fulfilled(); + + promise.then(function() { + push(); + promise.then(push); + }).then(function() { + push(); + }); + + assertArrayEquals([], order); + clock.tick(); + assertArrayEquals([0, 1, 2], order); + + function push() { + order.push(order.length); + } +} + + +function testCallbackRegistersAnotherListener_recursiveCallbacks_many() { + var messages = []; + var start = 97; // 'a' + + var p = webdriver.promise.fulfilled(); + p.then(push).then(function() { + messages.push('done'); + }); + + function push() { + messages.push(String.fromCharCode(start++)); + if (start != 101) { // 'd' + p.then(push); + } + } + + clock.tick(); + assertArrayEquals(['a', 'b', 'c', 'd', 'done'], messages); +} + + +function testThenReturnsOwnPromiseIfNoCallbacksWereGiven() { + var deferred = new webdriver.promise.Deferred(); + assertEquals(deferred.promise, deferred.promise.then()); + assertEquals(deferred.promise, webdriver.promise.when(deferred.promise)); +} + + +function testIsStillConsideredUnHandledIfNoCallbacksWereGivenOnCallsToThen() { + webdriver.promise.rejected(new StubError).then(); + var handler = callbackHelper(assertIsStubError); + + // so tearDown() doesn't throw + app.reset(); + app.on(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, handler); + clock.tick(); + handler.assertCalled(); +} + +function testResolvedReturnsInputValueIfItIsAPromise() { + var input = webdriver.promise.fulfilled('a'); + var output = webdriver.promise.fulfilled(input); + assertEquals(input, output); +} + + +function testADeferredsParentControlFlowIsActiveForCallbacks() { + var defaultFlow = webdriver.promise.controlFlow(); + + var flow1 = new webdriver.promise.ControlFlow(); + var d = new webdriver.promise.Deferred(flow1); + d.fulfill(); + + var flow2 = new webdriver.promise.ControlFlow(); + var d2 = new webdriver.promise.Deferred(flow2); + d2.fulfill(); + + assertIsFlow(defaultFlow); + + var callbacks = callbackPair(); + d.promise.then(assertIsFlow(flow1)). + then(assertIsFlow(flow1)). + then(function() { + return d2.promise.then(assertIsFlow(flow2)); + }). + then(assertIsFlow(flow1)). + then(callbacks.callback, callbacks.errback); + + clock.tick(); + callbacks.assertCallback(); + assertIsFlow(defaultFlow); + + function assertIsFlow(flow) { + return function() { + assertEquals(flow, webdriver.promise.controlFlow()); + }; + } +} + + +function testPromiseAll_emptyArray() { + var pair = callbackPair(function(value) { + assertArrayEquals([], value); + }); + + webdriver.promise.all([]).then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testPromiseAll() { + var a = [ + 0, 1, + webdriver.promise.defer(), + webdriver.promise.defer(), + 4, 5, 6 + ]; + delete a[5]; + + var pair = callbackPair(function(value) { + var expected = [0, 1, 2, 3, 4, 5, 6]; + delete expected[5]; + assertArrayEquals(expected, value); + }); + + webdriver.promise.all(a).then(pair.callback, pair.errback); + pair.assertNeither(); + + a[2].fulfill(2); + pair.assertNeither(); + + a[3].fulfill(3); + clock.tick(); + pair.assertCallback(); +} + + +function testPromiseAll_usesFirstRejection() { + var a = [ + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + + var pair = callbackPair(null, assertIsStubError); + + webdriver.promise.all(a).then(pair.callback, pair.errback); + pair.assertNeither(); + + a[1].reject(new StubError); + clock.tick(); + pair.assertErrback(); + + a[0].reject(Error('ignored')); + clock.tick(Infinity); +} + + +function testMappingAnArray() { + var a = [1, 2, 3]; + var result = webdriver.promise.map(a, function(value, index, a2) { + assertEquals(a, a2); + assertEquals('not a number', 'number', typeof index); + return value + 1; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([2, 3, 4], value); + }); + + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testMappingAnArray_omitsDeleted() { + var a = [0, 1, 2, 3, 4, 5, 6]; + delete a[1]; + delete a[3]; + delete a[4]; + delete a[6]; + + var result = webdriver.promise.map(a, function(value) { + return value * value; + }); + + var expected = [0, 1, 4, 9, 16, 25, 36]; + delete expected[1]; + delete expected[3]; + delete expected[4]; + delete expected[6]; + + var pair = callbackPair(function(value) { + assertArrayEquals(expected, value); + }); + + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testMappingAnArray_emptyArray() { + var result = webdriver.promise.map([], function(value) { + return value + 1; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([], value); + }); + + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testMappingAnArray_inputIsPromise() { + var input = webdriver.promise.defer(); + var result = webdriver.promise.map(input, function(value) { + return value + 1; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([2, 3, 4], value); + }); + + result.then(pair.callback, pair.errback); + pair.assertNeither(); + input.fulfill([1, 2, 3]); + clock.tick(); + pair.assertCallback(); +} + + +function testMappingAnArray_waitsForFunctionResultToResolve() { + var innerResults = [ + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + + var result = webdriver.promise.map([1, 2], function(value, index) { + return innerResults[index].promise; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals(['a', 'b'], value); + }); + + result.then(pair.callback, pair.errback); + pair.assertNeither(); + + innerResults[0].fulfill('a'); + clock.tick(); + pair.assertNeither(); + + innerResults[1].fulfill('b'); + clock.tick(); + pair.assertCallback(); +} + + +function testMappingAnArray_rejectsPromiseIfFunctionThrows() { + var result = webdriver.promise.map([1], throwStubError); + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testMappingAnArray_rejectsPromiseIfFunctionReturnsRejectedPromise() { + var result = webdriver.promise.map([1], function() { + return webdriver.promise.rejected(new StubError); + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testMappingAnArray_stopsCallingFunctionIfPreviousIterationFailed() { + var count = 0; + var result = webdriver.promise.map([1, 2, 3, 4], function() { + count++; + if (count == 3) { + throw new StubError; + } + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); + assertEquals(3, count); +} + + +function testMappingAnArray_rejectsWithFirstRejectedPromise() { + var innerResult = [ + webdriver.promise.fulfilled(), + createRejectedPromise(new StubError), + createRejectedPromise(Error('should be ignored')) + ]; + var count = 0; + var result = webdriver.promise.map([1, 2, 3, 4], function(value, index) { + count += 1; + return innerResult[index]; + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); + assertEquals(2, count); +} + + +function testMappingAnArray_preservesOrderWhenMapReturnsPromise() { + var deferreds = [ + webdriver.promise.defer(), + webdriver.promise.defer(), + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + var result = webdriver.promise.map(deferreds, function(value) { + return value.promise; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([0, 1, 2, 3], value); + }); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertNeither(); + + goog.array.forEachRight(deferreds, function(d, i) { + d.fulfill(i); + }); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray() { + var a = [0, 1, 2, 3]; + var result = webdriver.promise.filter(a, function(val, index, a2) { + assertEquals(a, a2); + assertEquals('not a number', 'number', typeof index); + return val > 1; + }); + + var pair = callbackPair(function(val) { + assertArrayEquals([2, 3], val); + }); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray_omitsDeleted() { + var a = [0, 1, 2, 3, 4, 5, 6]; + delete a[3]; + delete a[4]; + + var result = webdriver.promise.filter(a, function(value) { + return value > 1 && value < 6; + }); + + var pair = callbackPair(function(val) { + assertArrayEquals([2, 5], val); + }); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray_preservesInputs() { + var a = [0, 1, 2, 3]; + + var result = webdriver.promise.filter(a, function(value, i, a2) { + assertEquals(a, a2); + // Even if a function modifies the input array, the original value + // should be inserted into the new array. + a2[i] = a2[i] - 1; + return a2[i] >= 1; + }); + + var pair = callbackPair(function(val) { + assertArrayEquals([2, 3], val); + }); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray_inputIsPromise() { + var input = webdriver.promise.defer(); + var result = webdriver.promise.filter(input, function(value) { + return value > 1 && value < 3; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([2], value); + }); + + result.then(pair.callback, pair.errback); + pair.assertNeither(); + input.fulfill([1, 2, 3]); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray_waitsForFunctionResultToResolve() { + var innerResults = [ + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + + var result = webdriver.promise.filter([1, 2], function(value, index) { + return innerResults[index].promise; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([2], value); + }); + + result.then(pair.callback, pair.errback); + pair.assertNeither(); + + innerResults[0].fulfill(false); + clock.tick(); + pair.assertNeither(); + + innerResults[1].fulfill(true); + clock.tick(); + pair.assertCallback(); +} + + +function testFilteringAnArray_rejectsPromiseIfFunctionReturnsRejectedPromise() { + var result = webdriver.promise.filter([1], function() { + return webdriver.promise.rejected(new StubError); + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function testFilteringAnArray_stopsCallingFunctionIfPreviousIterationFailed() { + var count = 0; + var result = webdriver.promise.filter([1, 2, 3, 4], function() { + count++; + if (count == 3) { + throw new StubError; + } + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); + assertEquals(3, count); +} + + +function testFilteringAnArray_rejectsWithFirstRejectedPromise() { + var innerResult = [ + webdriver.promise.fulfilled(), + createRejectedPromise(new StubError), + createRejectedPromise(Error('should be ignored')) + ]; + var result = webdriver.promise.filter([1, 2, 3, 4], function(value, index) { + assertTrue(index < innerResult.length); + return innerResult[index]; + }); + + var pair = callbackPair(null, assertIsStubError); + result.then(pair.callback, pair.errback); + pair.assertNeither(); + + clock.tick(); + pair.assertErrback(); +} + + +function testFilteringAnArray_preservesOrderWhenFilterReturnsPromise() { + var deferreds = [ + webdriver.promise.defer(), + webdriver.promise.defer(), + webdriver.promise.defer(), + webdriver.promise.defer() + ]; + var result = webdriver.promise.filter([0, 1, 2, 3], function(value, index) { + return deferreds[index].promise; + }); + + var pair = callbackPair(function(value) { + assertArrayEquals([1, 2], value); + }); + result.then(pair.callback, pair.errback); + clock.tick(); + pair.assertNeither(); + + goog.array.forEachRight(deferreds, function(d, i) { + d.fulfill(i > 0 && i < 3); + }); + clock.tick(); + pair.assertCallback(); +} + + +function testAddThenableImplementation() { + function tmp() {} + assertFalse(webdriver.promise.Thenable.isImplementation(new tmp())); + webdriver.promise.Thenable.addImplementation(tmp); + assertTrue(webdriver.promise.Thenable.isImplementation(new tmp())); +} + + +function testLongStackTraces_doesNotAppendStackIfFeatureDisabled() { + webdriver.promise.LONG_STACK_TRACES = false; + + var error = Error('hello'); + var originalStack = error.stack; + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + assertEquals(originalStack, e.stack); + }); + webdriver.promise.rejected(error). + then(fail). + then(fail). + then(fail). + then(pair.callback, pair.errback); + clock.tick(); + pair.assertErrback(); +} + + +function getStackMessages(error) { + var stack = webdriver.stacktrace.getStack(error); + return goog.array.filter(stack.split(/\n/), function(line) { + return /^From: /.test(line); + }); +} + + +function testLongStackTraces_appendsInitialPromiseCreation_resolverThrows() { + webdriver.promise.LONG_STACK_TRACES = true; + + var error = Error('hello'); + var originalStack = error.stack; + + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + if (!goog.isString(originalStack)) { + return; + } + assertNotEquals(originalStack, e.stack); + assertTrue('should start with original stack', + goog.string.startsWith(e.stack, originalStack)); + assertArrayEquals(['From: Promise: new'], getStackMessages(e)); + }); + + new webdriver.promise.Promise(function() { + throw error; + }).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testLongStackTraces_appendsInitialPromiseCreation_rejectCalled() { + webdriver.promise.LONG_STACK_TRACES = true; + + var error = Error('hello'); + var originalStack = error.stack; + + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + if (!goog.isString(originalStack)) { + return; + } + assertNotEquals(originalStack, e.stack); + assertTrue('should start with original stack', + goog.string.startsWith(e.stack, originalStack)); + assertArrayEquals(['From: Promise: new'], getStackMessages(e)); + }); + + new webdriver.promise.Promise(function(_, reject) { + reject(error); + }).then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testLongStackTraces_appendsEachStepToRejectionError() { + webdriver.promise.LONG_STACK_TRACES = true; + + var error = Error('hello'); + var originalStack = error.stack; + + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + if (!goog.isString(originalStack)) { + return; + } + assertNotEquals(originalStack, e.stack); + assertTrue('should start with original stack', + goog.string.startsWith(e.stack, originalStack)); + assertArrayEquals([ + 'From: Promise: new', + 'From: Promise: then', + 'From: Promise: thenCatch', + 'From: Promise: then', + 'From: Promise: thenCatch', + ], getStackMessages(e)); + }); + + new webdriver.promise.Promise(function() { + throw error; + }). + then(fail). + thenCatch(function(e) { throw e; }). + then(fail). + thenCatch(function(e) { throw e; }). + then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} + + +function testLongStackTraces_errorOccursInCallbackChain() { + webdriver.promise.LONG_STACK_TRACES = true; + + var error = Error('hello'); + var originalStack = error.stack; + + var pair = callbackPair(null, function(e) { + assertEquals(error, e); + if (!goog.isString(originalStack)) { + return; + } + assertNotEquals(originalStack, e.stack); + assertTrue('should start with original stack', + goog.string.startsWith(e.stack, originalStack)); + assertArrayEquals([ + 'From: Promise: then', + 'From: Promise: thenCatch', + ], getStackMessages(e)); + }); + + webdriver.promise.fulfilled(). + then(goog.nullFunction). + then(goog.nullFunction). + then(function() { + throw error; + }). + thenCatch(function(e) { throw e; }). + then(pair.callback, pair.errback); + + clock.tick(); + pair.assertErrback(); +} diff --git a/lib/webdriver/test/stacktrace_test.js b/lib/webdriver/test/stacktrace_test.js new file mode 100644 index 0000000..8dd4d92 --- /dev/null +++ b/lib/webdriver/test/stacktrace_test.js @@ -0,0 +1,480 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('bot.Error'); +goog.require('bot.ErrorCode'); +goog.require('goog.string'); +goog.require('goog.testing.JsUnitException'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.StrictMock'); +goog.require('goog.testing.jsunit'); +goog.require('goog.testing.stacktrace'); +goog.require('webdriver.stacktrace'); +goog.require('webdriver.test.testutil'); + +var stubs; + +function setUpPage() { + stubs = new goog.testing.PropertyReplacer(); +} + +function tearDown() { + stubs.reset(); +} + + +function assertStackFrame(message, frameOrFrameString, expectedFrame) { + var frame = frameOrFrameString; + if (goog.isString(frame)) { + frame = webdriver.stacktrace.parseStackFrame_(frame); + assertNotNull(message + '\nunable to parse frame: ' + frameOrFrameString, + frame); + } + var diff = []; + for (var prop in expectedFrame) { + if (goog.isString(expectedFrame[prop]) && + frame[prop] !== expectedFrame[prop]) { + diff.push([ + prop, ': <', expectedFrame[prop], '> !== <', frame[prop], '>' + ].join('')); + } + } + if (diff.length) { + fail(message + + '\nfor: <' + frameOrFrameString + '>' + + '\nexpected: <' + expectedFrame + '>' + + '\nbut was: <' + frame + '>' + + '\n ' + + diff.join('\n ')); + } +} + +function assertFrame(frame, file, line) { + // Normalize path for when run through Node.js on Windows. + var url = frame.getUrl().replace(/\\/g, '/'); + assertContains('/' + file, url); + assertEquals(line, frame.getLine()); +} + +function testGetStacktraceFromFile() { + if (!webdriver.stacktrace.BROWSER_SUPPORTED) { + return false; + } + + var stacktrace = webdriver.test.testutil.getStackTrace(); + assertFrame(stacktrace[0], 'testutil.js', 50); + assertFrame(stacktrace[1], 'stacktrace_test.js', 78); +} + +function testGetStacktraceWithUrlOnLine() { + if (!webdriver.stacktrace.BROWSER_SUPPORTED) { + return false; + } + + // The url argument is intentionally ignored here. + function getStacktraceWithUrlArgument(url) { + return webdriver.stacktrace.get(); + } + + var stacktrace = getStacktraceWithUrlArgument('http://www.google.com'); + assertFrame(stacktrace[0], 'stacktrace_test.js', 90); + + stacktrace = getStacktraceWithUrlArgument('http://www.google.com/search'); + assertFrame(stacktrace[0], 'stacktrace_test.js', 90); +} + +function testParseStackFrameInV8() { + assertStackFrame('exception name only (node v0.8)', + ' at Error (unknown source)', + new webdriver.stacktrace.Frame('', 'Error', '', 'unknown source')); + + assertStackFrame('exception name only (chrome v22)', + ' at Error ()', + new webdriver.stacktrace.Frame('', 'Error', '', '')); + + assertStackFrame('context object + function name + url', + ' at Object.assert (file:///.../asserts.js:29:10)', + new webdriver.stacktrace.Frame('Object', 'assert', '', + 'file:///.../asserts.js:29:10')); + + assertStackFrame('context object + function name + file', + ' at Object.assert (asserts.js:29:10)', + new webdriver.stacktrace.Frame('Object', 'assert', '', + 'asserts.js:29:10')); + + assertStackFrame('context object + anonymous function + file', + ' at Interface. (repl.js:182:12)', + new webdriver.stacktrace.Frame('Interface', '', '', + 'repl.js:182:12')); + + assertStackFrame('url only', + ' at http://www.example.com/jsunit.js:117:13', + new webdriver.stacktrace.Frame('', '', '', + 'http://www.example.com/jsunit.js:117:13')); + + assertStackFrame('file only', + ' at repl:1:57', + new webdriver.stacktrace.Frame('', '', '', 'repl:1:57')); + + assertStackFrame('function alias', + ' at [object Object].exec [as execute] (file:///foo)', + new webdriver.stacktrace.Frame('[object Object]', 'exec', + 'execute', 'file:///foo')); + + assertStackFrame('constructor call', + ' at new Class (file:///foo)', + new webdriver.stacktrace.Frame('new ', 'Class', '', 'file:///foo')); + + assertStackFrame('object property constructor call', + ' at new [object Object].foo (repl:1:2)', + new webdriver.stacktrace.Frame('new [object Object]', 'foo', '', + 'repl:1:2')); + + assertStackFrame('namespaced constructor call', + ' at new foo.bar.Class (foo:1:2)', + new webdriver.stacktrace.Frame('new foo.bar', 'Class', '', 'foo:1:2')); + + assertStackFrame('anonymous constructor call', + ' at new (file:///foo)', + new webdriver.stacktrace.Frame('new ', '', '', + 'file:///foo')); + + assertStackFrame('native function call', + ' at Array.forEach (native)', + new webdriver.stacktrace.Frame('Array', 'forEach', '', 'native')); + + assertStackFrame('eval', + ' at foo (eval at file://bar)', + new webdriver.stacktrace.Frame('', 'foo', '', 'eval at file://bar')); + + assertStackFrame('nested anonymous eval', + ' at eval (eval at (unknown source), :2:7)', + new webdriver.stacktrace.Frame('', 'eval', '', + 'eval at (unknown source), :2:7')); + + assertStackFrame('Url containing parentheses', + ' at Object.assert (http://bar:4000/bar.js?value=(a):150:3)', + new webdriver.stacktrace.Frame('Object', 'assert', '', + 'http://bar:4000/bar.js?value=(a):150:3')); + + assertStackFrame('Frame with non-standard leading whitespace (issue 7994)', + ' at module.exports.runCucumber (/local/dir/path)', + new webdriver.stacktrace.Frame('module.exports', 'runCucumber', '', + '/local/dir/path')); +} + +function testParseClosureCanonicalStackFrame() { + assertStackFrame('unknown frame', '> (unknown)', + webdriver.stacktrace.ANONYMOUS_FRAME_); + assertStackFrame('anonymous frame', '> anonymous', + webdriver.stacktrace.ANONYMOUS_FRAME_); + + assertStackFrame('name only', '> foo', + new webdriver.stacktrace.Frame('', 'foo', '', '')); + + assertStackFrame('name and path', '> foo at http://x:123', + new webdriver.stacktrace.Frame('', 'foo', '', 'http://x:123')); + + assertStackFrame('anonymous function with path', + '> anonymous at file:///x/y/z', + new webdriver.stacktrace.Frame('', 'anonymous', '', 'file:///x/y/z')); + + assertStackFrame('anonymous function with v8 path', + '> anonymous at /x/y/z:12:34', + new webdriver.stacktrace.Frame('', 'anonymous', '', '/x/y/z:12:34')); + + assertStackFrame('context and name only', + '> foo.bar', new webdriver.stacktrace.Frame('foo', 'bar', '', '')); + + assertStackFrame('name and alias', + '> foo [as bar]', new webdriver.stacktrace.Frame('', 'foo', 'bar', '')); + + assertStackFrame('context, name, and alias', + '> foo.bar [as baz]', + new webdriver.stacktrace.Frame('foo', 'bar', 'baz', '')); + + assertStackFrame('path only', '> http://x:123', + new webdriver.stacktrace.Frame('', '', '', 'http://x:123')); + + assertStackFrame('name and arguments', + '> foo(arguments)', new webdriver.stacktrace.Frame('', 'foo', '', '')); + + assertStackFrame('full frame', + '> foo.bar(123, "abc") [as baz] at http://x:123', + new webdriver.stacktrace.Frame('foo', 'bar', 'baz', 'http://x:123')); + + assertStackFrame('name and url with sub-domain', + '> foo at http://x.y.z:80/path:1:2', + new webdriver.stacktrace.Frame('', 'foo', '', + 'http://x.y.z:80/path:1:2')); + + assertStackFrame('name and url with sub-domain', + '> foo.bar.baz at http://x.y.z:80/path:1:2', + new webdriver.stacktrace.Frame('foo.bar', 'baz', '', + 'http://x.y.z:80/path:1:2')); +} + +// All test strings are parsed with the conventional and long +// frame algorithms. +function testParseStackFrameInFirefox() { + var frameString = 'Error("Assertion failed")@:0'; + var frame = webdriver.stacktrace.parseStackFrame_(frameString); + var expected = new webdriver.stacktrace.Frame('', 'Error', '', ''); + assertObjectEquals('function name + arguments', expected, frame); + + frame = webdriver.stacktrace.parseLongFirefoxFrame_(frameString); + assertObjectEquals('function name + arguments', expected, frame); + + frameString = '()@file:///foo:42'; + frame = webdriver.stacktrace.parseStackFrame_(frameString); + expected = new webdriver.stacktrace.Frame('', '', '', 'file:///foo:42'); + assertObjectEquals('anonymous function', expected, frame); + + frame = webdriver.stacktrace.parseLongFirefoxFrame_(frameString); + assertObjectEquals('anonymous function', expected, frame); + + frameString = '@javascript:alert(0)'; + frame = webdriver.stacktrace.parseStackFrame_(frameString); + expected = new webdriver.stacktrace.Frame('', '', '', 'javascript:alert(0)'); + assertObjectEquals('anonymous function', expected, frame); + + frame = webdriver.stacktrace.parseLongFirefoxFrame_(frameString); + assertObjectEquals('anonymous function', expected, frame); +} + +function testStringRepresentation() { + var frame = new webdriver.stacktrace.Frame('window', 'foo', 'bar', + 'http://x?a=1&b=2:1'); + assertEquals(' at window.foo [as bar] (http://x?a=1&b=2:1)', + frame.toString()); + + frame = new webdriver.stacktrace.Frame('', 'Error', '', ''); + assertEquals(' at Error ()', frame.toString()); + + assertEquals(' at ', + webdriver.stacktrace.ANONYMOUS_FRAME_.toString()); + + frame = new webdriver.stacktrace.Frame('', '', '', 'http://x:123'); + assertEquals(' at http://x:123', frame.toString()); + + frame = new webdriver.stacktrace.Frame('foo', 'bar', '', 'http://x:123'); + assertEquals(' at foo.bar (http://x:123)', frame.toString()); + + frame = new webdriver.stacktrace.Frame('new ', 'Foo', '', 'http://x:123'); + assertEquals(' at new Foo (http://x:123)', frame.toString()); + + frame = new webdriver.stacktrace.Frame('new foo', 'Bar', '', ''); + assertEquals(' at new foo.Bar ()', frame.toString()); +} + +// Create a stack trace string with one modest record and one long record, +// Verify that all frames are parsed. The length of the long arg is set +// to blow Firefox 3x's stack if put through a RegExp. +function testParsingLongStackTrace() { + var longArg = goog.string.buildString( + '(', goog.string.repeat('x', 1000000), ')'); + var stackTrace = goog.string.buildString( + 'shortFrame()@:0\n', + 'longFrame', + longArg, + '@http://google.com/somescript:0\n'); + var frames = webdriver.stacktrace.parse_(stackTrace); + assertEquals('number of returned frames', 2, frames.length); + var expected = new webdriver.stacktrace.Frame('', 'shortFrame', '', ''); + assertStackFrame('short frame', frames[0], expected); + + expected = new webdriver.stacktrace.Frame( + '', 'longFrame', '', 'http://google.com/somescript:0'); + assertStackFrame('exception name only', frames[1], expected); +} + +function testRemovesV8MessageHeaderBeforeParsingStack() { + var stack = + ' at Color.red (http://x:1234)\n' + + ' at Foo.bar (http://y:5678)'; + + var frames = webdriver.stacktrace.parse_(stack); + assertEquals(2, frames.length); + assertEquals(' at Color.red (http://x:1234)', frames[0].toString()); + assertEquals(' at Foo.bar (http://y:5678)', frames[1].toString()); +} + +function testCanParseClosureJsUnitExceptions() { + stubs.set(goog.testing.stacktrace, 'get', function() { + return '> Color.red at http://x:1234\n' + + '> Foo.bar at http://y:5678'; + }); + + var error = new goog.testing.JsUnitException('stub'); + stubs.reset(); + + var frames = webdriver.stacktrace.parse_(error.stackTrace); + assertEquals(2, frames.length); + assertEquals(' at Color.red (http://x:1234)', frames[0].toString()); + assertEquals(' at Foo.bar (http://y:5678)', frames[1].toString()); +} + +function testFormattingAV8StyleError() { + var errorStub = { + name: 'Error', + message: 'foo', + toString: function() { return 'Error: foo'; }, + stack: + 'Error: foo\n' + + ' at Color.red (http://x:1234)\n' + + ' at Foo.bar (http://y:5678)' + }; + + var ret = webdriver.stacktrace.format(errorStub); + assertEquals(errorStub, ret); + assertEquals([ + 'Error: foo', + ' at Color.red (http://x:1234)', + ' at Foo.bar (http://y:5678)' + ].join('\n'), ret.stack); +} + +function testFormattingAFirefoxStyleError() { + var errorStub = { + name: 'Error', + message: 'boom', + toString: function() { return 'Error: boom'; }, + stack: + 'foo@file:///foo/foo.js:1\n' + + '@file:///bar/bar.js:1' + }; + + var ret = webdriver.stacktrace.format(errorStub); + assertEquals(errorStub, ret); + assertEquals([ + 'Error: boom', + ' at foo (file:///foo/foo.js:1)', + ' at file:///bar/bar.js:1' + ].join('\n'), ret.stack); +} + +function testInsertsAnAnonymousFrameWhenUnableToParse() { + var errorStub = { + name: 'Error', + message: 'boom', + toString: function() { return 'Error: boom'; }, + stack: + 'foo@file:///foo/foo.js:1\n' + + 'this is unparsable garbage\n' + + '@file:///bar/bar.js:1' + }; + + var ret = webdriver.stacktrace.format(errorStub); + assertEquals(errorStub, ret); + assertEquals([ + 'Error: boom', + ' at foo (file:///foo/foo.js:1)', + ' at ', + ' at file:///bar/bar.js:1' + ].join('\n'), ret.stack); +} + +function testFormattingBotErrors() { + var error = new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT, 'boom'); + var expectedStack = [ + 'NoSuchElementError: boom', + ' at Color.red (http://x:1234)', + ' at Foo.bar (http://y:5678)' + ].join('\n'); + error.stack = expectedStack; + + var ret = webdriver.stacktrace.format(error); + assertEquals(ret, error); + assertEquals(expectedStack, error.stack); +} + +function testFormatsUsingNameAndMessageIfAvailable() { + var ret = webdriver.stacktrace.format({ + name: 'TypeError', + message: 'boom' + }); + assertEquals('TypeError: boom\n', ret.stack); + + ret = webdriver.stacktrace.format({ + message: 'boom' + }); + assertEquals('boom\n', ret.stack); + + ret = webdriver.stacktrace.format({ + toString: function() { + return 'Hello world' + } + }); + assertEquals('Hello world\n', ret.stack); + + ret = webdriver.stacktrace.format({ + name: 'TypeError', + message: 'boom', + toString: function() { + return 'Should not use this' + } + }); + assertEquals('TypeError: boom\n', ret.stack); +} + +function testDoesNotFormatErrorIfOriginalStacktraceIsInAnUnexpectedFormat() { + var error = Error('testing'); + var stack = error.stack = [ + 'Error: testing', + '..> at Color.red (http://x:1234)', + '..> at Foo.bar (http://y:5678)' + ].join('\n'); + + var ret = webdriver.stacktrace.format(error); + assertEquals(ret, error); + assertEquals(stack, error.stack); +} + +function testParseStackFrameInIE10() { + assertStackFrame('name and path', + ' at foo (http://bar:4000/bar.js:150:3)', + new webdriver.stacktrace.Frame('', 'foo', '', + 'http://bar:4000/bar.js:150:3')); + + assertStackFrame('Anonymous function', + ' at Anonymous function (http://bar:4000/bar.js:150:3)', + new webdriver.stacktrace.Frame('', 'Anonymous function', '', + 'http://bar:4000/bar.js:150:3')); + + assertStackFrame('Global code', + ' at Global code (http://bar:4000/bar.js:150:3)', + new webdriver.stacktrace.Frame('', 'Global code', '', + 'http://bar:4000/bar.js:150:3')); + + assertStackFrame('eval code', + ' at foo (eval code:150:3)', + new webdriver.stacktrace.Frame('', 'foo', '', 'eval code:150:3')); + + assertStackFrame('nested eval', + ' at eval code (eval code:150:3)', + new webdriver.stacktrace.Frame('', 'eval code', '', 'eval code:150:3')); + + assertStackFrame('Url containing parentheses', + ' at foo (http://bar:4000/bar.js?value=(a):150:3)', + new webdriver.stacktrace.Frame('', 'foo', '', + 'http://bar:4000/bar.js?value=(a):150:3')); + + assertStackFrame('Url ending with parentheses', + ' at foo (http://bar:4000/bar.js?a=))', + new webdriver.stacktrace.Frame('', 'foo', '', + 'http://bar:4000/bar.js?a=)')); +} diff --git a/lib/webdriver/test/test_bootstrap.js b/lib/webdriver/test/test_bootstrap.js new file mode 100644 index 0000000..e429377 --- /dev/null +++ b/lib/webdriver/test/test_bootstrap.js @@ -0,0 +1,67 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Bootstrap file that loads all of the WebDriver scripts in the + * correct order. Once loaded, pages can use {@code goog.require} statements + * from Google Closure to load additional files as needed. + * + * Example Usage: + * + * + * + * + * + * + * + * + * + */ + +(function() { + var scripts = document.getElementsByTagName('script'); + var directoryPath = './'; + var thisFile = 'test_bootstrap.js'; + + for (var i = 0; i < scripts.length; i++) { + var src = scripts[i].src; + var len = src.length; + if (src.substr(len - thisFile.length) == thisFile) { + directoryPath = src.substr(0, len - thisFile.length); + break; + } + } + + // All of the files to load. Files are specified in the order they must be + // loaded, NOT alphabetical order. + var files = [ + '../../../third_party/closure/goog/base.js', + '../../deps.js' + ]; + + for (var j = 0; j < files.length; j++) { + document.write(''); + } +})(); diff --git a/lib/webdriver/test/testing/asserts_test.js b/lib/webdriver/test/testing/asserts_test.js new file mode 100644 index 0000000..c5e0681 --- /dev/null +++ b/lib/webdriver/test/testing/asserts_test.js @@ -0,0 +1,117 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.test.testutil'); +goog.require('webdriver.testing.assert'); +goog.require('webdriver.testing.asserts'); + +var assert = webdriver.testing.assert; +var result; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + result = webdriver.test.testutil.callbackPair(); +} + + +function testAssertion_nonPromiseValue_valueMatches() { + assert('foo').equalTo('foo'); + // OK if it does not throw. +} + + +function testAssertion_nonPromiseValue_notValueMatches() { + var a = assert('foo'); + assertThrows(goog.bind(a.equalTo, a, 'bar')); +} + + +function testAssertion_promiseValue_valueMatches() { + return assert(webdriver.promise.fulfilled('foo')).equalTo('foo'); +} + + +function testAssertion_promiseValue_notValueMatches() { + var d = new webdriver.promise.Deferred(); + return assert(webdriver.promise.fulfilled('bar')).equalTo('foo'). + then(fail, goog.nullFunction); +} + + +function testAssertion_promiseValue_promiseRejected() { + var err = Error(); + return assert(webdriver.promise.rejected(err)).equalTo('foo'). + then(fail, function(e) { + assertEquals(err, e); + }); +} + + +function testAssertion_decoration() { + assert('foo').is.equalTo('foo'); + // Ok if no throws. +} + + +function testAssertion_negation() { + var a = assert('false'); + + a.not.equalTo('bar'); // OK if this does not throw. + a.is.not.equalTo('bar'); // OK if this does not throw. + + var notA = a.not; + assertThrows(goog.bind(notA.equalTo, notA, 123)); + + notA = a.is.not; + assertThrows(goog.bind(notA.equalTo, notA, 123)); +} + + +function testApplyMatcher_nonPromiseValue_valueMatches() { + return assertThat('foo', equals('foo')); +} + + +function testApplyMatcher_nonPromiseValue_notValueMatches() { + return assertThat('foo', equals('bar')).then(fail, goog.nullFunction); +} + + +function testApplyMatcher_promiseValue_valueMatches() { + return assertThat(webdriver.promise.fulfilled('foo'), equals('foo')); +} + + +function testApplyMatcher_promiseValue_notValueMatches() { + return assertThat(webdriver.promise.fulfilled('bar'), equals('foo')). + then(fail, goog.nullFunction); +} + + +function testApplyMatcher_promiseValue_promiseRejected() { + var err = Error(); + return assertThat(webdriver.promise.rejected(err), equals('foo')). + then(fail, function(e) { + assertEquals(err, e); + }); +} diff --git a/lib/webdriver/test/testing/client_test.js b/lib/webdriver/test/testing/client_test.js new file mode 100644 index 0000000..f1179ba --- /dev/null +++ b/lib/webdriver/test/testing/client_test.js @@ -0,0 +1,88 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.testing.Client'); + +var FAKE_WINDOW = { + location: { + pathname: '/foo/bar/baz' + } +}; + +function FakeXhr() {} +FakeXhr.prototype.open = goog.nullFunction; +FakeXhr.prototype.send = goog.nullFunction; + +var stubs = new goog.testing.PropertyReplacer; +var control = new goog.testing.MockControl; +var mockXhr; +var client; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + +function setUp() { + client = new webdriver.testing.Client(FAKE_WINDOW); + mockXhr = control.createStrictMock(FakeXhr); + stubs.set(goog.net, 'XmlHttp', function() { + return mockXhr; + }); +} + +function tearDown() { + stubs.reset(); + control.$tearDown(); +} + +function expectToSendEvent(type, data) { + mockXhr.open('POST', '/testevent', true); + mockXhr.send(JSON.stringify({ + 'id': '/foo/bar/baz', + 'type': type, + 'data': data + })); + control.$replayAll(); +} + +function testSendInitEvent() { + expectToSendEvent('INIT', {}); + client.sendInitEvent(); + control.$verifyAll(); +} + +function testSendResultsEvent() { + expectToSendEvent('RESULTS', { + 'isSuccess': false, + 'report': 'boom' + }); + client.sendResultsEvent(false, 'boom'); + control.$verifyAll(); +} + +function testSendScreenshotEvent() { + expectToSendEvent('SCREENSHOT', { + 'name': 'ss01', + 'data': '12412412asdfasf' + }); + client.sendScreenshotEvent('12412412asdfasf', 'ss01'); + control.$verifyAll(); +} diff --git a/lib/webdriver/test/testing/flowtester_test.js b/lib/webdriver/test/testing/flowtester_test.js new file mode 100644 index 0000000..cad21c9 --- /dev/null +++ b/lib/webdriver/test/testing/flowtester_test.js @@ -0,0 +1,204 @@ +// Copyright 2014 Software Freedom Conservancy. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.jsunit'); +goog.require('webdriver.promise'); +goog.require('webdriver.testing.Clock'); +goog.require('webdriver.testing.promise.FlowTester'); + +var FAKE_TIMER = { + clearInterval: goog.nullFunction, + clearTimeout: goog.nullFunction, + setInterval: goog.nullFunction, + setTimeout: goog.nullFunction +}; + +var originalDefaultFlow = webdriver.promise.controlFlow(); +var originalCreateFlow = webdriver.promise.createFlow; + +var control, mockClock; + +var flowTester; + +function setUp() { + control = new goog.testing.MockControl(); + mockClock = control.createStrictMock(webdriver.testing.Clock); + flowTester = new webdriver.testing.promise.FlowTester( + mockClock, FAKE_TIMER); +} + +function tearDown() { + control.$tearDown(); + flowTester.dispose(); + assertEquals(originalDefaultFlow, webdriver.promise.controlFlow()); + assertEquals(originalCreateFlow, webdriver.promise.createFlow); +} + +function testTurnEventLoopAdvancesClockByEventLoopFrequency() { + mockClock.tick(webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY); + control.$replayAll(); + + flowTester.turnEventLoop(); + control.$verifyAll(); +} + +function captureNewFlow() { + webdriver.promise.createFlow(); + return goog.array.peek(flowTester.allFlows_).flow; +} + +function emitIdle(flow) { + flow.emit(webdriver.promise.ControlFlow.EventType.IDLE); +} + +function emitTask(flow) { + flow.emit(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK); +} + +function emitError(flow, error) { + flow.emit(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + error); +} + +function testVerifySuccess_aSingleFlow() { + var verifySuccess = goog.bind(flowTester.verifySuccess, flowTester); + var flow = webdriver.promise.controlFlow(); + + assertNotThrows('Flow has done nothing yet', verifySuccess); + + emitTask(flow); + assertThrows('Flow is not idle', verifySuccess); + + emitIdle(flow); + assertNotThrows('Flow went idle', verifySuccess); + + emitError(flow, Error()); + assertThrows('Flow had an error', verifySuccess); + + emitIdle(flow); + assertThrows('Flow previously had an error', verifySuccess); +} + +function testVerifySuccess_multipleFlows() { + var verifySuccess = goog.bind(flowTester.verifySuccess, flowTester); + + var flow0 = webdriver.promise.controlFlow(); + assertNotThrows('default throw is idle', verifySuccess); + + var flow1 = captureNewFlow(); + assertThrows('New flows start busy', verifySuccess); + + emitIdle(flow1); + assertNotThrows(verifySuccess); + + assertThrows('Target flow not found', goog.partial(verifySuccess, {})); + + emitError(flow1, Error()); + assertThrows(verifySuccess); + + assertNotThrows( + 'The designated flow is idle and has no errors!', + goog.partial(verifySuccess, flow0)); +} + +function testVerifyFailure() { + var verifyFailure = goog.bind(flowTester.verifyFailure, flowTester); + var flow0 = webdriver.promise.controlFlow(); + + assertThrows('No failures', verifyFailure); + + emitError(flow0, Error()); + assertThrows('Target flow not found', goog.partial(verifyFailure, {})); + assertNotThrows(verifyFailure); + assertNotThrows(goog.partial(verifyFailure, flow0)); + + emitError(flow0, Error()); + assertThrows('multiple failures', verifyFailure); +} + +function testVerifyFailure_multipleFlows() { + var verifyFailure = goog.bind(flowTester.verifyFailure, flowTester); + + var flow0 = webdriver.promise.controlFlow(); + var flow1 = captureNewFlow(); + + emitIdle(flow0); + emitIdle(flow1); + assertThrows(verifyFailure); + + emitError(flow0, Error()); + assertNotThrows(verifyFailure); + assertNotThrows(goog.partial(verifyFailure, flow0)); + assertThrows(goog.partial(verifyFailure, flow1)); + + emitError(flow1, Error()); + assertNotThrows(verifyFailure); + assertNotThrows(goog.partial(verifyFailure, flow0)); + assertNotThrows(goog.partial(verifyFailure, flow1)); +} + +function testGetFailure() { + var getFailure = goog.bind(flowTester.getFailure, flowTester); + + var flow0 = webdriver.promise.controlFlow(); + var flow1 = captureNewFlow(); + + emitIdle(flow0); + emitIdle(flow1); + assertThrows(getFailure); + + var error0 = Error(); + emitError(flow0, error0); + assertEquals(error0, getFailure()); + assertThrows(goog.partial(getFailure, flow1)); + + var error1 = Error(); + emitError(flow1, error1); + assertThrows(getFailure); + assertEquals(error0, getFailure(flow0)); + assertEquals(error1, getFailure(flow1)); +} + +function testAssertStillRunning() { + var assertStillRunning = goog.bind( + flowTester.assertStillRunning, flowTester); + + var flow0 = webdriver.promise.controlFlow(); + var flow1 = captureNewFlow(); + + assertThrows(assertStillRunning); + assertThrows(goog.partial(assertStillRunning, flow0)); + assertNotThrows(goog.partial(assertStillRunning, flow1)); + + emitIdle(flow1); + assertThrows(assertStillRunning); + assertThrows(goog.partial(assertStillRunning, flow0)); + assertThrows(goog.partial(assertStillRunning, flow1)); + + emitTask(flow0); + assertThrows(assertStillRunning); + assertNotThrows(goog.partial(assertStillRunning, flow0)); + assertThrows(goog.partial(assertStillRunning, flow1)); + + emitTask(flow1); + assertNotThrows(assertStillRunning); + assertNotThrows(goog.partial(assertStillRunning, flow0)); + assertNotThrows(goog.partial(assertStillRunning, flow1)); + + emitError(flow0, Error()); + assertThrows(assertStillRunning); + assertThrows(goog.partial(assertStillRunning, flow0)); + assertNotThrows(goog.partial(assertStillRunning, flow1)); +} diff --git a/lib/webdriver/test/testing/testcase_test.js b/lib/webdriver/test/testing/testcase_test.js new file mode 100644 index 0000000..51b54d1 --- /dev/null +++ b/lib/webdriver/test/testing/testcase_test.js @@ -0,0 +1,229 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.Promise'); +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.mockmatchers'); +goog.require('goog.testing.jsunit'); +goog.require('goog.testing.recordFunction'); +goog.require('goog.userAgent'); +goog.require('webdriver.test.testutil'); +goog.require('webdriver.testing.TestCase'); + + +// Aliases for readability. +var IGNORE_ARGUMENT = goog.testing.mockmatchers.ignoreArgument, + IS_ARRAY_ARGUMENT = goog.testing.mockmatchers.isArray, + StubError = webdriver.test.testutil.StubError, + throwStubError = webdriver.test.testutil.throwStubError, + assertIsStubError = webdriver.test.testutil.assertIsStubError; + +var control = new goog.testing.MockControl(); +var mockTestCase, testStub, mockOnComplete, mockOnError, uncaughtExceptions; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + // Use one master mock so we can assert execution order. + mockTestCase = control.createStrictMock({ + setUp: goog.nullFunction, + testFn: goog.nullFunction, + tearDown: goog.nullFunction, + onComplete: goog.nullFunction, + onError: goog.nullFunction + }, true); + + mockOnComplete = goog.bind(mockTestCase.onComplete, mockTestCase); + mockOnError = goog.bind(mockTestCase.onError, mockTestCase); + + testStub = { + name: 'testStub', + scope: mockTestCase, + ref: mockTestCase.testFn + }; + + webdriver.test.testutil.messages = []; + uncaughtExceptions = []; + + webdriver.promise.controlFlow(). + on('uncaughtExceptions', onUncaughtException); +} + +function tearDown() { + var flow = webdriver.promise.controlFlow(); + return new goog.Promise(function(fulfill) { + flow.execute(goog.nullFunction); // Flush. + flow.once('idle', fulfill); + }).then(function() { + assertArrayEquals('There were uncaught exceptions', + [], uncaughtExceptions); + control.$tearDown(); + flow.reset(); + }); +} + +function onUncaughtException(e) { + uncaughtExceptions.push(e); +} + +function schedule(msg, opt_fn) { + var fn = opt_fn || goog.nullFunction; + return webdriver.promise.controlFlow().execute(fn, msg); +} + +function runTest() { + return webdriver.testing.TestCase.prototype.runSingleTest_. + call(mockTestCase, testStub, mockOnError). + then(mockOnComplete); +} + +function testExecutesTheBasicTestFlow() { + mockTestCase.setUp(); + mockTestCase.testFn(); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testExecutingAHappyTestWithScheduledActions() { + mockTestCase.setUp().$does(function() { schedule('a'); }); + mockTestCase.testFn().$does(function() { schedule('b'); }); + mockTestCase.tearDown().$does(function() { schedule('c'); }); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testShouldSkipTestFnIfSetupThrows() { + var e = Error(); + mockTestCase.setUp().$does(function() { throw e; }); + mockOnError(e); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testShouldSkipTestFnIfSetupActionFails_1() { + var e = Error(); + mockTestCase.setUp().$does(function() { + schedule('an explosion', function() { throw e; }); + }); + mockOnError(e); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testShouldSkipTestFnIfSetupActionFails_2() { + var e = Error(); + mockTestCase.setUp().$does(function() { + schedule('an explosion', function() { throw e; }); + }); + mockOnError(e); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testShouldSkipTestFnIfNestedSetupActionFails() { + var e = Error(); + mockTestCase.setUp().$does(function() { + schedule('a', goog.nullFunction).then(function() { + schedule('b', function() { throw e; }); + }); + }); + mockOnError(e); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testRunsAllTasksForEachPhaseBeforeTheNextPhase() { + webdriver.test.testutil.messages = []; + mockTestCase.setUp().$does(function() { schedule('a'); }); + mockTestCase.testFn().$does(function() { schedule('b'); }); + mockTestCase.tearDown().$does(function() { schedule('c'); }); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testRecordsErrorsFromTestFnBeforeTearDown() { + var e = Error(); + mockTestCase.setUp(); + mockTestCase.testFn().$does(function() { throw e; }); + mockOnError(e); + mockTestCase.tearDown(); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testRecordsErrorsFromTearDown() { + var e = Error(); + mockTestCase.setUp(); + mockTestCase.testFn(); + mockTestCase.tearDown().$does(function() { throw e; }); + mockOnError(e); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testErrorFromSetUpAndTearDown() { + var e1 = Error(); + var e2 = Error(); + mockTestCase.setUp().$does(function() { throw e1; }); + mockOnError(e1); + mockTestCase.tearDown().$does(function() { throw e2; }); + mockOnError(e2); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} + +function testErrorFromTestFnAndTearDown() { + var e1 = Error(), e2 = Error(); + mockTestCase.setUp(); + mockTestCase.testFn().$does(function() { throw e1; }); + mockOnError(e1); + mockTestCase.tearDown().$does(function() { throw e2; }); + mockOnError(e2); + mockOnComplete(IGNORE_ARGUMENT); + control.$replayAll(); + + return runTest(); +} diff --git a/lib/webdriver/test/testutil.js b/lib/webdriver/test/testutil.js new file mode 100644 index 0000000..2cf02a5 --- /dev/null +++ b/lib/webdriver/test/testutil.js @@ -0,0 +1,209 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.test.testutil'); +goog.provide('webdriver.test.testutil.StubError'); + +goog.require('goog.array'); +goog.require('goog.debug.Error'); +goog.require('goog.string'); +goog.require('goog.testing.recordFunction'); +goog.require('webdriver.stacktrace'); + + + +/** + * A custom error used for testing. + * @param {string=} opt_msg The error message to use. + * @constructor + * @extends {goog.debug.Error} + * @final + */ +webdriver.test.testutil.StubError = function(opt_msg) { + webdriver.test.testutil.StubError.base(this, 'constructor', opt_msg); +}; +goog.inherits(webdriver.test.testutil.StubError, goog.debug.Error); + + +/** @override */ +webdriver.test.testutil.StubError.prototype.name = 'StubError'; + + +/** @type {Array.} */ +webdriver.test.testutil.messages = []; + +webdriver.test.testutil.getStackTrace = function() { + return webdriver.stacktrace.get(); +}; + +webdriver.test.testutil.throwStubError = function() { + throw new webdriver.test.testutil.StubError; +}; + +webdriver.test.testutil.assertIsStubError = function(error) { + assertTrue(error + ' is not an instanceof StubError', + error instanceof webdriver.test.testutil.StubError); +}; + + +/** + * Asserts the contents of the {@link webdriver.test.testutil.messages} array + * are as expected. + * @param {...*} var_args The expected contents. + */ +webdriver.test.testutil.assertMessages = function(var_args) { + var args = Array.prototype.slice.call(arguments, 0); + assertArrayEquals(args, webdriver.test.testutil.messages); +}; + + +/** + * Wraps a call to {@link webdriver.test.testutil.assertMessages} so it can + * be passed as a callback. + * @param {...*} var_args The expected contents. + * @return {!Function} The wrapped function. + */ +webdriver.test.testutil.assertingMessages = function(var_args) { + var args = goog.array.slice(arguments, 0); + return function() { + return webdriver.test.testutil.assertMessages.apply(null, args); + }; +}; + + +/** + * Asserts an object is a promise. + * @param {*} obj The object to check. + */ +webdriver.test.testutil.assertIsPromise = function(obj) { + assertTrue('Value is not a promise: ' + goog.typeOf(obj), + webdriver.promise.isPromise(obj)); +}; + + +/** + * Asserts an object is not a promise. + * @param {*} obj The object to check. + */ +webdriver.test.testutil.assertNotPromise = function(obj) { + assertFalse(webdriver.promise.isPromise(obj)); +}; + +/** + * Wraps a function. The wrapped function will have several utility functions: + *

          + *
        • assertCalled: Asserts that the function was called. + *
        • assertNotCalled: Asserts that the function was not called. + *
        + * @param {Function=} opt_fn The function to wrap; defaults to + * goog.nullFunction. + * @return {!Function} The wrapped function. + * @see goog.testing.recordFunction + */ +webdriver.test.testutil.callbackHelper = function(opt_fn) { + var callback = goog.testing.recordFunction(opt_fn); + + callback.getExpectedCallCountMessage = function(n, opt_prefix, opt_noJoin) { + var message = []; + if (opt_prefix) message.push(opt_prefix); + + var calls = callback.getCalls(); + message.push( + 'Expected to be called ' + n + ' times.', + ' was called ' + calls.length + ' times:'); + goog.array.forEach(calls, function(call) { + var e = call.getError(); + if (e) { + throw e; + } + }); + return opt_noJoin ? message : message.join('\n'); + }; + + callback.assertCalled = function(opt_message) { + assertEquals(callback.getExpectedCallCountMessage(1, opt_message), + 1, callback.getCallCount()); + }; + + callback.assertNotCalled = function(opt_message) { + assertEquals(callback.getExpectedCallCountMessage(0, opt_message), + 0, callback.getCallCount()); + }; + + return callback; +}; + + +/** + * Creates a utility for managing a pair of callbacks, capable of asserting only + * one of the pair was ever called. + * + * @param {Function=} opt_callback The callback to manage. + * @param {Function=} opt_errback The errback to manage. + */ +webdriver.test.testutil.callbackPair = function(opt_callback, opt_errback) { + var pair = { + callback: webdriver.test.testutil.callbackHelper(opt_callback), + errback: webdriver.test.testutil.callbackHelper(opt_errback) + }; + + /** @param {string=} opt_message Optional failure message. */ + pair.assertEither = function(opt_message) { + if (!pair.callback.getCallCount() && + !pair.errback.getCallCount()) { + var message = ['Neither callback nor errback has been called']; + if (opt_message) goog.array.insertAt(message, opt_message); + fail(message.join('\n')); + } + }; + + /** @param {string=} opt_message Optional failure message. */ + pair.assertNeither = function(opt_message) { + var message = (opt_message || '') + 'Did not expect callback or errback'; + pair.callback.assertNotCalled(message); + pair.errback.assertNotCalled(message); + }; + + /** @param {string=} opt_message Optional failure message. */ + pair.assertCallback = function(opt_message) { + var message = opt_message ? (opt_message + ': ') : ''; + pair.errback.assertNotCalled(message + 'Expected callback, not errback'); + pair.callback.assertCalled(message + 'Callback not called'); + }; + + /** @param {string=} opt_message Optional failure message. */ + pair.assertErrback = function(opt_message) { + var message = opt_message ? (opt_message + ': ') : ''; + pair.callback.assertNotCalled(message + 'Expected errback, not callback'); + pair.errback.assertCalled(message + 'Errback not called'); + }; + + pair.reset = function() { + pair.callback.reset(); + pair.errback.reset(); + }; + + return pair; +}; + + +webdriver.test.testutil.assertObjectEquals = function(expected, actual) { + assertObjectEquals( + 'Expected: ' + JSON.stringify(expected) + '\n' + + 'Actual: ' + JSON.stringify(actual), + expected, actual); +}; diff --git a/lib/webdriver/test/testutil_test.js b/lib/webdriver/test/testutil_test.js new file mode 100644 index 0000000..af55b32 --- /dev/null +++ b/lib/webdriver/test/testutil_test.js @@ -0,0 +1,104 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.test.testutil'); + +// Aliases for readability. +var callbackHelper = webdriver.test.testutil.callbackHelper, + callbackPair = webdriver.test.testutil.callbackPair; + +function testCallbackHelper_functionCalled() { + var callback = callbackHelper(); + callback(); + assertNotThrows(callback.assertCalled); + assertThrows(callback.assertNotCalled); +} + +function testCallbackHelper_functionCalledMoreThanOnce() { + var callback = callbackHelper(); + callback(); + callback(123, 'abc'); + assertThrows(callback.assertCalled); + assertThrows(callback.assertNotCalled); +} + +function testCallbackHelper_functionNotCalled() { + var callback = callbackHelper(); + assertNotThrows(callback.assertNotCalled); + assertThrows(callback.assertCalled); +} + +function testCallbackHelper_wrappedFunctionIsCalled() { + var count = 0; + var callback = callbackHelper(function() { + count += 1; + }); + callback(); + assertNotThrows(callback.assertCalled); + assertThrows(callback.assertNotCalled); + assertEquals(1, count); +} + +function testCallbackPair_callbackExpected() { + var pair = callbackPair(); + assertThrows(pair.assertCallback); + pair.callback(); + assertNotThrows(pair.assertCallback); + pair.errback(); + assertThrows(pair.assertCallback); + + pair.reset(); + pair.callback(); + assertNotThrows(pair.assertCallback); + pair.callback(); + assertThrows('Should expect to be called only once', + pair.assertCallback); +} + +function testCallbackPair_errbackExpected() { + var pair = callbackPair(); + assertThrows(pair.assertErrback); + pair.errback(); + assertNotThrows(pair.assertErrback); + pair.callback(); + assertThrows(pair.assertErrback); +} + +function testCallbackPair_eitherExpected() { + var pair = callbackPair(); + assertThrows(pair.assertEither); + pair.errback(); + assertNotThrows(pair.assertEither); + pair.reset(); + pair.callback(); + assertNotThrows(pair.assertEither); + pair.errback(); + assertNotThrows(pair.assertEither); +} + +function testCallbackPair_neitherExpected() { + var pair = callbackPair(); + assertNotThrows(pair.assertNeither); + pair.errback(); + assertThrows(pair.assertNeither); + pair.reset(); + pair.callback(); + assertThrows(pair.assertNeither); + pair.errback(); + assertThrows(pair.assertNeither); +} diff --git a/lib/webdriver/test/until_test.js b/lib/webdriver/test/until_test.js new file mode 100644 index 0000000..e4af619 --- /dev/null +++ b/lib/webdriver/test/until_test.js @@ -0,0 +1,411 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.test.until_test'); +goog.setTestOnly('webdriver.test.until_test'); + +goog.require('bot.Error'); +goog.require('bot.ErrorCode'); +goog.require('bot.response'); +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.By'); +goog.require('webdriver.CommandName'); +goog.require('webdriver.WebDriver'); +goog.require('webdriver.WebElement'); +goog.require('webdriver.WebElementPromise'); +goog.require('webdriver.until'); + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +var By = webdriver.By; +var until = webdriver.until; +var CommandName = webdriver.CommandName; + +var driver, executor; + +var TestExecutor = function() { + this.handlers_ = {}; +}; + + +TestExecutor.prototype.on = function(cmd, handler) { + this.handlers_[cmd] = handler; + return this; +}; + + +TestExecutor.prototype.execute = function(cmd, callback) { + if (!this.handlers_[cmd.getName()]) { + throw Error('Unsupported command: ' + cmd.getName()); + } + this.handlers_[cmd.getName()](cmd, callback); +}; + + +function setUp() { + executor = new TestExecutor(); + driver = new webdriver.WebDriver('session-id', executor); +} + + +function testUntilAbleToSwitchToFrame_failsFastForNonSwitchErrors() { + var e = Error('boom'); + executor.on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(e); + }); + + return driver.wait(until.ableToSwitchToFrame(0), 100) + .then(fail, function(e2) { + assertEquals(e, e2); + }); +} + + +function testUntilAbleToSwitchToFrame_byIndex() { + executor.on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + return driver.wait(until.ableToSwitchToFrame(0), 100); +} + + +function testUntilAbleToSwitchToFrame_byWebElement() { + executor.on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + var el = new webdriver.WebElement(driver, {ELEMENT: 1234}); + return driver.wait(until.ableToSwitchToFrame(el), 100); +} + + +function testUntilAbleToSwitchToFrame_byWebElementPromise() { + executor.on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + var el = new webdriver.WebElementPromise(driver, + webdriver.promise.fulfilled( + new webdriver.WebElement(driver, {ELEMENT: 1234}))); + return driver.wait(until.ableToSwitchToFrame(el), 100); +} + + +function testUntilAbleToSwitchToFrame_byLocator() { + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, { + status: bot.ErrorCode.SUCCESS, + value: [{ELEMENT: 1234}] + }); + }).on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 100); +} + + +function testUntilAbleToSwitchToFrame_byLocator_elementNotInitiallyFound() { + var foundResponses = [[], [], [{ELEMENT: 1234}]]; + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, { + status: bot.ErrorCode.SUCCESS, + value: foundResponses.shift() + }); + }).on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + return driver.wait(until.ableToSwitchToFrame(By.id('foo')), 2000) + .then(function() { + assertEquals(0, foundResponses.length); + }); +} + + +function testUntilAbleToSwitchToFrame_timesOutIfNeverAbletoSwitchFrames() { + var count = 0; + executor.on(CommandName.SWITCH_TO_FRAME, function(cmd, callback) { + count += 1; + callback(null, {status: bot.ErrorCode.NO_SUCH_FRAME}); + }); + + return driver.wait(until.ableToSwitchToFrame(0), 100).then(fail, function(e) { + assertTrue(count > 0); + assertTrue('Wrong message: ' + e.message, goog.string.startsWith( + e.message, 'Waiting to be able to switch to frame')); + }); +} + + +function testUntilAlertIsPresent_failsFastForNonAlertSwitchErrors() { + return driver.wait(until.alertIsPresent(), 100).then(fail, function(e) { + assertEquals( + 'Unsupported command: ' + CommandName.GET_ALERT_TEXT, e.message); + }); +} + + +function testUntilAlertIsPresent() { + var count = 0; + executor.on(CommandName.GET_ALERT_TEXT, function(cmd, callback) { + if (count++ < 3) { + callback(null, {status: bot.ErrorCode.NO_SUCH_ALERT}); + } else { + callback(null, {status: bot.ErrorCode.SUCCESS}); + } + }).on(CommandName.DISMISS_ALERT, function(cmd, callback) { + callback(null, {status: bot.ErrorCode.SUCCESS}); + }); + + return driver.wait(until.alertIsPresent(), 1000).then(function(alert) { + assertEquals(4, count); + return alert.dismiss(); + }); +} + + +function testUntilTitleIs() { + var titles = ['foo', 'bar', 'baz']; + executor.on(CommandName.GET_TITLE, function(cmd, callback) { + callback(null, bot.response.createResponse(titles.shift())); + }); + + return driver.wait(until.titleIs('bar'), 3000).then(function() { + assertArrayEquals(['baz'], titles); + }); +} + + +function testUntilTitleContains() { + var titles = ['foo', 'froogle', 'google']; + executor.on(CommandName.GET_TITLE, function(cmd, callback) { + callback(null, bot.response.createResponse(titles.shift())); + }); + + return driver.wait(until.titleContains('oogle'), 3000).then(function() { + assertArrayEquals(['google'], titles); + }); +} + + +function testUntilTitleMatches() { + var titles = ['foo', 'froogle', 'aaaabc', 'aabbbc', 'google']; + executor.on(CommandName.GET_TITLE, function(cmd, callback) { + callback(null, bot.response.createResponse(titles.shift())); + }); + + return driver.wait(until.titleMatches(/^a{2,3}b+c$/), 3000).then(function() { + assertArrayEquals(['google'], titles); + }); +} + + +function testUntilElementLocated() { + var responses = [[], [{ELEMENT: 'abc123'}, {ELEMENT: 'foo'}], ['end']]; + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, bot.response.createResponse(responses.shift())); + }); + + return driver.wait(until.elementLocated(By.id('quux')), 2000) + .then(function(el) { + return el.getId(); + }).then(function(id) { + assertArrayEquals([['end']], responses); + assertEquals('abc123', id['ELEMENT']); + }); +} + +function runNoElementFoundTest(locator, locatorStr) { + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, bot.response.createResponse([])); + }); + + function expectedFailure() { + fail('expected condition to timeout'); + } + + return driver.wait(until.elementLocated(locator), 100) + .then(expectedFailure, function(error) { + var expected = 'Waiting for element to be located ' + locatorStr; + var lines = error.message.split(/\n/, 2); + assertEquals(expected, lines[0]); + assertRegExp(/^Wait timed out after \d+ms$/, lines[1]); + }); +} + +function testUntilElementLocated_elementNeverFound_byLocator() { + return runNoElementFoundTest(By.id('quux'), 'By.id("quux")'); +} + +function testUntilElementLocated_elementNeverFound_byHash() { + return runNoElementFoundTest({id: 'quux'}, 'By.id("quux")'); +} + +function testUntilElementLocated_elementNeverFound_byFunction() { + return runNoElementFoundTest(goog.nullFunction, 'by function()'); +} + +function testUntilElementsLocated() { + var responses = [[], [{ELEMENT: 'abc123'}, {ELEMENT: 'foo'}], ['end']]; + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, bot.response.createResponse(responses.shift())); + }); + + return driver.wait(until.elementsLocated(By.id('quux')), 2000) + .then(function(els) { + return webdriver.promise.all(goog.array.map(els, function(el) { + return el.getId(); + })); + }).then(function(ids) { + assertArrayEquals([['end']], responses); + assertEquals(2, ids.length); + assertEquals('abc123', ids[0]['ELEMENT']); + assertEquals('foo', ids[1]['ELEMENT']); + }); +} + +function runNoElementsFoundTest(locator, locatorStr) { + executor.on(CommandName.FIND_ELEMENTS, function(cmd, callback) { + callback(null, bot.response.createResponse([])); + }); + + function expectedFailure() { + fail('expected condition to timeout'); + } + + return driver.wait(until.elementsLocated(locator), 100) + .then(expectedFailure, function(error) { + var expected = + 'Waiting for at least one element to be located ' + locatorStr; + var lines = error.message.split(/\n/, 2); + assertEquals(expected, lines[0]); + assertRegExp(/^Wait timed out after \d+ms$/, lines[1]); + }); +} + +function testUntilElementsLocated_noElementsEverFound_byLocator() { + return runNoElementsFoundTest(By.id('quux'), 'By.id("quux")'); +} + +function testUntilElementsLocated_noElementsEverFound_byHash() { + return runNoElementsFoundTest({id: 'quux'}, 'By.id("quux")'); +} + +function testUntilElementsLocated_noElementsEverFound_byFunction() { + return runNoElementsFoundTest(goog.nullFunction, 'by function()'); +} + +function testUntilStalenessOf() { + var responses = [ + bot.response.createResponse('body'), + bot.response.createResponse('body'), + bot.response.createResponse('body'), + bot.response.createErrorResponse( + new bot.Error(bot.ErrorCode.STALE_ELEMENT_REFERENCE, 'now stale')), + ['end'] + ]; + executor.on(CommandName.GET_ELEMENT_TAG_NAME, function(cmd, callback) { + callback(null, responses.shift()); + }); + + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return driver.wait(until.stalenessOf(el), 2000).then(function() { + assertArrayEquals([['end']], responses); + }); +} + +function runElementStateTest(predicate, command, responses) { + assertTrue(responses.length > 1); + + responses = goog.array.concat(responses, ['end']); + executor.on(command, function(cmd, callback) { + callback(null, bot.response.createResponse(responses.shift())); + }); + return driver.wait(predicate, 2000).then(function() { + assertArrayEquals(['end'], responses); + }); +} + +function testUntilElementIsVisible() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsVisible(el), + CommandName.IS_ELEMENT_DISPLAYED, [false, false, true]); +} + + +function testUntilElementIsNotVisible() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsNotVisible(el), + CommandName.IS_ELEMENT_DISPLAYED, [true, true, false]); +} + + +function testUntilElementIsEnabled() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsEnabled(el), + CommandName.IS_ELEMENT_ENABLED, [false, false, true]); +} + + +function testUntilElementIsDisabled() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsDisabled(el), + CommandName.IS_ELEMENT_ENABLED, [true, true, false]); +} + + +function testUntilElementIsSelected() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsSelected(el), + CommandName.IS_ELEMENT_SELECTED, [false, false, true]); +} + + +function testUntilElementIsNotSelected() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementIsNotSelected(el), + CommandName.IS_ELEMENT_SELECTED, [true, true, false]); +} + + +function testUntilElementTextIs() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementTextIs(el, 'foobar'), + CommandName.GET_ELEMENT_TEXT, ['foo', 'fooba', 'foobar']); +} + + +function testUntilElementTextContains() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementTextContains(el, 'bar'), + CommandName.GET_ELEMENT_TEXT, ['foo', 'foobaz', 'foobarbaz']); +} + + +function testUntilElementTextMatches() { + var el = new webdriver.WebElement(driver, {ELEMENT: 'foo'}); + return runElementStateTest(until.elementTextMatches(el, /fo+bar{3}/), + CommandName.GET_ELEMENT_TEXT, ['foo', 'foobar', 'fooobarrr']); +} diff --git a/lib/webdriver/test/webdriver_generator_test.js b/lib/webdriver/test/webdriver_generator_test.js new file mode 100644 index 0000000..7b04ea4 --- /dev/null +++ b/lib/webdriver/test/webdriver_generator_test.js @@ -0,0 +1,92 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.test.WebDriver.generator.test'); +goog.setTestOnly('webdriver.test.WebDriver.generator.test'); + +goog.require('goog.testing.jsunit'); +goog.require('webdriver.Session'); +goog.require('webdriver.WebDriver'); + + +var driver; + +function setUp() { + driver = new webdriver.WebDriver( + new webdriver.Session('test-session', {}), + new ExplodingExecutor()); +} + + +function testCanUseGeneratorsWithWebDriverCall() { + return driver.call(function* () { + var x = yield webdriver.promise.fulfilled(1); + var y = yield webdriver.promise.fulfilled(2); + return x + y; + }).then(function(value) { + assertEquals(3, value); + }); +} + + +function testCanDefineScopeOnGeneratorCall() { + return driver.call(function* () { + var x = yield webdriver.promise.fulfilled(1); + return this.name + x; + }, {name: 'Bob'}).then(function(value) { + assertEquals('Bob1', value); + }); +} + + +function testCanSpecifyArgsOnGeneratorCall() { + return driver.call(function* (a, b) { + var x = yield webdriver.promise.fulfilled(1); + var y = yield webdriver.promise.fulfilled(2); + return [x + y, a, b]; + }, null, 'abc', 123).then(function(value) { + assertArrayEquals([3, 'abc', 123], value); + }); +} + + +function testCanUseGeneratorWithWebDriverWait() { + var values = []; + return driver.wait(function* () { + yield values.push(1); + values.push(yield webdriver.promise.delayed(10).then(function() { + return 2; + })); + yield values.push(3); + return values.length === 6; + }, 250).then(function() { + assertArrayEquals([1, 2, 3, 1, 2, 3], values); + }); +} + + +/** + * @constructor + * @implements {webdriver.CommandExecutor} + */ +function ExplodingExecutor() {} + + +/** @override */ +ExplodingExecutor.prototype.execute = function(command, cb) { + cb(Error('Unsupported operation')); +}; diff --git a/lib/webdriver/test/webdriver_test.js b/lib/webdriver/test/webdriver_test.js new file mode 100644 index 0000000..5558c36 --- /dev/null +++ b/lib/webdriver/test/webdriver_test.js @@ -0,0 +1,2369 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.require('bot.ErrorCode'); +goog.require('goog.Promise'); +goog.require('goog.functions'); +goog.require('goog.testing.PropertyReplacer'); +goog.require('goog.testing.MockControl'); +goog.require('goog.testing.jsunit'); +goog.require('goog.userAgent'); +goog.require('webdriver.Capabilities'); +goog.require('webdriver.Command'); +goog.require('webdriver.CommandExecutor'); +goog.require('webdriver.CommandName'); +goog.require('webdriver.FileDetector'); +goog.require('webdriver.WebDriver'); +goog.require('webdriver.Serializable'); +goog.require('webdriver.Session'); +goog.require('webdriver.logging'); +goog.require('webdriver.promise'); +goog.require('webdriver.test.testutil'); + +var SESSION_ID = 'test_session_id'; + +var STUB_DRIVER = { + controlFlow: goog.nullFunction +}; + +// Alias some long names that interfere with test readability. +var CName = webdriver.CommandName, + ECode = bot.ErrorCode, + StubError = webdriver.test.testutil.StubError, + throwStubError = webdriver.test.testutil.throwStubError, + assertIsStubError = webdriver.test.testutil.assertIsStubError; + +// By is exported by webdriver.By, but IDEs don't recognize +// goog.exportSymbol. Explicitly define it here to make the +// IDE stop complaining. +var By = webdriver.By; + +var driver; +var flow; +var mockControl; +var uncaughtExceptions; + +function shouldRunTests() { + return !goog.userAgent.IE || goog.userAgent.isVersionOrHigher(10); +} + + +function setUp() { + mockControl = new goog.testing.MockControl(); + flow = webdriver.promise.controlFlow(); + uncaughtExceptions = []; + flow.on('uncaughtException', onUncaughtException); +} + + +function tearDown() { + return waitForIdle(flow).then(function() { + mockControl.$verifyAll(); + mockControl.$tearDown(); + + assertArrayEquals('There were uncaught exceptions', + [], uncaughtExceptions); + flow.reset(); + }); +} + + +function onUncaughtException(e) { + uncaughtExceptions.push(e); +} + + +function waitForIdle(opt_flow) { + var theFlow = opt_flow || flow; + return new goog.Promise(function(fulfill, reject) { + if (!theFlow.activeFrame_ && !theFlow.yieldCount_) { + fulfill(); + return; + } + theFlow.once('idle', fulfill); + theFlow.once('uncaughtException', reject); + }); +} + + +function waitForAbort(opt_flow, opt_n) { + var n = opt_n || 1; + var theFlow = opt_flow || flow; + theFlow.removeAllListeners( + webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION); + return new goog.Promise(function(fulfill, reject) { + theFlow.once('idle', function() { + reject(Error('expected flow to report an unhandled error')); + }); + + var errors = []; + theFlow.on('uncaughtException', onError); + function onError(e) { + errors.push(e); + if (errors.length === n) { + theFlow.removeListener('uncaughtException', onError); + fulfill(n === 1 ? errors[0] : errors); + } + } + }); +} + + +function expectedError(code, message) { + return function(e) { + assertEquals('Wrong error message', message, e.message); + assertEquals('Wrong error code', code, e.code); + }; +} + + +function createCommandMatcher(commandName, parameters) { + return new goog.testing.mockmatchers.ArgumentMatcher(function(actual) { + assertEquals('wrong name', commandName, actual.getName()); + var differences = goog.testing.asserts.findDifferences( + parameters, actual.getParameters()); + assertNull( + 'Wrong parameters for "' + commandName + '"' + + '\n Expected: ' + JSON.stringify(parameters) + + '\n Actual: ' + JSON.stringify(actual.getParameters()), + differences); + return true; + }, commandName + '(' + JSON.stringify(parameters) + ')'); +} + + +TestHelper = function() { + this.executor = mockControl.createStrictMock(webdriver.CommandExecutor); +}; + + +TestHelper.prototype.expect = function(commandName, opt_parameters) { + return new TestHelper.Command(this, commandName, opt_parameters); +}; + + +TestHelper.prototype.replayAll = function() { + mockControl.$replayAll(); + return this; +}; + + +TestHelper.Command = function(testHelper, commandName, opt_parameters) { + this.helper_ = testHelper; + this.name_ = commandName; + this.toDo_ = null; + this.anyTimes_ = false; + this.times_ = 0; + this.sessionId_ = SESSION_ID; + this.withParameters(opt_parameters || {}); +}; + + +TestHelper.Command.prototype.withParameters = function(parameters) { + this.parameters_ = parameters; + if (this.name_ !== CName.NEW_SESSION) { + this.parameters_['sessionId'] = this.sessionId_; + } + return this; +}; + + +TestHelper.Command.prototype.buildExpectation_ = function() { + var commandMatcher = createCommandMatcher(this.name_, this.parameters_); + assertNotNull(this.toDo_); + var expectation = this.helper_.executor. + execute(commandMatcher, goog.testing.mockmatchers.isFunction). + $does(this.toDo_); + if (this.anyTimes_) { + assertEquals(0, this.times_); + expectation.$anyTimes(); + } + if (this.times_) { + assertFalse(this.anyTimes_); + expectation.$times(this.times_); + } +}; + + +TestHelper.Command.prototype.andReturn = function(code, opt_value) { + this.toDo_ = function(command, callback) { + callback(null, { + 'status': code, + 'sessionId': { + 'value': SESSION_ID + }, + 'value': goog.isDef(opt_value) ? opt_value : null + }); + }; + return this; +}; + + +TestHelper.Command.prototype.anyTimes = function() { + this.anyTimes_ = true; + return this; +}; + + +TestHelper.Command.prototype.times = function(n) { + this.times_ = n; + return this; +}; + + +TestHelper.Command.prototype.andReturnSuccess = function(opt_returnValue) { + return this.andReturn(ECode.SUCCESS, opt_returnValue); +}; + + +TestHelper.Command.prototype.andReturnError = function(errCode, opt_value) { + return this.andReturn(errCode, opt_value); +}; + + +TestHelper.Command.prototype.replayAll = function() { + if (!this.toDo_) { + this.andReturnSuccess(null); + } + this.buildExpectation_(); + return this.helper_.replayAll(); +}; + + +TestHelper.Command.prototype.expect = function(name, opt_parameters) { + if (!this.toDo_) { + this.andReturnSuccess(null); + } + this.buildExpectation_(); + return this.helper_.expect(name, opt_parameters); +}; + + +/** + * @param {!(webdriver.Session|webdriver.promise.Promise)=} opt_session The + * session to use. + * @return {!webdriver.WebDriver} A new driver instance. + */ +TestHelper.prototype.createDriver = function(opt_session) { + var session = opt_session || new webdriver.Session(SESSION_ID, {}); + return new webdriver.WebDriver(session, this.executor); +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// Tests +// +////////////////////////////////////////////////////////////////////////////// + +function testAttachToSession_sessionIsAvailable() { + var testHelper = new TestHelper(). + expect(CName.DESCRIBE_SESSION). + withParameters({'sessionId': SESSION_ID}). + andReturnSuccess({'browserName': 'firefox'}). + replayAll(); + + var driver = webdriver.WebDriver.attachToSession(testHelper.executor, + SESSION_ID); + return driver.getSession().then(function(session) { + webdriver.test.testutil.assertObjectEquals({ + 'value':'test_session_id' + }, session.getId()); + assertEquals('firefox', session.getCapability('browserName')); + }); +} + + +function testAttachToSession_failsToGetSessionInfo() { + var testHelper = new TestHelper(). + expect(CName.DESCRIBE_SESSION). + withParameters({'sessionId': SESSION_ID}). + andReturnError(ECode.UNKNOWN_ERROR, {'message': 'boom'}). + replayAll(); + + var driver = webdriver.WebDriver.attachToSession(testHelper.executor, + SESSION_ID); + return driver.getSession().then(fail, function(e) { + assertEquals(bot.ErrorCode.UNKNOWN_ERROR, e.code); + assertEquals('boom', e.message); + }); +} + + +function testAttachToSession_usesActiveFlowByDefault() { + var testHelper = new TestHelper(). + expect(CName.DESCRIBE_SESSION). + withParameters({'sessionId': SESSION_ID}). + andReturnSuccess({}). + replayAll(); + + var driver = webdriver.WebDriver.attachToSession(testHelper.executor, + SESSION_ID); + assertEquals(driver.controlFlow(), webdriver.promise.controlFlow()); + + return waitForIdle(driver.controlFlow()); +} + + +function testAttachToSession_canAttachInCustomFlow() { + var testHelper = new TestHelper(). + expect(CName.DESCRIBE_SESSION). + withParameters({'sessionId': SESSION_ID}). + andReturnSuccess({}). + replayAll(); + + var otherFlow = new webdriver.promise.ControlFlow(); + var driver = webdriver.WebDriver.attachToSession( + testHelper.executor, SESSION_ID, otherFlow); + assertEquals(otherFlow, driver.controlFlow()); + assertNotEquals(otherFlow, webdriver.promise.controlFlow()); + + return waitForIdle(otherFlow); +} + + +function testCreateSession_happyPathWithCapabilitiesHashObject() { + var testHelper = new TestHelper(). + expect(CName.NEW_SESSION). + withParameters({ + 'desiredCapabilities': {'browserName': 'firefox'} + }). + andReturnSuccess({'browserName': 'firefox'}). + replayAll(); + + var driver = webdriver.WebDriver.createSession(testHelper.executor, { + 'browserName': 'firefox' + }); + return driver.getSession().then(function(session) { + webdriver.test.testutil.assertObjectEquals({ + 'value':'test_session_id' + }, session.getId()); + assertEquals('firefox', session.getCapability('browserName')); + }); +} + + +function testCreateSession_happyPathWithCapabilitiesInstance() { + var testHelper = new TestHelper(). + expect(CName.NEW_SESSION). + withParameters({ + 'desiredCapabilities': {'browserName': 'firefox'} + }). + andReturnSuccess({'browserName': 'firefox'}). + replayAll(); + + var driver = webdriver.WebDriver.createSession( + testHelper.executor, webdriver.Capabilities.firefox()); + return driver.getSession().then(function(session) { + webdriver.test.testutil.assertObjectEquals({ + 'value':'test_session_id' + }, session.getId()); + assertEquals('firefox', session.getCapability('browserName')); + }); +} + + +function testCreateSession_failsToCreateSession() { + var testHelper = new TestHelper(). + expect(CName.NEW_SESSION). + withParameters({ + 'desiredCapabilities': {'browserName': 'firefox'} + }). + andReturnError(ECode.UNKNOWN_ERROR, {'message': 'boom'}). + replayAll(); + + var driver = webdriver.WebDriver.createSession(testHelper.executor, { + 'browserName': 'firefox' + }); + return driver.getSession().then(fail, function(e) { + assertEquals(bot.ErrorCode.UNKNOWN_ERROR, e.code); + assertEquals('boom', e.message); + }); +} + + +function testCreateSession_usesActiveFlowByDefault() { + var testHelper = new TestHelper(). + expect(CName.NEW_SESSION). + withParameters({'desiredCapabilities': {}}). + andReturnSuccess({}). + replayAll(); + + var driver = webdriver.WebDriver.createSession(testHelper.executor, {}); + assertEquals(webdriver.promise.controlFlow(), driver.controlFlow()); + + return waitForIdle(driver.controlFlow()); +} + + +function testCreateSession_canCreateInCustomFlow() { + var testHelper = new TestHelper(). + expect(CName.NEW_SESSION). + withParameters({'desiredCapabilities': {}}). + andReturnSuccess({}). + replayAll(); + + var otherFlow = new webdriver.promise.ControlFlow(goog.global); + var driver = webdriver.WebDriver.createSession( + testHelper.executor, {}, otherFlow); + assertEquals(otherFlow, driver.controlFlow()); + assertNotEquals(otherFlow, webdriver.promise.controlFlow()); + + return waitForIdle(otherFlow); +} + + +function testToWireValue_function() { + var fn = function() { return 'foo'; }; + return webdriver.WebDriver.toWireValue_(fn).then(function(value) { + assertEquals(fn + '', value); + }); +} + + +function testToWireValue_date() { + if (goog.userAgent.IE) { + return; // Because IE... + } + return webdriver.WebDriver.toWireValue_(new Date(605728511546)). + then(function(value) { + assertEquals('1989-03-12T17:55:11.546Z', value); + }); +} + + +function testToWireValue_simpleObject() { + var expected = {'sessionId': 'foo'}; + return webdriver.WebDriver.toWireValue_({ + 'sessionId': new webdriver.Session('foo', {}) + }).then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testToWireValue_nestedObject() { + var expected = {'sessionId': {'value': 'foo'}}; + return webdriver.WebDriver.toWireValue_({ + 'sessionId': { + 'value': new webdriver.Session('foo', {}) + } + }).then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testToWireValue_capabilities() { + var prefs = new webdriver.logging.Preferences(); + prefs.setLevel(webdriver.logging.Type.BROWSER, + webdriver.logging.Level.DEBUG); + + var caps = webdriver.Capabilities.chrome(); + caps.set(webdriver.Capability.LOGGING_PREFS, prefs); + + return webdriver.WebDriver.toWireValue_(caps).then(function(actual) { + webdriver.test.testutil.assertObjectEquals({ + 'browserName': 'chrome', + 'loggingPrefs': { + 'browser': 'DEBUG' + } + }, actual); + }); +} + + +function testToWireValue_webElement() { + var expected = {}; + expected[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var element = new webdriver.WebElement(STUB_DRIVER, expected); + return webdriver.WebDriver.toWireValue_(element).then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testToWireValue_webElementPromise() { + var expected = {}; + expected[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var element = new webdriver.WebElement(STUB_DRIVER, expected); + var elementPromise = new webdriver.WebElementPromise(STUB_DRIVER, + webdriver.promise.fulfilled(element)); + return webdriver.WebDriver.toWireValue_(elementPromise). + then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testToWireValue_domElement() { + assertThrows( + goog.partial(webdriver.WebDriver.toWireValue_, document.body)); +} + + +function testToWireValue_serializableObject() { + /** + * @constructor + * @extends {webdriver.Serializable} + */ + var CustomSerializable = function () { + webdriver.Serializable.call(this); + }; + goog.inherits(CustomSerializable, webdriver.Serializable); + + /** @override */ + CustomSerializable.prototype.serialize = function() { + return webdriver.promise.fulfilled({ + name: webdriver.promise.fulfilled('bob'), + age: 30 + }); + }; + + var obj = new CustomSerializable(); + return webdriver.WebDriver.toWireValue_(obj). + then(function(actual) { + webdriver.test.testutil.assertObjectEquals( + {name: 'bob', age: 30}, actual); + }); +} + + +function testToWireValue_simpleArray() { + var expected = ['foo']; + return webdriver.WebDriver.toWireValue_([new webdriver.Session('foo', {})]). + then(function(actual) { + assertArrayEquals(expected, actual); + }); +} + + +function testToWireValue_arrayWithWebElement() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var element = new webdriver.WebElement(STUB_DRIVER, elementJson); + return webdriver.WebDriver.toWireValue_([element]). + then(function(actual) { + assertTrue(goog.isArray(actual)); + assertEquals(1, actual.length); + webdriver.test.testutil.assertObjectEquals(elementJson, actual[0]); + }); +} + + +function testToWireValue_arrayWithWebElementPromise() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var element = new webdriver.WebElement(STUB_DRIVER, elementJson); + var elementPromise = new webdriver.WebElementPromise(STUB_DRIVER, + webdriver.promise.fulfilled(element)); + + return webdriver.WebDriver.toWireValue_([elementPromise]). + then(function(actual) { + assertTrue(goog.isArray(actual)); + assertEquals(1, actual.length); + webdriver.test.testutil.assertObjectEquals(elementJson, actual[0]); + }); +} + + +function testToWireValue_complexArray() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + var expected = ['abc', 123, true, elementJson, [123, {'foo': 'bar'}]]; + + var element = new webdriver.WebElement(STUB_DRIVER, elementJson); + var input = ['abc', 123, true, element, [123, {'foo': 'bar'}]]; + return webdriver.WebDriver.toWireValue_(input). + then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testToWireValue_arrayWithNestedPromises() { + return webdriver.WebDriver.toWireValue_([ + 'abc', + webdriver.promise.fulfilled([ + 123, + webdriver.promise.fulfilled(true) + ]) + ]).then(function(actual) { + assertEquals(2, actual.length); + assertEquals('abc', actual[0]); + assertArrayEquals([123, true], actual[1]); + }); +} + + +function testToWireValue_complexHash() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + var expected = { + 'script': 'return 1', + 'args': ['abc', 123, true, elementJson, [123, {'foo': 'bar'}]], + 'sessionId': 'foo' + }; + + var element = new webdriver.WebElement(STUB_DRIVER, elementJson); + var parameters = { + 'script': 'return 1', + 'args':['abc', 123, true, element, [123, {'foo': 'bar'}]], + 'sessionId': new webdriver.Session('foo', {}) + }; + + return webdriver.WebDriver.toWireValue_(parameters). + then(function(actual) { + webdriver.test.testutil.assertObjectEquals(expected, actual); + }); +} + + +function testFromWireValue_primitives() { + assertEquals(1, webdriver.WebDriver.fromWireValue_({}, 1)); + assertEquals('', webdriver.WebDriver.fromWireValue_({}, '')); + assertEquals(true, webdriver.WebDriver.fromWireValue_({}, true)); + + assertUndefined(webdriver.WebDriver.fromWireValue_({}, undefined)); + assertNull(webdriver.WebDriver.fromWireValue_({}, null)); +} + + +function testFromWireValue_webElements() { + var json = {}; + json[webdriver.WebElement.ELEMENT_KEY] = 'foo'; + + var element = webdriver.WebDriver.fromWireValue_(STUB_DRIVER, json); + assertEquals(STUB_DRIVER, element.getDriver()); + + return element.getId().then(function(id) { + webdriver.test.testutil.assertObjectEquals(json, id); + }); +} + + +function testFromWireValue_simpleObject() { + var json = {'sessionId': 'foo'}; + var out = webdriver.WebDriver.fromWireValue_({}, json); + webdriver.test.testutil.assertObjectEquals(json, out); +} + + +function testFromWireValue_nestedObject() { + var json = {'foo': {'bar': 123}}; + var out = webdriver.WebDriver.fromWireValue_({}, json); + webdriver.test.testutil.assertObjectEquals(json, out); +} + + +function testFromWireValue_array() { + var json = [{'foo': {'bar': 123}}]; + var out = webdriver.WebDriver.fromWireValue_({}, json); + webdriver.test.testutil.assertObjectEquals(json, out); +} + + +function testFromWireValue_passesThroughFunctionProperties() { + var json = [{'foo': {'bar': 123}, 'func': goog.nullFunction}]; + var out = webdriver.WebDriver.fromWireValue_({}, json); + webdriver.test.testutil.assertObjectEquals(json, out); +} + + +function testDoesNotExecuteCommandIfSessionDoesNotResolve() { + var session = webdriver.promise.rejected(new StubError); + var testHelper = new TestHelper().replayAll(); + testHelper.createDriver(session).getTitle(); + return waitForAbort().then(assertIsStubError); +} + + +function testCommandReturnValuesArePassedToFirstCallback() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE).andReturnSuccess('Google Search'). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.getTitle().then(function(title) { + assertEquals('Google Search', title); + }); +} + + +function testStopsCommandExecutionWhenAnErrorOccurs() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW). + withParameters({'name': 'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message': 'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.switchTo().window('foo'); + driver.getTitle(); // mock should blow if this gets executed + + return waitForAbort(). + then(expectedError(ECode.NO_SUCH_WINDOW, 'window not found')); +} + + +function testCanSuppressCommandFailures() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW). + withParameters({'name': 'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message': 'window not found'}). + expect(CName.GET_TITLE). + andReturnSuccess('Google Search'). + replayAll(); + + var driver = testHelper.createDriver(); + driver.switchTo().window('foo').thenCatch(function(e) { + assertEquals(ECode.NO_SUCH_WINDOW, e.code); + assertEquals('window not found', e.message); + }); + driver.getTitle(); + return waitForIdle(); +} + + +function testErrorsPropagateUpToTheRunningApplication() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW). + withParameters({'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message': 'window not found'}). + replayAll(); + + testHelper.createDriver().switchTo().window('foo'); + return waitForAbort(). + then(expectedError(ECode.NO_SUCH_WINDOW, 'window not found')); +} + + +function testErrbacksThatReturnErrorsStillSwitchToCallbackChain() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW). + withParameters({'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.switchTo().window('foo'). + thenCatch(function() { return new StubError; }); + then(assertIsStubError); +} + + +function testErrbacksThrownCanOverrideOriginalError() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW, {'name': 'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.switchTo().window('foo').thenCatch(throwStubError); + + return waitForAbort().then(assertIsStubError); +} + + +function testCannotScheduleCommandsIfTheSessionIdHasBeenDeleted() { + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + delete driver.session_; + assertThrows(goog.bind(driver.get, driver, 'http://www.google.com')); +} + + +function testDeletesSessionIdAfterQuitting() { + var driver; + var testHelper = new TestHelper(). + expect(CName.QUIT). + replayAll(); + + driver = testHelper.createDriver(); + return driver.quit().then(function() { + assertUndefined('Session ID should have been deleted', driver.session_); + }); +} + + +function testReportsErrorWhenExecutingCommandsAfterExecutingAQuit() { + var testHelper = new TestHelper(). + expect(CName.QUIT). + replayAll(); + + var driver = testHelper.createDriver(); + driver.quit(); + driver.get('http://www.google.com'); + return waitForAbort(). + then(expectedError(undefined, + 'This driver instance does not have a valid session ID ' + + '(did you call WebDriver.quit()?) and may no longer be used.')); +} + + +function testCallbackCommandsExecuteBeforeNextCommand() { + var testHelper = new TestHelper(). + expect(CName.GET_CURRENT_URL). + expect(CName.GET, {'url': 'http://www.google.com'}). + expect(CName.CLOSE). + expect(CName.GET_TITLE). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getCurrentUrl().then(function() { + driver.get('http://www.google.com').then(function() { + driver.close(); + }); + }); + driver.getTitle(); + + return waitForIdle(); +} + + +function testEachCallbackFrameRunsToCompletionBeforeTheNext() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.GET_CURRENT_URL). + expect(CName.GET_CURRENT_WINDOW_HANDLE). + expect(CName.CLOSE). + expect(CName.QUIT). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle(). + // Everything in this callback... + then(function() { + driver.getCurrentUrl(); + driver.getWindowHandle(); + }). + // ...should execute before everything in this callback. + then(function() { + driver.close(); + }); + // This should execute after everything above + driver.quit(); + + return waitForIdle(); +} + + +function testNestedCommandFailuresBubbleUpToGlobalHandlerIfUnsuppressed() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.SWITCH_TO_WINDOW, {'name': 'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle().then(function() { + driver.switchTo().window('foo'); + }); + + return waitForAbort(). + then(expectedError(ECode.NO_SUCH_WINDOW, 'window not found')); +} + + +function testNestedCommandFailuresCanBeSuppressWhenTheyOccur() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.SWITCH_TO_WINDOW, {'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + expect(CName.CLOSE). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle().then(function() { + driver.switchTo().window('foo').thenCatch(goog.nullFunction); + }); + driver.close(); + + return waitForIdle(); +} + + +function testNestedCommandFailuresBubbleUpThroughTheFrameStack() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.SWITCH_TO_WINDOW, {'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle(). + then(function() { + return driver.switchTo().window('foo'); + }). + thenCatch(function(e) { + assertEquals(ECode.NO_SUCH_WINDOW, e.code); + assertEquals('window not found', e.message); + }); + + return waitForIdle(); +} + + +function testNestedCommandFailuresCanBeCaughtAndSuppressed() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.GET_CURRENT_URL). + expect(CName.SWITCH_TO_WINDOW, {'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + expect(CName.CLOSE). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle().then(function() { + driver.getCurrentUrl(). + then(function() { + return driver.switchTo().window('foo'); + }). + thenCatch(goog.nullFunction); + driver.close(); + }); + + return waitForIdle(); +} + + +function testReturningADeferredResultFromACallback() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.GET_CURRENT_URL). + andReturnSuccess('http://www.google.com'). + replayAll(); + + var driver = testHelper.createDriver(); + driver.getTitle(). + then(function() { + return driver.getCurrentUrl(); + }). + then(function(value) { + assertEquals('http://www.google.com', value); + }); + return waitForIdle(); +} + + +function testReturningADeferredResultFromAnErrbackSuppressesTheError() { + var count = 0; + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW, {'name':'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + expect(CName.GET_CURRENT_URL). + andReturnSuccess('http://www.google.com'). + replayAll(); + + var driver = testHelper.createDriver(); + driver.switchTo().window('foo'). + thenCatch(function(e) { + assertEquals(ECode.NO_SUCH_WINDOW, e.code); + assertEquals('window not found', e.message); + count += 1; + return driver.getCurrentUrl(); + }). + then(function(url) { + count += 1; + assertEquals('http://www.google.com', url); + }); + return waitForIdle().then(function() { + assertEquals(2, count); + }); +} + + +function testExecutingACustomFunctionThatReturnsANonDeferred() { + var testHelper = new TestHelper().replayAll(); + + var driver = testHelper.createDriver(); + return driver.call(goog.functions.constant('abc123')).then(function(value) { + assertEquals('abc123', value); + }); +} + + +function testExecutionOrderwithCustomFunctions() { + var msg = []; + var testHelper = new TestHelper(). + expect(CName.GET_TITLE).andReturnSuccess('cheese '). + expect(CName.GET_CURRENT_URL).andReturnSuccess('tasty'). + replayAll(); + + var driver = testHelper.createDriver(); + + var pushMsg = goog.bind(msg.push, msg); + driver.getTitle().then(pushMsg); + driver.call(goog.functions.constant('is ')).then(pushMsg); + driver.getCurrentUrl().then(pushMsg); + driver.call(goog.functions.constant('!')).then(pushMsg); + + return waitForIdle().then(function() { + assertEquals('cheese is tasty!', msg.join('')); + }); +} + + +function testPassingArgumentsToACustomFunction() { + var testHelper = new TestHelper().replayAll(); + + var add = function(a, b) { + return a + b; + }; + var driver = testHelper.createDriver(); + return driver.call(add, null, 1, 2).then(function(value) { + assertEquals(3, value); + }); +} + +function testPassingPromisedArgumentsToACustomFunction() { + var testHelper = new TestHelper().replayAll(); + + var promisedArg = webdriver.promise.fulfilled(2); + var add = function(a, b) { + return a + b; + }; + var driver = testHelper.createDriver(); + return driver.call(add, null, 1, promisedArg).then(function(value) { + assertEquals(3, value); + }); +} + +function testPassingArgumentsAndScopeToACustomFunction() { + function Foo(name) { + this.name = name; + } + Foo.prototype.getName = function() { + return this.name; + }; + var foo = new Foo('foo'); + + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + return driver.call(foo.getName, foo).then(function(value) { + assertEquals('foo', value); + }); +} + + +function testExecutingACustomFunctionThatThrowsAnError() { + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + return driver.call(goog.functions.error('bam!')).then(fail, function(e) { + assertTrue(e instanceof Error); + assertEquals('bam!', e.message); + }); +} + + +function testExecutingACustomFunctionThatSchedulesCommands() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.CLOSE). + expect(CName.QUIT). + replayAll(); + + var driver = testHelper.createDriver(); + driver.call(function() { + driver.getTitle(); + driver.close(); + }); + driver.quit(); + return waitForIdle(); +} + + +function testExecutingAFunctionThatReturnsATaskResultAfterSchedulingAnother() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + andReturnSuccess('Google Search'). + expect(CName.CLOSE). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.call(function() { + var title = driver.getTitle(); + driver.close(); + return title; + }).then(function(title) { + assertEquals('Google Search', title); + }); +} + + +function testExecutingACustomFunctionWhoseNestedCommandFails() { + var testHelper = new TestHelper(). + expect(CName.SWITCH_TO_WINDOW, {'name': 'foo'}). + andReturnError(ECode.NO_SUCH_WINDOW, {'message':'window not found'}). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.call(function() { + return driver.switchTo().window('foo'); + }).then(fail, function(e) { + assertEquals(ECode.NO_SUCH_WINDOW, e.code); + assertEquals('window not found', e.message); + }); +} + + +function testCustomFunctionDoesNotCompleteUntilReturnedPromiseIsResolved() { + var testHelper = new TestHelper().replayAll(); + + var order = []; + var driver = testHelper.createDriver(); + + var d = webdriver.promise.defer(); + d.promise.then(function() { + order.push('b'); + }); + + driver.call(function() { + order.push('a'); + return d.promise; + }); + driver.call(function() { + order.push('c'); + }); + + // timeout to ensure the first function starts its execution before we + // trigger d's callbacks. + return webdriver.promise.delayed(0).then(function() { + assertArrayEquals(['a'], order); + d.fulfill(); + return waitForIdle().then(function() { + assertArrayEquals(['a', 'b', 'c'], order); + }); + }); +} + + +function testNestedFunctionCommandExecutionOrder() { + var msg = []; + var testHelper = new TestHelper().replayAll(); + + var driver = testHelper.createDriver(); + driver.call(msg.push, msg, 'a'); + driver.call(function() { + driver.call(msg.push, msg, 'c'); + driver.call(function() { + driver.call(msg.push, msg, 'e'); + driver.call(msg.push, msg, 'f'); + }); + driver.call(msg.push, msg, 'd'); + }); + driver.call(msg.push, msg, 'b'); + return waitForIdle().then(function() { + assertEquals('acefdb', msg.join('')); + }); +} + + +function testExecutingNestedFunctionCommands() { + var msg = []; + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + var pushMsg = goog.bind(msg.push, msg); + driver.call(goog.functions.constant('cheese ')).then(pushMsg); + driver.call(function() { + driver.call(goog.functions.constant('is ')).then(pushMsg); + driver.call(goog.functions.constant('tasty')).then(pushMsg); + }); + driver.call(goog.functions.constant('!')).then(pushMsg); + return waitForIdle().then(function() { + assertEquals('cheese is tasty!', msg.join('')); + }); +} + + +function testReturnValuesFromNestedFunctionCommands() { + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + return driver.call(function() { + return driver.call(function() { + return driver.call(goog.functions.constant('foobar')); + }); + }).then(function(value) { + assertEquals('foobar', value); + }); +} + + +function testExecutingANormalCommandAfterNestedCommandsThatReturnsAnAction() { + var msg = []; + var testHelper = new TestHelper(). + expect(CName.CLOSE). + replayAll(); + var driver = testHelper.createDriver(); + driver.call(function() { + return driver.call(function() { + msg.push('a'); + return driver.call(goog.functions.constant('foobar')); + }); + }); + driver.close().then(function() { + msg.push('b'); + }); + return waitForIdle().then(function() { + assertEquals('ab', msg.join('')); + }); +} + + +function testNestedCommandErrorsBubbleUp_caught() { + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + var result = driver.call(function() { + return driver.call(function() { + return driver.call(goog.functions.error('bam!')); + }); + }).then(fail, expectedError(undefined, 'bam!')); + return goog.Promise.all([waitForIdle(), result]); +} + + +function testNestedCommandErrorsBubbleUp_uncaught() { + var testHelper = new TestHelper().replayAll(); + var driver = testHelper.createDriver(); + driver.call(function() { + return driver.call(function() { + return driver.call(goog.functions.error('bam!')); + }); + }); + return waitForAbort().then(expectedError(undefined, 'bam!')); +} + + +function testExecutingNestedCustomFunctionsThatSchedulesCommands() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE). + expect(CName.CLOSE). + replayAll(); + + var driver = testHelper.createDriver(); + driver.call(function() { + driver.call(function() { + driver.getTitle(); + }); + driver.close(); + }); + return waitForIdle(); +} + + +function testExecutingACustomFunctionThatReturnsADeferredAction() { + var testHelper = new TestHelper(). + expect(CName.GET_TITLE).andReturnSuccess('Google'). + replayAll(); + + var driver = testHelper.createDriver(); + driver.call(function() { + return driver.getTitle(); + }).then(function(title) { + assertEquals('Google', title); + }); + return waitForIdle(); +} + +function testWebElementPromise_resolvesWhenUnderlyingElementDoes() { + var el = new webdriver.WebElement(STUB_DRIVER, {'ELEMENT': 'foo'}); + var promise = webdriver.promise.fulfilled(el); + return new webdriver.WebElementPromise(STUB_DRIVER, promise). + then(function(e) { + assertEquals(e, el); + }); +} + +function testWebElement_resolvesBeforeCallbacksOnWireValueTrigger() { + var el = new webdriver.promise.Deferred(); + + var element = new webdriver.WebElementPromise(STUB_DRIVER, el.promise); + var messages = []; + + element.then(function() { + messages.push('element resolved'); + }); + element.getId().then(function() { + messages.push('wire value resolved'); + }); + + assertArrayEquals([], messages); + el.fulfill(new webdriver.WebElement(STUB_DRIVER, {'ELEMENT': 'foo'})); + return waitForIdle().then(function() { + assertArrayEquals([ + 'element resolved', + 'wire value resolved' + ], messages); + }); +} + +function testWebElement_isRejectedIfUnderlyingIdIsRejected() { + var element = new webdriver.WebElementPromise( + STUB_DRIVER, webdriver.promise.rejected(new StubError)); + return element.then(fail, assertIsStubError); +} + + +function testExecuteScript_nullReturnValue() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return document.body;', + 'args': [] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript('return document.body;').then(function(result) { + assertNull(result); + }); +} + + +function testExecuteScript_primitiveReturnValue() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return document.body;', + 'args': [] + }). + andReturnSuccess(123). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript('return document.body;').then(function(result) { + assertEquals(123, result); + }); +} + + +function testExecuteScript_webElementReturnValue() { + var json = {}; + json[webdriver.WebElement.ELEMENT_KEY] = 'foo'; + + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return document.body;', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript('return document.body;'). + then(function(webelement) { + return webdriver.promise.when(webelement.id_, function(id) { + webdriver.test.testutil.assertObjectEquals(id, json); + }); + }); +} + + +function testExecuteScript_arrayReturnValue() { + var json = [{}]; + json[0][webdriver.WebElement.ELEMENT_KEY] = 'foo'; + + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return document.body;', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript('return document.body;'). + then(function(array) { + return webdriver.promise.when(array[0].id_, function(id) { + webdriver.test.testutil.assertObjectEquals(id, json[0]); + }); + }); +} + + +function testExecuteScript_objectReturnValue() { + var json = {'foo':{}}; + json['foo'][webdriver.WebElement.ELEMENT_KEY] = 'foo'; + + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return document.body;', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + var callback; + return driver.executeScript('return document.body;'). + then(function(obj) { + return webdriver.promise.when(obj['foo'].id_, function(id) { + webdriver.test.testutil.assertObjectEquals(id, json['foo']); + }); + }); +} + + +function testExecuteScript_scriptAsFunction() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return (' + goog.nullFunction + + ').apply(null, arguments);', + 'args': [] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript(goog.nullFunction); +} + + +function testExecuteScript_simpleArgumentConversion() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return 1;', + 'args': ['abc', 123, true, [123, {'foo': 'bar'}]] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript( + 'return 1;', 'abc', 123, true, [123, {'foo': 'bar'}]); +} + + +function testExecuteScript_webElementArgumentConversion() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return 1;', + 'args': [elementJson] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.executeScript('return 1;', + new webdriver.WebElement(driver, elementJson)); +} + + +function testExecuteScript_webElementPromiseArgumentConversion() { + var elementJson = {'ELEMENT':'bar'}; + + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnSuccess(elementJson). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return 1;', + 'args': [elementJson] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.id('foo')); + return driver.executeScript('return 1;', element); +} + + +function testExecuteScript_argumentConversion() { + var elementJson = {}; + elementJson[webdriver.WebElement.ELEMENT_KEY] = 'fefifofum'; + + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'return 1;', + 'args': ['abc', 123, true, elementJson, [123, {'foo': 'bar'}]] + }). + andReturnSuccess(null). + replayAll(); + + var driver = testHelper.createDriver(); + var element = new webdriver.WebElement(driver, elementJson); + return driver.executeScript('return 1;', + 'abc', 123, true, element, [123, {'foo': 'bar'}]); +} + + +function testExecuteScript_scriptReturnsAnError() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT). + withParameters({ + 'script': 'throw Error(arguments[0]);', + 'args': ['bam'] + }). + andReturnError(ECode.UNKNOWN_ERROR, {'message':'bam'}). + replayAll(); + var driver = testHelper.createDriver(); + return driver.executeScript('throw Error(arguments[0]);', 'bam'). + then(fail, expectedError(ECode.UNKNOWN_ERROR, 'bam')); +} + + +function testExecuteScript_failsIfArgumentIsARejectedPromise() { + var testHelper = new TestHelper().replayAll(); + + var arg = webdriver.promise.rejected(new StubError); + arg.thenCatch(goog.nullFunction); // Suppress default handler. + + var driver = testHelper.createDriver(); + return driver.executeScript(goog.nullFunction, arg). + then(fail, assertIsStubError); +} + + +function testExecuteAsyncScript_failsIfArgumentIsARejectedPromise() { + var testHelper = new TestHelper().replayAll(); + + var arg = webdriver.promise.rejected(new StubError); + arg.thenCatch(goog.nullFunction); // Suppress default handler. + + var driver = testHelper.createDriver(); + return driver.executeAsyncScript(goog.nullFunction, arg). + then(fail, assertIsStubError); +} + + +function testFindElement_elementNotFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError(ECode.NO_SUCH_ELEMENT, { + 'message':'Unable to find element' + }). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.id('foo')); + element.click(); // This should never execute. + return waitForAbort().then( + expectedError(ECode.NO_SUCH_ELEMENT, 'Unable to find element')); +} + + +function testFindElement_elementNotFoundInACallback() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError( + ECode.NO_SUCH_ELEMENT, {'message':'Unable to find element'}). + replayAll(); + + var driver = testHelper.createDriver(); + webdriver.promise.fulfilled().then(function() { + var element = driver.findElement(By.id('foo')); + return element.click(); // Should not execute. + }); + return waitForAbort().then( + expectedError(ECode.NO_SUCH_ELEMENT, 'Unable to find element')); +} + + +function testFindElement_elementFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnSuccess({'ELEMENT':'bar'}). + expect(CName.CLICK_ELEMENT, {'id':{'ELEMENT':'bar'}}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.id('foo')); + element.click(); + return waitForIdle(); +} + + +function testFindElement_canUseElementInCallback() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnSuccess({'ELEMENT':'bar'}). + expect(CName.CLICK_ELEMENT, {'id':{'ELEMENT':'bar'}}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + driver.findElement(By.id('foo')).then(function(element) { + element.click(); + }); + return waitForIdle(); +} + + +function testFindElement_byJs() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': 'return document.body', + 'args': [] + }). + andReturnSuccess({'ELEMENT':'bar'}). + expect(CName.CLICK_ELEMENT, {'id':{'ELEMENT':'bar'}}). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.js('return document.body')); + element.click(); // just to make sure + return waitForIdle(); +} + + +function testFindElement_byJs_returnsNonWebElementValue() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, {'script': 'return 123', 'args': []}). + andReturnSuccess(123). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.js('return 123')); + element.click(); // Should not execute. + return waitForAbort().then(function(e) { + assertEquals( + 'Not the expected error message', + 'Custom locator did not return a WebElement', e.message); + }); +} + + +function testFindElement_byJs_canPassArguments() { + var script = 'return document.getElementsByTagName(arguments[0]);'; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': script, + 'args': ['div'] + }). + andReturnSuccess({'ELEMENT':'one'}). + replayAll(); + var driver = testHelper.createDriver(); + driver.findElement(By.js(script, 'div')); + return waitForIdle(); +} + + +function testFindElement_customLocator() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'tag name', 'value':'a'}). + andReturnSuccess([{'ELEMENT':'foo'}, {'ELEMENT':'bar'}]). + expect(CName.CLICK_ELEMENT, {'id':{'ELEMENT':'foo'}}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(function(d) { + assertEquals(driver, d); + return d.findElements(By.tagName('a')); + }); + element.click(); + return waitForIdle(); +} + + +function testFindElement_customLocatorThrowsIfResultIsNotAWebElement() { + var testHelper = new TestHelper().replayAll(); + + var driver = testHelper.createDriver(); + driver.findElement(function() { + return 1; + }); + return waitForAbort().then(function(e) { + assertEquals( + 'Not the expected error message', + 'Custom locator did not return a WebElement', e.message); + }); +} + + +function testIsElementPresent_elementNotFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([]). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.isElementPresent(By.id('foo')).then(assertFalse); +} + + +function testIsElementPresent_elementFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([{'ELEMENT':'bar'}]). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.isElementPresent(By.id('foo')).then(assertTrue); +} + + +function testIsElementPresent_letsErrorsPropagate() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnError(ECode.UNKNOWN_ERROR, {'message':'There is no spoon'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.isElementPresent(By.id('foo')); + return waitForAbort().then( + expectedError(ECode.UNKNOWN_ERROR, 'There is no spoon')); +} + + +function testIsElementPresent_byJs() { + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, {'script': 'return 123', 'args': []}). + andReturnSuccess([{'ELEMENT':'bar'}]). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.isElementPresent(By.js('return 123')).then(assertTrue); +} + + +function testIsElementPresent_byJs_canPassScriptArguments() { + var script = 'return document.getElementsByTagName(arguments[0]);'; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': script, + 'args': ['div'] + }). + andReturnSuccess({'ELEMENT':'one'}). + replayAll(); + + var driver = testHelper.createDriver(); + driver.isElementPresent(By.js(script, 'div')); + return waitForIdle(); +} + + +function testFindElements() { + var json = [ + {'ELEMENT':'foo'}, + {'ELEMENT':'bar'}, + {'ELEMENT':'baz'} + ]; + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'tag name', 'value':'a'}). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + driver.findElements(By.tagName('a')).then(function(elements) { + assertEquals(3, elements.length); + + var callbacks = new Array(3); + assertTypeAndId(0); + assertTypeAndId(1); + assertTypeAndId(2); + + return webdriver.promise.all(callbacks); + + function assertTypeAndId(index) { + assertTrue('Not a WebElement at index ' + index, + elements[index] instanceof webdriver.WebElement); + callbacks[index] = elements[index].getId().then(function(id) { + webdriver.test.testutil.assertObjectEquals(json[index], id); + }); + } + }); +} + + +function testFindElements_byJs() { + var json = [ + {'ELEMENT':'foo'}, + {'ELEMENT':'bar'}, + {'ELEMENT':'baz'} + ]; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': 'return document.getElementsByTagName("div");', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + + return driver. + findElements(By.js('return document.getElementsByTagName("div");')). + then(function(elements) { + var callbacks = new Array(3); + assertEquals(3, elements.length); + + assertTypeAndId(0); + assertTypeAndId(1); + assertTypeAndId(2); + return webdriver.promise.all(callbacks); + + function assertTypeAndId(index) { + assertTrue('Not a WebElement at index ' + index, + elements[index] instanceof webdriver.WebElement); + callbacks[index] = elements[index].getId().then(function(id) { + webdriver.test.testutil.assertObjectEquals(json[index], id); + }); + } + }); +} + + +function testFindElements_byJs_filtersOutNonWebElementResponses() { + var json = [ + {'ELEMENT':'foo'}, + 123, + 'a', + false, + {'ELEMENT':'bar'}, + {'not a web element': 1}, + {'ELEMENT':'baz'} + ]; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': 'return document.getElementsByTagName("div");', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + driver.findElements(By.js('return document.getElementsByTagName("div");')). + then(function(elements) { + assertEquals(3, elements.length); + var callbacks = new Array(3); + assertTypeAndId(0, 0); + assertTypeAndId(1, 4); + assertTypeAndId(2, 6); + return webdriver.promise.all(callbacks); + + function assertTypeAndId(index, jsonIndex) { + assertTrue('Not a WebElement at index ' + index, + elements[index] instanceof webdriver.WebElement); + callbacks[index] = elements[index].getId().then(function(id) { + webdriver.test.testutil.assertObjectEquals(json[jsonIndex], id); + }); + } + }); + return waitForIdle(); +} + + +function testFindElements_byJs_convertsSingleWebElementResponseToArray() { + var json = {'ELEMENT':'foo'}; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': 'return document.getElementsByTagName("div");', + 'args': [] + }). + andReturnSuccess(json). + replayAll(); + + var driver = testHelper.createDriver(); + return driver. + findElements(By.js('return document.getElementsByTagName("div");')). + then(function(elements) { + assertEquals(1, elements.length); + assertTrue(elements[0] instanceof webdriver.WebElement); + return elements[0].getId().then(function(id) { + webdriver.test.testutil.assertObjectEquals(json, id); + }); + }); +} + + +function testFindElements_byJs_canPassScriptArguments() { + var script = 'return document.getElementsByTagName(arguments[0]);'; + var testHelper = new TestHelper(). + expect(CName.EXECUTE_SCRIPT, { + 'script': script, + 'args': ['div'] + }). + andReturnSuccess([{'ELEMENT':'one'}, {'ELEMENT':'two'}]). + replayAll(); + + var driver = testHelper.createDriver(); + driver.findElements(By.js(script, 'div')); + return waitForIdle(); +} + + +function testSendKeysConvertsVarArgsIntoStrings_simpleArgs() { + var testHelper = new TestHelper(). + expect(CName.SEND_KEYS_TO_ELEMENT, {'id':{'ELEMENT':'one'}, + 'value':['1','2','abc','3']}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + var element = new webdriver.WebElement(driver, {'ELEMENT': 'one'}); + element.sendKeys(1, 2, 'abc', 3); + return waitForIdle(); +} + + +function testSendKeysConvertsVarArgsIntoStrings_promisedArgs() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnSuccess({'ELEMENT':'one'}). + expect(CName.SEND_KEYS_TO_ELEMENT, {'id':{'ELEMENT':'one'}, + 'value':['abc', '123', 'def']}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.id('foo')); + element.sendKeys( + webdriver.promise.fulfilled('abc'), 123, + webdriver.promise.fulfilled('def')); + return waitForIdle(); +} + + +function testSendKeysWithAFileDetector() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnSuccess({'ELEMENT':'one'}). + expect(CName.SEND_KEYS_TO_ELEMENT, {'id':{'ELEMENT':'one'}, + 'value':['modified/path']}). + andReturnSuccess(). + replayAll(); + + var driver = testHelper.createDriver(); + + var mockDetector = mockControl.createStrictMock(webdriver.FileDetector); + mockDetector.handleFile(driver, 'original/path'). + $returns(webdriver.promise.fulfilled('modified/path')); + mockDetector.$replay(); + + driver.setFileDetector(mockDetector); + + var element = driver.findElement(By.id('foo')); + element.sendKeys('original/', 'path'); +} + +function testElementEquality_isReflexive() { + var a = new webdriver.WebElement(STUB_DRIVER, 'foo'); + return webdriver.WebElement.equals(a, a).then(assertTrue); +} + +function testElementEquals_doesNotSendRpcIfElementsHaveSameId() { + var a = new webdriver.WebElement(STUB_DRIVER, 'foo'), + b = new webdriver.WebElement(STUB_DRIVER, 'foo'), + c = new webdriver.WebElement(STUB_DRIVER, 'foo'); + return webdriver.promise.all([ + webdriver.WebElement.equals(a, b).then( + goog.partial(assertTrue, 'a should == b!')), + webdriver.WebElement.equals(b, a).then( + goog.partial(assertTrue, 'symmetry check failed')), + webdriver.WebElement.equals(a, c).then( + goog.partial(assertTrue, 'a should == c!')), + webdriver.WebElement.equals(b, c).then( + goog.partial(assertTrue, 'transitive check failed')) + ]); +} + +function testElementEquals_sendsRpcIfElementsHaveDifferentIds() { + var id1 = {'ELEMENT':'foo'}; + var id2 = {'ELEMENT':'bar'}; + var testHelper = new TestHelper(). + expect(CName.ELEMENT_EQUALS, {'id':id1, 'other':id2}). + andReturnSuccess(true). + replayAll(); + + var driver = testHelper.createDriver(); + var a = new webdriver.WebElement(driver, id1), + b = new webdriver.WebElement(driver, id2); + return webdriver.WebElement.equals(a, b).then(assertTrue); +} + + +function testElementEquals_failsIfAnInputElementCouldNotBeFound() { + var testHelper = new TestHelper().replayAll(); + + var id = webdriver.promise.rejected(new StubError); + id.thenCatch(goog.nullFunction); // Suppress default handler. + + var driver = testHelper.createDriver(); + var a = new webdriver.WebElement(driver, {'ELEMENT': 'foo'}); + var b = new webdriver.WebElementPromise(driver, id); + + return webdriver.WebElement.equals(a, b).then(fail, assertIsStubError); +} + + +function testWaiting_waitSucceeds() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([]). + times(2). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([{'ELEMENT':'bar'}]). + replayAll(); + + var driver = testHelper.createDriver(); + driver.wait(function() { + return driver.isElementPresent(By.id('foo')); + }, 200); + return waitForIdle(); +} + + +function testWaiting_waitTimesout_timeoutCaught() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([]). + anyTimes(). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.wait(function() { + return driver.isElementPresent(By.id('foo')); + }, 25).then(fail, function(e) { + assertEquals('Wait timed out after ', + e.message.substring(0, 'Wait timed out after '.length)); + }); +} + + +function testWaiting_waitTimesout_timeoutNotCaught() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENTS, {'using':'id', 'value':'foo'}). + andReturnSuccess([]). + anyTimes(). + replayAll(); + + var driver = testHelper.createDriver(); + driver.wait(function() { + return driver.isElementPresent(By.id('foo')); + }, 25); + return waitForAbort().then(function(e) { + assertEquals('Wait timed out after ', + e.message.substring(0, 'Wait timed out after '.length)); + }); +} + +function testInterceptsAndTransformsUnhandledAlertErrors() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError(ECode.UNEXPECTED_ALERT_OPEN, { + 'message': 'boom', + 'alert': {'text': 'hello'} + }). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.findElement(By.id('foo')).then(fail, function(e) { + assertTrue(e instanceof webdriver.UnhandledAlertError); + assertEquals('hello', e.getAlertText()); + }); +} + +function +testUnhandledAlertErrors_usesEmptyStringIfAlertTextOmittedFromResponse() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError(ECode.UNEXPECTED_ALERT_OPEN, {'message': 'boom'}). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.findElement(By.id('foo')).then(fail, function(e) { + assertTrue(e instanceof webdriver.UnhandledAlertError); + assertEquals('', e.getAlertText()); + }); +} + +function testAlertHandleResolvesWhenPromisedTextResolves() { + var promise = new webdriver.promise.Deferred(); + + var alert = new webdriver.AlertPromise(STUB_DRIVER, promise); + assertTrue(alert.isPending()); + + promise.fulfill(new webdriver.Alert(STUB_DRIVER, 'foo')); + return alert.getText().then(function(text) { + assertEquals('foo', text); + }); +} + + +function testWebElementsBelongToSameFlowAsParentDriver() { + var testHelper = new TestHelper() + .expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}) + .andReturnSuccess({'ELEMENT': 'abc123'}) + .replayAll(); + + var driver = testHelper.createDriver(); + var otherFlow = new webdriver.promise.ControlFlow(); + otherFlow.execute(function() { + driver.findElement({id: 'foo'}).then(function() { + assertEquals( + 'WebElement should belong to the same flow as its parent driver', + driver.controlFlow(), webdriver.promise.controlFlow()); + }); + }); + + assertNotEquals(otherFlow, driver.controlFlow); + return goog.Promise.all([ + waitForIdle(otherFlow), + waitForIdle(driver.controlFlow()) + ]); +} + + +function testSwitchToAlertThatIsNotPresent() { + var testHelper = new TestHelper() + .expect(CName.GET_ALERT_TEXT) + .andReturnError(ECode.NO_SUCH_ALERT, {'message': 'no alert'}) + .replayAll(); + + var driver = testHelper.createDriver(); + var alert = driver.switchTo().alert(); + alert.dismiss(); // Should never execute. + return waitForAbort().then(expectedError(ECode.NO_SUCH_ALERT, 'no alert')); +} + + +function testAlertsBelongToSameFlowAsParentDriver() { + var testHelper = new TestHelper() + .expect(CName.GET_ALERT_TEXT).andReturnSuccess('hello') + .replayAll(); + + var driver = testHelper.createDriver(); + var otherFlow = new webdriver.promise.ControlFlow(); + otherFlow.execute(function() { + driver.switchTo().alert().then(function() { + assertEquals( + 'Alert should belong to the same flow as its parent driver', + driver.controlFlow(), webdriver.promise.controlFlow()); + }); + }); + + assertNotEquals(otherFlow, driver.controlFlow); + return goog.Promise.all([ + waitForIdle(otherFlow), + waitForIdle(driver.controlFlow()) + ]); +} + +function testFetchingLogs() { + var testHelper = new TestHelper(). + expect(CName.GET_LOG, {'type': 'browser'}). + andReturnSuccess([ + new webdriver.logging.Entry( + webdriver.logging.Level.INFO, 'hello', 1234), + {'level': 'DEBUG', 'message': 'abc123', 'timestamp': 5678} + ]). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.manage().logs().get('browser').then(function(entries) { + assertEquals(2, entries.length); + + assertTrue(entries[0] instanceof webdriver.logging.Entry); + assertEquals(webdriver.logging.Level.INFO.value, entries[0].level.value); + assertEquals('hello', entries[0].message); + assertEquals(1234, entries[0].timestamp); + + assertTrue(entries[1] instanceof webdriver.logging.Entry); + assertEquals(webdriver.logging.Level.DEBUG.value, entries[1].level.value); + assertEquals('abc123', entries[1].message); + assertEquals(5678, entries[1].timestamp); + }); +} + + +function testCommandsFailIfInitialSessionCreationFailed() { + var testHelper = new TestHelper().replayAll(); + + var session = webdriver.promise.rejected(new StubError); + + var driver = testHelper.createDriver(session); + var navigateResult = driver.get('some-url').then(fail, assertIsStubError); + var quitResult = driver.quit().then(fail, assertIsStubError); + + return waitForIdle().then(function() { + return webdriver.promise.all(navigateResult, quitResult); + }); +} + + +function testWebElementCommandsFailIfInitialDriverCreationFailed() { + var testHelper = new TestHelper().replayAll(); + + var session = webdriver.promise.rejected(new StubError); + + var driver = testHelper.createDriver(session); + return driver.findElement(By.id('foo')).click(). + then(fail, assertIsStubError); +} + + +function testWebElementCommansFailIfElementCouldNotBeFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError(ECode.NO_SUCH_ELEMENT, + {'message':'Unable to find element'}). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.findElement(By.id('foo')).click(). + then(fail, + expectedError(ECode.NO_SUCH_ELEMENT, 'Unable to find element')); +} + + +function testCannotFindChildElementsIfParentCouldNotBeFound() { + var testHelper = new TestHelper(). + expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}). + andReturnError(ECode.NO_SUCH_ELEMENT, + {'message':'Unable to find element'}). + replayAll(); + + var driver = testHelper.createDriver(); + return driver.findElement(By.id('foo')) + .findElement(By.id('bar')) + .findElement(By.id('baz')) + .then(fail, + expectedError(ECode.NO_SUCH_ELEMENT, 'Unable to find element')); +} + + +function testActionSequenceFailsIfInitialDriverCreationFailed() { + var testHelper = new TestHelper().replayAll(); + + var session = webdriver.promise.rejected(new StubError); + + // Suppress the default error handler so we can verify it propagates + // to the perform() call below. + session.thenCatch(goog.nullFunction); + + return testHelper.createDriver(session). + actions(). + mouseDown(). + mouseUp(). + perform(). + thenCatch(assertIsStubError); +} + + +function testActionSequence_mouseMove_noElement() { + var testHelper = new TestHelper() + .expect(CName.MOVE_TO, {'xoffset': 0, 'yoffset': 125}) + .andReturnSuccess() + .replayAll(); + + return testHelper.createDriver(). + actions(). + mouseMove({x: 0, y: 125}). + perform(); +} + + +function testActionSequence_mouseMove_element() { + var testHelper = new TestHelper() + .expect(CName.FIND_ELEMENT, {'using':'id', 'value':'foo'}) + .andReturnSuccess({'ELEMENT': 'abc123'}) + .expect( + CName.MOVE_TO, {'element': 'abc123', 'xoffset': 0, 'yoffset': 125}) + .andReturnSuccess() + .replayAll(); + + var driver = testHelper.createDriver(); + var element = driver.findElement(By.id('foo')); + return driver.actions() + .mouseMove(element, {x: 0, y: 125}) + .perform(); +} + + +function testActionSequence_mouseDown() { + var testHelper = new TestHelper() + .expect(CName.MOUSE_DOWN, {'button': webdriver.Button.LEFT}) + .andReturnSuccess() + .replayAll(); + + return testHelper.createDriver(). + actions(). + mouseDown(). + perform(); +} + + +function testActionSequence() { + var testHelper = new TestHelper() + .expect(CName.FIND_ELEMENT, {'using':'id', 'value':'a'}) + .andReturnSuccess({'ELEMENT': 'id1'}) + .expect(CName.FIND_ELEMENT, {'using':'id', 'value':'b'}) + .andReturnSuccess({'ELEMENT': 'id2'}) + .expect(CName.SEND_KEYS_TO_ACTIVE_ELEMENT, + {'value': [webdriver.Key.SHIFT]}) + .andReturnSuccess() + .expect(CName.MOVE_TO, {'element': 'id1'}) + .andReturnSuccess() + .expect(CName.CLICK, {'button': webdriver.Button.LEFT}) + .andReturnSuccess() + .expect(CName.MOVE_TO, {'element': 'id2'}) + .andReturnSuccess() + .expect(CName.CLICK, {'button': webdriver.Button.LEFT}) + .andReturnSuccess() + .replayAll(); + + var driver = testHelper.createDriver(); + var element1 = driver.findElement(By.id('a')); + var element2 = driver.findElement(By.id('b')); + + return driver.actions() + .keyDown(webdriver.Key.SHIFT) + .click(element1) + .click(element2) + .perform(); +} + + +function testAlertCommandsFailIfAlertNotPresent() { + var testHelper = new TestHelper() + .expect(CName.GET_ALERT_TEXT) + .andReturnError(ECode.NO_SUCH_ALERT, {'message': 'no alert'}) + .replayAll(); + + var driver = testHelper.createDriver(); + var alert = driver.switchTo().alert(); + + var expectError = expectedError(ECode.NO_SUCH_ALERT, 'no alert'); + var callbacks = []; + for (var key in webdriver.Alert.prototype) { + if (webdriver.Alert.prototype.hasOwnProperty(key)) { + callbacks.push(key, alert[key].call(alert).thenCatch(expectError)); + } + } + + return waitForIdle().then(function() { + return webdriver.promise.all(callbacks); + }); +} diff --git a/lib/webdriver/testing/asserts.js b/lib/webdriver/testing/asserts.js index bbe979d..b8ba02f 100644 --- a/lib/webdriver/testing/asserts.js +++ b/lib/webdriver/testing/asserts.js @@ -1,16 +1,19 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Assertions and expectation utilities for use in WebDriver test @@ -373,7 +376,7 @@ webdriver.testing.Assertion.prototype.isFalse = function() { * @extends {webdriver.testing.Assertion} */ webdriver.testing.NegatedAssertion = function(value) { - goog.base(this, value); + webdriver.testing.NegatedAssertion.base(this, 'constructor', value); this.value = value; }; goog.inherits( @@ -384,11 +387,10 @@ goog.inherits( webdriver.testing.NegatedAssertion.prototype.apply = function( matcher, opt_message) { matcher = new goog.labs.testing.IsNotMatcher(matcher); - return goog.base(this, 'apply', matcher, opt_message); + return webdriver.testing.NegatedAssertion.base(this, 'apply', matcher, + opt_message); }; - - /** * Creates a new assertion. * @param {*} value The value to perform an assertion on. diff --git a/lib/webdriver/testing/client.js b/lib/webdriver/testing/client.js new file mode 100644 index 0000000..ff13842 --- /dev/null +++ b/lib/webdriver/testing/client.js @@ -0,0 +1,179 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.testing.Client'); + +goog.require('goog.json'); +goog.require('goog.net.XmlHttp'); + + + +/** + * The client responsible for publishing test events to the server. Each event + * will be published using a POST {@link goog.net.XmlHttp} request. The body of + * each request will be a JSON object with the following fields: + *
          + *
        • id: An identifier for this client, derived from the window + * locations' pathname. + *
        • type: The type of event. + *
        • data: A JSONObject whose contents will be specific to each event type. + *
        + * + * @param {Window=} opt_win The window to pull the path name from for this + * client. Defaults to the current window. + * @param {string=} opt_url The URL to publish test events to. Defaults to + * {@link webdriver.testing.Client.DEFAULT_URL}. + * @constructor + */ +webdriver.testing.Client = function(opt_win, opt_url) { + + /** @private {string} */ + this.id_ = (opt_win || window).location.pathname; + + /** @private {string} */ + this.url_ = opt_url || webdriver.testing.Client.DEFAULT_URL; +}; + + +/** + * Default URL to publish test events to. + * @type {string} + * @const + */ +webdriver.testing.Client.DEFAULT_URL = '/testevent'; + + +/** + * The types of events that may be published by a TestClient to the server. + * @enum {string} + * @private + */ +webdriver.testing.Client.EventType_ = { + + /** Sent to signal that a test suite has been fully initialized. */ + INIT: 'INIT', + + /** + * Sent when starting a new test. The data object will have the following + * fields: + * - name: The name of the test. + */ + START_TEST: 'START_TEST', + + /** + * Sent when an error has occurred. The data object will have the following + * fields: + * - message: The error message. + */ + ERROR: 'ERROR', + + /** + * Sent when all tests have completed. The data object will have the following + * fields: + * - isSuccess: Whether the tests succeeded. + * - report: A flat log for the test suite. + */ + RESULTS: 'RESULTS', + + /** + * Sent when there is a screenshot for the server to record. The included data + * object will have two fields: + * - name: A debug label for the screenshot. + * - data: The PNG screenshot as a base64 string. + */ + SCREENSHOT: 'SCREENSHOT' +}; + + +/** + * Sends a simple message to the server, notifying it that the test runner has + * been initialized. + */ +webdriver.testing.Client.prototype.sendInitEvent = function() { + this.sendEvent_(webdriver.testing.Client.EventType_.INIT); +}; + + +/** + * Sends an error event. + * @param {string} message The error message. + */ +webdriver.testing.Client.prototype.sendErrorEvent = function(message) { + this.sendEvent_(webdriver.testing.Client.EventType_.ERROR, { + 'message': message + }); +}; + + +/** + * Sends an event indicating that a new test has started. + * @param {string} name The name of the test. + */ +webdriver.testing.Client.prototype.sendTestStartedEvent = function(name) { + this.sendEvent_(webdriver.testing.Client.EventType_.START_TEST, { + 'name': name + }); +}; + + +/** + * Sends an event to the server indicating that tests have completed. + * @param {boolean} isSuccess Whether the tests finished successfully. + * @param {string} report The test log. + */ +webdriver.testing.Client.prototype.sendResultsEvent = function(isSuccess, + report) { + this.sendEvent_(webdriver.testing.Client.EventType_.RESULTS, { + 'isSuccess': isSuccess, + 'report': report + }); +}; + + +/** +* Sends a screenshot to be recorded by the server. +* @param {string} data The PNG screenshot as a base64 string. +* @param {string=} opt_name A debug label for the screenshot; if omitted, the +* server will generate a random name. +*/ +webdriver.testing.Client.prototype.sendScreenshotEvent = function(data, + opt_name) { + this.sendEvent_(webdriver.testing.Client.EventType_.SCREENSHOT, { + 'name': opt_name, + 'data': data + }); +}; + + +/** +* Sends an event to the server. +* @param {string} type The type of event to send. +* @param {!Object=} opt_data JSON data object to send with the event, if any. +* @private +*/ +webdriver.testing.Client.prototype.sendEvent_ = function(type, opt_data) { + var payload = goog.json.serialize({ + 'id': this.id_, + 'type': type, + 'data': opt_data || {} + }); + + var xhr = new goog.net.XmlHttp; + xhr.open('POST', this.url_, true); + xhr.send(payload); + // TODO: Log if the event was not sent properly. +}; diff --git a/lib/webdriver/testing/flowtester.js b/lib/webdriver/testing/flowtester.js new file mode 100644 index 0000000..db9ffff --- /dev/null +++ b/lib/webdriver/testing/flowtester.js @@ -0,0 +1,372 @@ +// Copyright 2013 Software Freedom Conservancy. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +goog.provide('webdriver.testing.Clock'); +goog.provide('webdriver.testing.promise.FlowTester'); + +goog.require('goog.array'); +goog.require('webdriver.promise.ControlFlow'); + + + +/** + * Describes an object that can be used to advance the clock and trigger + * timeouts registered on the global timing functions. + * @interface + */ +webdriver.testing.Clock = function() {}; + + +/** + * Advances the clock. + * @param {number=} opt_ms The number of milliseconds to advance the clock by. + * Defaults to 1 ms. + */ +webdriver.testing.Clock.prototype.tick = function(opt_ms) {}; + + + +/** + * Utility for writing unit tests against a + * {@link webdriver.promise.ControlFlow}. This class assumes the global + * timeout functions (e.g. setTimeout) have been replaced with test doubles. + * These doubles should allow registered timeouts to be triggered by + * calling {@code clock.tick()}. + * @param {!webdriver.testing.Clock} clock The fake clock to use. + * @param {{clearInterval: function(number), + * clearTimeout: function(number), + * setInterval: function(!Function, number): number, + * setTimeout: function(!Function, number): number}} timer + * The timer object to use for the application under test. + * @constructor + * @implements {goog.disposable.IDisposable} + */ +webdriver.testing.promise.FlowTester = function(clock, timer) { + + var self = this; + + /** @private {!webdriver.testing.Clock} */ + this.clock_ = clock; + + /** + * @private {!Array.<{isIdle: boolean, + * errors: !Array., + * flow: !webdriver.promise.ControlFlow}>} + */ + this.allFlows_ = []; + + /** @private {!webdriver.promise.ControlFlow} */ + this.flow_ = createFlow(); + + /** @private {!webdriver.promise.ControlFlow} */ + this.originalFlow_ = webdriver.promise.controlFlow(); + webdriver.promise.setDefaultFlow(this.flow_); + + /** + * @private {function(function(!webdriver.promise.ControlFlow)): + * !webdriver.promise.Promise} + */ + this.originalCreateFlow_ = webdriver.promise.createFlow; + + /** + * @param {function(!webdriver.promise.ControlFlow)} callback The entry point + * to the newly created flow. + * @return {!webdriver.promise.Promise} . + */ + webdriver.promise.createFlow = function(callback) { + var flow = createFlow(); + return flow.execute(function() { + return callback(flow); + }); + }; + + function createFlow() { + var record = { + isIdle: true, + errors: [], + flow: new webdriver.promise.ControlFlow(timer). + on(webdriver.promise.ControlFlow.EventType.IDLE, function() { + record.isIdle = true; + }). + on(webdriver.promise.ControlFlow.EventType.SCHEDULE_TASK, function() { + record.isIdle = false; + }). + on(webdriver.promise.ControlFlow.EventType.UNCAUGHT_EXCEPTION, + function(e) { + record.isIdle = true; + record.errors.push(e); + }) + }; + self.allFlows_.push(record); + return record.flow; + } +}; + + +/** @private {boolean} */ +webdriver.testing.promise.FlowTester.prototype.isDisposed_ = false; + + +/** @override */ +webdriver.testing.promise.FlowTester.prototype.isDisposed = function() { + return this.isDisposed_; +}; + + +/** + * Disposes of this instance, restoring the default control flow object. + * @override + */ +webdriver.testing.promise.FlowTester.prototype.dispose = function() { + if (!this.isDisposed_) { + goog.array.forEach(this.allFlows_, function(record) { + record.flow.reset(); + }); + webdriver.promise.setDefaultFlow(this.originalFlow_); + webdriver.promise.createFlow = this.originalCreateFlow_; + this.isDisposed_ = true; + } +}; + + +/** + * @param {!Array} messages The array to join into a single error message. + * @throws {Error} An error with a message formatted from {@code messages}. + * @private + */ +webdriver.testing.promise.FlowTester.prototype.throwWithMessages_ = function( + messages) { + throw Error(messages.join('\n--------\n') + '\n === done ==='); +}; + + +/** + * Verifies that all created flows are idle and completed without any errors. + * If a specific flow object is provided, will only check that flow. + * @param {webdriver.promise.ControlFlow=} opt_flow The specific flow to check. + * If not specified, will verify against all flows. + */ +webdriver.testing.promise.FlowTester.prototype.verifySuccess = function( + opt_flow) { + var messages = []; + var foundFlow = false; + + goog.array.forEach(this.allFlows_, function(record, index) { + if (!opt_flow || opt_flow === record.flow) { + foundFlow = true; + if (record.errors.length) { + messages = goog.array.concat( + messages, 'Uncaught errors for flow #' + index, + goog.array.map(record.errors, function(error) { + if (!error) { + error = error + ''; + } + return error.stack || error.message || String(error); + })); + } + + if (!record.isIdle) { + messages.push('Flow #' + index + ' is not idle'); + } + } + }); + + if (opt_flow && !foundFlow) { + messages.push('Specified flow not found!'); + } + + if (messages.length) { + this.throwWithMessages_(messages); + } +}; + + +/** + * Verifies that all flows are idle and at least one reported a single failure. + * If a specific flow object is provided, will only check that flow. + * @param {webdriver.promise.ControlFlow=} opt_flow The flow expected to have + * generated the error. + */ +webdriver.testing.promise.FlowTester.prototype.verifyFailure = function( + opt_flow) { + var messages = []; + var foundFlow = false; + var foundAFailure = false; + + goog.array.forEach(this.allFlows_, function(record, index) { + if (!opt_flow || opt_flow === record.flow) { + foundFlow = true; + + if (!record.isIdle) { + messages.push('Flow #' + index + ' is not idle'); + } + + if (record.errors.length) { + foundAFailure = true; + + if (record.errors.length > 1) { + messages = goog.array.concat( + messages, 'Flow #' + index + ' had multiple errors: ', + record.errors); + } + } + + } + }); + + if (opt_flow && !foundFlow) { + messages.push('Specified flow not found!'); + } + + if (!foundAFailure) { + messages.push('No failures found!'); + } + + if (messages.length) { + this.throwWithMessages_(messages); + } +}; + + +/** + * @param {webdriver.promise.ControlFlow=} opt_flow The specific control flow + * to check. If not specified, will implicitly verify there was a single + * error across all flows. + * @return {Error} The error reported by the application. + * @throws {Error} If the application is not finished, or did not report + * exactly one error. + */ +webdriver.testing.promise.FlowTester.prototype.getFailure = function( + opt_flow) { + this.verifyFailure(opt_flow); + + var errors = []; + goog.array.forEach(this.allFlows_, function(record) { + if (!opt_flow || opt_flow === record.flow) { + errors = goog.array.concat(errors, record.errors); + } + }); + + if (errors.length > 1) { + this.throwWithMessages_(goog.array.concat( + 'There was more than one failure', errors)); + } + + return errors[0]; +}; + + +/** + * Advances the clock so the {@link webdriver.promise.ControlFlow}'s event + * loop will run once. + */ +webdriver.testing.promise.FlowTester.prototype.turnEventLoop = function() { + this.clock_.tick(webdriver.promise.ControlFlow.EVENT_LOOP_FREQUENCY); +}; + + +/** + * @param {webdriver.promise.ControlFlow=} opt_flow The specific flow to check. + * If not specified, will verify all flows are still running. + * @throws {Error} If the application is not running. + */ +webdriver.testing.promise.FlowTester.prototype.assertStillRunning = function( + opt_flow) { + var messages = []; + var foundFlow = false; + + goog.array.forEach(this.allFlows_, function(record, index) { + if (!opt_flow || opt_flow === record.flow) { + foundFlow = true; + if (record.isIdle) { + messages.push('Flow #' + index + ' is idle'); + } + + if (record.errors.length) { + messages = goog.array.concat( + messages, 'Uncaught errors for flow #' + index, record.errors); + } + } + }); + + if (opt_flow && !foundFlow) { + messages.push('Specified flow not found!'); + } + + if (messages.length) { + this.throwWithMessages_(messages); + } +}; + + +/** + * Runs the application, turning its event loop until it is expected to have + * shutdown (as indicated by having no more frames, or no frames with pending + * tasks). + */ +webdriver.testing.promise.FlowTester.prototype.run = function() { + var flow = this.flow_; + var self = this; + var done = false; + var shouldBeDone = false; + + while (!done) { + this.turnEventLoop(); + if (shouldBeDone) { + assertIsDone(); + } else { + determineIfShouldBeDone(); + } + + // If the event loop generated an unhandled promise, it won't be reported + // until one more turn of the JS event loop, so we need to tick the + // clock once more. This is necessary for our tests to simulate a real + // JS environment. + this.clock_.tick(); + } + + function assertIsDone() { + // Shutdown is done in one extra turn of the event loop. + self.clock_.tick(); + done = goog.array.every(self.allFlows_, function(record) { + return record.isIdle; + }); + + if (!done) { + goog.array.forEach(self.allFlows_, function(record) { + if (record.flow.activeFrame_) { + // Not done yet, but there are no frames left. This can happen if the + // very first scheduled task was scheduled inside of a promise + // callback. Turn the event loop one more time; the flow should detect + // that it is now finished and start the shutdown procedure. Don't + // recurse here since we could go into an infinite loop if the flow is + // broken. + self.turnEventLoop(); + self.clock_.tick(); + } + }); + } + + if (!done) { + throw Error('Should be done now: ' + flow.getSchedule()); + } + } + + function determineIfShouldBeDone() { + shouldBeDone = flow === webdriver.promise.controlFlow() && + goog.array.every(self.allFlows_, function(record) { + return !record.flow.activeFrame_; + }); + } +}; diff --git a/lib/webdriver/testing/jsunit.js b/lib/webdriver/testing/jsunit.js new file mode 100644 index 0000000..bfb1365 --- /dev/null +++ b/lib/webdriver/testing/jsunit.js @@ -0,0 +1,363 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview File to include for turning any HTML file page into a WebDriver + * JSUnit test suite by configuring an onload listener to the body that will + * instantiate and start the test runner. + */ + +goog.provide('webdriver.testing.jsunit'); +goog.provide('webdriver.testing.jsunit.TestRunner'); + +goog.require('goog.testing.TestRunner'); +goog.require('webdriver.testing.Client'); +goog.require('webdriver.testing.TestCase'); + + + +/** + * Constructs a test runner. + * @param {!webdriver.testing.Client} client . + * @constructor + * @extends {goog.testing.TestRunner} + */ +webdriver.testing.jsunit.TestRunner = function(client) { + goog.base(this); + + /** @private {!webdriver.testing.Client} */ + this.client_ = client; +}; +goog.inherits(webdriver.testing.jsunit.TestRunner, goog.testing.TestRunner); + + +/** + * Element created in the document to add test results to. + * @private {Element} + */ +webdriver.testing.jsunit.TestRunner.prototype.logEl_ = null; + + +/** + * DOM element used to stored screenshots. Screenshots are stored in the DOM to + * avoid exhausting JS stack-space. + * @private {Element} + */ +webdriver.testing.jsunit.TestRunner.prototype.screenshotCacheEl_ = null; + + +/** @override */ +webdriver.testing.jsunit.TestRunner.prototype.initialize = function(testCase) { + goog.base(this, 'initialize', testCase); + this.screenshotCacheEl_ = document.createElement('div'); + document.body.appendChild(this.screenshotCacheEl_); + this.screenshotCacheEl_.style.display = 'none'; +}; + + +/** @override */ +webdriver.testing.jsunit.TestRunner.prototype.execute = function() { + if (!this.testCase) { + throw Error('The test runner must be initialized with a test case before ' + + 'execute can be called.'); + } + this.screenshotCacheEl_.innerHTML = ''; + this.client_.sendInitEvent(); + this.testCase.setCompletedCallback(goog.bind(this.onComplete_, this)); + this.testCase.runTests(); +}; + + +/** + * Writes a nicely formatted log out to the document. Overrides + * {@link goog.testing.TestRunner#writeLog} to handle writing screenshots to the + * log. + * @param {string} log The string to write. + * @override + */ +webdriver.testing.jsunit.TestRunner.prototype.writeLog = function(log) { + var lines = log.split('\n'); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var color; + var isFailOrError = /FAILED/.test(line) || /ERROR/.test(line); + var isScreenshot = / \[SCREENSHOT\] /.test(line); + if (/PASSED/.test(line)) { + color = 'darkgreen'; + } else if (isFailOrError) { + color = 'darkred'; + } else if (isScreenshot) { + color = 'darkblue'; + } else { + color = '#333'; + } + + var div = document.createElement('div'); + if (line.substr(0, 2) == '> ') { + // The stack trace may contain links so it has to be interpreted as HTML. + div.innerHTML = line; + } else { + div.appendChild(document.createTextNode(line)); + } + + if (isFailOrError) { + var testNameMatch = /(\S+) (\[[^\]]*] )?: (FAILED|ERROR)/.exec(line); + if (testNameMatch) { + // Build a URL to run the test individually. If this test was already + // part of another subset test, we need to overwrite the old runTests + // query parameter. We also need to do this without bringing in any + // extra dependencies, otherwise we could mask missing dependency bugs. + var newSearch = 'runTests=' + testNameMatch[1]; + var search = window.location.search; + if (search) { + var oldTests = /runTests=([^&]*)/.exec(search); + if (oldTests) { + newSearch = search.substr(0, oldTests.index) + + newSearch + + search.substr(oldTests.index + oldTests[0].length); + } else { + newSearch = search + '&' + newSearch; + } + } else { + newSearch = '?' + newSearch; + } + var href = window.location.href; + var hash = window.location.hash; + if (hash && hash.charAt(0) != '#') { + hash = '#' + hash; + } + href = href.split('#')[0].split('?')[0] + newSearch + hash; + + // Add the link. + var a = document.createElement('A'); + a.innerHTML = '(run individually)'; + a.style.fontSize = '0.8em'; + a.href = href; + div.appendChild(document.createTextNode(' ')); + div.appendChild(a); + } + } + + if (isScreenshot && this.screenshotCacheEl_.childNodes.length) { + var nextScreenshot = this.screenshotCacheEl_.childNodes[0]; + this.screenshotCacheEl_.removeChild(nextScreenshot); + + a = document.createElement('A'); + a.style.fontSize = '0.8em'; + a.href = 'javascript:void(0);'; + a.onclick = goog.partial(toggleVisibility, a, nextScreenshot); + toggleVisibility(a, nextScreenshot); + div.appendChild(document.createTextNode(' ')); + div.appendChild(a); + } + + div.style.color = color; + div.style.font = 'normal 100% monospace'; + + try { + div.style.whiteSpace = 'pre-wrap'; + } catch (e) { + // NOTE(user): IE raises an exception when assigning to pre-wrap. + // Thankfully, it doesn't collapse whitespace when using monospace fonts, + // so it will display correctly if we ignore the exception. + } + + if (i < 2) { + div.style.fontWeight = 'bold'; + } + this.logEl_.appendChild(div); + + if (nextScreenshot) { + a = document.createElement('A'); + // Accessing the |src| property in IE sometimes results in an + // "Invalid pointer" error, which indicates it has been garbage + // collected. This does not occur when using getAttribute. + a.href = nextScreenshot.getAttribute('src'); + a.target = '_blank'; + a.appendChild(nextScreenshot); + this.logEl_.appendChild(a); + nextScreenshot = null; + } + } + + function toggleVisibility(link, img) { + if (img.style.display === 'none') { + img.style.display = ''; + link.innerHTML = '(hide screenshot)'; + } else { + img.style.display = 'none'; + link.innerHTML = '(view screenshot)'; + } + } +}; + + +/** + * Copied from goog.testing.TestRunner.prototype.onComplete_, which has private + * visibility. + * @private + */ +webdriver.testing.jsunit.TestRunner.prototype.onComplete_ = function() { + var log = this.testCase.getReport(true); + if (this.errors.length > 0) { + log += '\n' + this.errors.join('\n'); + } + + if (!this.logEl_) { + this.logEl_ = document.createElement('div'); + document.body.appendChild(this.logEl_); + } + + // Remove all children from the log element. + var logEl = this.logEl_; + while (logEl.firstChild) { + logEl.removeChild(logEl.firstChild); + } + + this.writeLog(log); + this.client_.sendResultsEvent(this.isSuccess(), this.getReport(true)); +}; + + +/** + * Takes a screenshot. In addition to saving the screenshot for viewing in the + * HTML logs, the screenshot will also be saved using + * @param {!webdriver.WebDriver} driver The driver to take the screenshot with. + * @param {string=} opt_label An optional debug label to identify the screenshot + * with. + * @return {!webdriver.promise.Promise} A promise that will be resolved to the + * screenshot as a base-64 encoded PNG. + */ +webdriver.testing.jsunit.TestRunner.prototype.takeScreenshot = function( + driver, opt_label) { + if (!this.isInitialized()) { + throw Error( + 'The test runner must be initialized before it may be used to' + + ' take screenshots'); + } + + var client = this.client_; + var testCase = this.testCase; + var screenshotCache = this.screenshotCacheEl_; + return driver.takeScreenshot().then(function(png) { + client.sendScreenshotEvent(png, opt_label); + + var img = document.createElement('img'); + img.src = 'data:image/png;base64,' + png; + img.style.border = '1px solid black'; + img.style.maxWidth = '500px'; + screenshotCache.appendChild(img); + + if (testCase) { + testCase.saveMessage('[SCREENSHOT] ' + (opt_label || '')); + } + return png; + }); +}; + + +/** + * Sends a base64 encoded PNG image to the server to be saved in the test + * outputs. + * @param {string} data The base64 encoded PNG image to be sent to the server. + * @param {string=} opt_label An optional debug label to identify the + * screenshot with. + */ +webdriver.testing.jsunit.TestRunner.prototype.saveImage = function( + data, opt_label) { + if (!this.isInitialized()) { + throw Error( + 'The test runner must be initialized before it may be used to' + + ' save images'); + } + + this.client_.sendScreenshotEvent(data, opt_label); + + var img = document.createElement('img'); + img.src = 'data:image/png;base64,' + data; + img.style.border = '1px solid black'; + img.style.maxWidth = '500px'; + this.screenshotCacheEl_.appendChild(img); + + if (this.testCase) { + this.testCase.saveMessage('[SCREENSHOT] ' + (opt_label || '')); + } +}; + + +(function() { + var client = new webdriver.testing.Client(); + var tr = new webdriver.testing.jsunit.TestRunner(client); + + // Export our test runner so it can be accessed by Selenium/WebDriver. This + // will only work if webdriver.WebDriver is using a pure-JavaScript + // webdriver.CommandExecutor. Otherwise, the JS-client could change the + // driver's focus to another window or frame and the Java/Python-client + // wouldn't be able to access this object. + goog.exportSymbol('G_testRunner', tr); + goog.exportSymbol('G_testRunner.initialize', tr.initialize); + goog.exportSymbol('G_testRunner.isInitialized', tr.isInitialized); + goog.exportSymbol('G_testRunner.isFinished', tr.isFinished); + goog.exportSymbol('G_testRunner.isSuccess', tr.isSuccess); + goog.exportSymbol('G_testRunner.getReport', tr.getReport); + goog.exportSymbol('G_testRunner.getRunTime', tr.getRunTime); + goog.exportSymbol('G_testRunner.getNumFilesLoaded', tr.getNumFilesLoaded); + goog.exportSymbol('G_testRunner.setStrict', tr.setStrict); + goog.exportSymbol('G_testRunner.logTestFailure', tr.logTestFailure); + + // Export debug as a global function for JSUnit compatibility. This just + // calls log on the current test case. + if (!goog.global['debug']) { + goog.exportSymbol('debug', goog.bind(tr.log, tr)); + } + + // Add an error handler to report errors that may occur during + // initialization of the page. + var onerror = window.onerror; + window.onerror = function(error, url, line) { + // Call any existing onerror handlers. + if (onerror) { + onerror(error, url, line); + } + if (typeof error == 'object') { + // Webkit started passing an event object as the only argument to + // window.onerror. It doesn't contain an error message, url or line + // number. We therefore log as much info as we can. + if (error.target && error.target.tagName == 'SCRIPT') { + tr.logError('UNKNOWN ERROR: Script ' + error.target.src); + } else { + tr.logError('UNKNOWN ERROR: No error information available.'); + } + } else { + tr.logError('JS ERROR: ' + error + '\nURL: ' + url + '\nLine: ' + line); + } + }; + + var onload = window.onload; + window.onload = function() { + // Call any existing onload handlers. + if (onload) { + onload(); + } + + var testCase = new webdriver.testing.TestCase(client, document.title); + testCase.autoDiscoverTests(); + + tr.initialize(testCase); + tr.execute(); + }; +})(); diff --git a/lib/webdriver/testing/testcase.js b/lib/webdriver/testing/testcase.js new file mode 100644 index 0000000..8c2bc32 --- /dev/null +++ b/lib/webdriver/testing/testcase.js @@ -0,0 +1,168 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines a special test case that runs each test inside of a + * {@code webdriver.Application}. This allows each phase to schedule + * asynchronous actions that run to completion before the next phase of the + * test. + * + * This file requires the global {@code G_testRunner} to be initialized before + * use. This can be accomplished by also importing + * {@link webdriver.testing.jsunit}. This namespace is not required by default + * to improve interoperability with other namespaces that may initialize + * G_testRunner. + */ + +goog.provide('webdriver.testing.TestCase'); + +goog.require('goog.testing.TestCase'); +goog.require('webdriver.promise'); +/** @suppress {extraRequire} Imported for user convenience. */ +goog.require('webdriver.testing.asserts'); + + + +/** + * Constructs a test case that synchronizes each test case with the singleton + * {@code webdriver.promise.ControlFlow}. + * + * @param {!webdriver.testing.Client} client The test client to use for + * reporting test results. + * @param {string=} opt_name The name of the test case, defaults to + * 'Untitled Test Case'. + * @constructor + * @extends {goog.testing.TestCase} + */ +webdriver.testing.TestCase = function(client, opt_name) { + goog.base(this, opt_name); + + /** @private {!webdriver.testing.Client} */ + this.client_ = client; +}; +goog.inherits(webdriver.testing.TestCase, goog.testing.TestCase); + + +/** + * Executes the next test inside its own {@code webdriver.Application}. + * @override + */ +webdriver.testing.TestCase.prototype.cycleTests = function() { + var test = this.next(); + if (!test) { + this.finalize(); + return; + } + + goog.testing.TestCase.currentTestName = test.name; + this.result_.runCount++; + this.log('Running test: ' + test.name); + this.client_.sendTestStartedEvent(test.name); + + var self = this; + var hadError = false; + var app = webdriver.promise.controlFlow(); + + this.runSingleTest_(test, onError).then(function() { + hadError || self.doSuccess(test); + self.timeout(function() { + self.cycleTests(); + }, 100); + }); + + function onError(e) { + hadError = true; + self.doError(test, e); + // Note: result_ is a @protected field but still uses the trailing + // underscore. + var err = self.result_.errors[self.result_.errors.length - 1]; + self.client_.sendErrorEvent(err.toString()); + } +}; + + +/** @override */ +webdriver.testing.TestCase.prototype.logError = function(name, opt_e) { + var errMsg = null; + var stack = null; + if (opt_e) { + this.log(opt_e); + if (goog.isString(opt_e)) { + errMsg = opt_e; + } else { + errMsg = opt_e.toString(); + stack = opt_e.stack.substring(errMsg.length + 1); + } + } else { + errMsg = 'An unknown error occurred'; + } + var err = new goog.testing.TestCase.Error(name, errMsg, stack); + + // Avoid double logging. + if (!opt_e || !opt_e['isJsUnitException'] || + !opt_e['loggedJsUnitException']) { + this.saveMessage(err.toString()); + } + + if (opt_e && opt_e['isJsUnitException']) { + opt_e['loggedJsUnitException'] = true; + } + + return err; +}; + + +/** + * Executes a single test, scheduling each phase with the global application. + * Each phase will wait for the application to go idle before moving on to the + * next test phase. This function models the follow basic test flow: + * + * try { + * this.setUp.call(test.scope); + * test.ref.call(test.scope); + * } catch (ex) { + * onError(ex); + * } finally { + * try { + * this.tearDown.call(test.scope); + * } catch (e) { + * onError(e); + * } + * } + * + * @param {!goog.testing.TestCase.Test} test The test to run. + * @param {function(*)} onError The function to call each time an error is + * detected. + * @return {!webdriver.promise.Promise} A promise that will be resolved when the + * test has finished running. + * @private + */ +webdriver.testing.TestCase.prototype.runSingleTest_ = function(test, onError) { + var flow = webdriver.promise.controlFlow(); + + return execute(test.name + '.setUp()', this.setUp)(). + then(execute(test.name + '()', test.ref)). + thenCatch(onError). + then(execute(test.name + '.tearDown()', this.tearDown)). + thenCatch(onError); + + function execute(description, fn) { + return function() { + return flow.execute(goog.bind(fn, test.scope), description); + } + } +}; diff --git a/lib/webdriver/testing/window.js b/lib/webdriver/testing/window.js new file mode 100644 index 0000000..dd56733 --- /dev/null +++ b/lib/webdriver/testing/window.js @@ -0,0 +1,244 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview A utility class for working with test windows. + */ + +goog.provide('webdriver.testing.Window'); + +goog.require('goog.string'); +goog.require('webdriver.promise'); + + + +/** + * Class for managing a window. + * + * This class is implemented as a promise so consumers may register + * callbacks on it to handle situations where the window fails to open. + * + * For example: + * + * var testWindow = webdriver.testing.Window.create(driver); + * // Throw a custom error when the window fails to open. + * testWindow.thenCatch(function(e) { + * throw Error('Failed to open test window: ' + e); + * }); + * + * @param {!webdriver.WebDriver} driver The driver to use. + * @param {(string|!webdriver.promise.Promise)} handle Either the managed + * window's handle, or a promise that will resolve to it. + * @param {(Window|webdriver.promise.Promise)=} opt_window The raw window + * object, if available. + * @constructor + * @extends {webdriver.promise.Promise} + */ +webdriver.testing.Window = function(driver, handle, opt_window) { + webdriver.promise.Promise.call(this, function(fulfill, reject) { + handle.then(fulfill, reject); + }); + + /** @private {!webdriver.WebDriver} */ + this.driver_ = driver; + + /** @private {!webdriver.promise.Promise} */ + this.handle_ = webdriver.promise.when(handle); + + /** @private {!webdriver.promise.Promise} */ + this.window_ = webdriver.promise.when(opt_window); +}; +goog.inherits(webdriver.testing.Window, webdriver.promise.Promise); + + +/** + * Default amount of time, in milliseconds, to wait for a new window to open. + * @type {number} + * @const + */ +webdriver.testing.Window.DEFAULT_OPEN_TIMEOUT = 2000; + + +/** + * Window running this script. Lazily initialized the first time it is requested + * from {@link webdriver.testing.Window.findWindow}. + * @private {webdriver.testing.Window} + */ +webdriver.testing.Window.currentWindow_ = null; + + +/** + * Opens and focuses on a new window. + * + * @param {!webdriver.WebDriver} driver The driver to use. + * @param {?{width: number, height: number}=} opt_size The desired size for + * the new window. + * @param {number=} opt_timeout How long, in milliseconds, to wait for the new + * window to open. Defaults to + * {@link webdriver.testing.Window.DEFAULT_OPEN_TIMEOUT}. + * @return {!webdriver.testing.Window} The new window. + */ +webdriver.testing.Window.create = function(driver, opt_size, opt_timeout) { + var windowPromise = webdriver.promise.defer(); + var handle = driver.call(function() { + var features = [ + 'location=yes', + 'titlebar=yes' + ]; + + if (opt_size) { + features.push( + 'width=' + opt_size.width, + 'height=' + opt_size.height); + } + + var name = goog.string.getRandomString(); + windowPromise.fulfill(window.open('', name, features.join(','))); + + driver.wait(function() { + return driver.switchTo().window(name).then( + function() { return true; }, + function() { return false; }); + }, opt_timeout || webdriver.testing.Window.DEFAULT_OPEN_TIMEOUT); + return driver.getWindowHandle(); + }); + + return new webdriver.testing.Window(driver, handle, windowPromise); +}; + + +/** + * Changes focus to the topmost window for the provided DOM window. + * + * @param {!webdriver.WebDriver} driver The driver to use. + * @param {Window=} opt_window The window to search for. Defaults to the window + * running this script. + * @return {!webdriver.testing.Window} The located window. + */ +webdriver.testing.Window.focusOnWindow = function(driver, opt_window) { + var name = goog.string.getRandomString(); + var win = opt_window ? opt_window.top : window; + + var ret; + if (win === window && webdriver.testing.Window.currentWindow_) { + ret = webdriver.testing.Window.currentWindow_; + webdriver.testing.Window.currentWindow_.focus(); + } else { + win.name = name; + ret = new webdriver.testing.Window(driver, + driver.switchTo().window(name). + then(goog.bind(driver.getWindowHandle, driver)), + win); + if (win === window) { + webdriver.testing.Window.currentWindow_ = ret; + } + } + return ret; +}; + + +/** @override */ +webdriver.testing.Window.prototype.cancel = function() { + return this.handle_.cancel(); +}; + + +/** + * Focuses the wrapped driver on the window managed by this class. + * @return {!webdriver.promise.Promise} A promise that will be resolved + * when this command has completed. + */ +webdriver.testing.Window.prototype.focus = function() { + return this.driver_.switchTo().window(this.handle_); +}; + + +/** + * Focuses on and closes the managed window. The driver must be + * focused on another window before issuing any further commands. + * @return {!webdriver.promise.Promise} A promise that will be resolved + * when this command has completed. + */ +webdriver.testing.Window.prototype.close = function() { + var self = this; + return this.window_.then(function(win) { + if (win) { + win.close(); + } else { + return self.focus().then(goog.bind(self.driver_.close, self.driver_)); + } + }); +}; + + +/** + * Retrieves the current size of this window. + * @return {!webdriver.promise.Promise} A promise that resolves to the size of + * this window as a {width:number, height:number} object. + */ +webdriver.testing.Window.prototype.getSize = function() { + var driver = this.driver_; + return this.focus().then(function() { + return driver.manage().window().getSize(); + }); +}; + + +/** + * Sets the size of this window. + * @param {number} width The desired width, in pixels. + * @param {number} height The desired height, in pixels. + * @return {!webdriver.promise.Promise} A promise that resolves when the + * command has completed. + */ +webdriver.testing.Window.prototype.setSize = function(width, height) { + var driver = this.driver_; + return this.focus().then(function() { + return driver.manage().window().setSize(width, height); + }); +}; + + +/** + * Retrieves the current position of this window, in pixels relative to the + * upper left corner of the screen. + * @return {!webdriver.promise.Promise} A promise that resolves to the + * position of this window as a {x:number, y:number} object. + */ +webdriver.testing.Window.prototype.getPosition = function() { + var driver = this.driver_; + return this.focus().then(function() { + return driver.manage().window().getPosition(); + }); +}; + + +/** + * Repositions this window. + * @param {number} x The desired horizontal position, in pixels from the left + * side of the screen. + * @param {number} y The desired vertical position, in pixels from the top + * of the screen. + * @return {!webdriver.promise.Promise} A promise that resolves when the + * command has completed. + */ +webdriver.testing.Window.prototype.setPosition = function(x, y) { + var driver = this.driver_; + return this.focus().then(function() { + return driver.manage().window().setPosition(x, y); + }); +}; diff --git a/lib/webdriver/touchsequence.js b/lib/webdriver/touchsequence.js new file mode 100644 index 0000000..a5b302d --- /dev/null +++ b/lib/webdriver/touchsequence.js @@ -0,0 +1,248 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +goog.provide('webdriver.TouchSequence'); + +goog.require('goog.array'); +goog.require('webdriver.Command'); +goog.require('webdriver.CommandName'); + + + +/** + * Class for defining sequences of user touch interactions. Each sequence + * will not be executed until {@link #perform} is called. + * + * Example: + * + * new webdriver.TouchSequence(driver). + * tapAndHold({x: 0, y: 0}). + * move({x: 3, y: 4}). + * release({x: 10, y: 10}). + * perform(); + * + * @param {!webdriver.WebDriver} driver The driver instance to use. + * @constructor + */ +webdriver.TouchSequence = function(driver) { + + /** @private {!webdriver.WebDriver} */ + this.driver_ = driver; + + /** @private {!Array<{description: string, command: !webdriver.Command}>} */ + this.touchActions_ = []; +}; + + +/** + * Schedules an action to be executed each time {@link #perform} is called on + * this instance. + * @param {string} description A description of the command. + * @param {!webdriver.Command} command The command. + * @private + */ +webdriver.TouchSequence.prototype.schedule_ = function(description, command) { + this.touchActions_.push({ + description: description, + command: command + }); +}; + + +/** + * Executes this action sequence. + * @return {!webdriver.promise.Promise} A promise that will be resolved once + * this sequence has completed. + */ +webdriver.TouchSequence.prototype.perform = function() { + // Make a protected copy of the scheduled actions. This will protect against + // users defining additional commands before this sequence is actually + // executed. + var actions = goog.array.clone(this.touchActions_); + var driver = this.driver_; + return driver.controlFlow().execute(function() { + goog.array.forEach(actions, function(action) { + driver.schedule(action.command, action.description); + }); + }, 'TouchSequence.perform'); +}; + + +/** + * Taps an element. + * + * @param {!webdriver.WebElement} elem The element to tap. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.tap = function(elem) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_SINGLE_TAP). + setParameter('element', elem.getRawId()); + + this.schedule_('tap', command); + return this; +}; + + +/** + * Double taps an element. + * + * @param {!webdriver.WebElement} elem The element to double tap. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.doubleTap = function(elem) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_DOUBLE_TAP). + setParameter('element', elem.getRawId()); + + this.schedule_('doubleTap', command); + return this; +}; + + +/** + * Long press on an element. + * + * @param {!webdriver.WebElement} elem The element to long press. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.longPress = function(elem) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_LONG_PRESS). + setParameter('element', elem.getRawId()); + + this.schedule_('longPress', command); + return this; +}; + + +/** + * Touch down at the given location. + * + * @param {{x: number, y: number}} location The location to touch down at. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.tapAndHold = function(location) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_DOWN). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('tapAndHold', command); + return this; +}; + + +/** + * Move a held {@linkplain #tapAndHold touch} to the specified location. + * + * @param {{x: number, y: number}} location The location to move to. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.move = function(location) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_MOVE). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('move', command); + return this; +}; + + +/** + * Release a held {@linkplain #tapAndHold touch} at the specified location. + * + * @param {{x: number, y: number}} location The location to release at. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.release = function(location) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_UP). + setParameter('x', location.x). + setParameter('y', location.y); + + this.schedule_('release', command); + return this; +}; + + +/** + * Scrolls the touch screen by the given offset. + * + * @param {{x: number, y: number}} offset The offset to scroll to. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.scroll = function(offset) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_SCROLL). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y); + + this.schedule_('scroll', command); + return this; +}; + + +/** + * Scrolls the touch screen, starting on `elem` and moving by the specified + * offset. + * + * @param {!webdriver.WebElement} elem The element where scroll starts. + * @param {{x: number, y: number}} offset The offset to scroll to. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.scrollFromElement = function(elem, offset) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_SCROLL). + setParameter('element', elem.getRawId()). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y); + + this.schedule_('scrollFromElement', command); + return this; +}; + + +/** + * Flick, starting anywhere on the screen, at speed xspeed and yspeed. + * + * @param {{xspeed: number, yspeed: number}} speed The speed to flick in each + direction, in pixels per second. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.flick = function(speed) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_FLICK). + setParameter('xspeed', speed.xspeed). + setParameter('yspeed', speed.yspeed); + + this.schedule_('flick', command); + return this; +}; + + +/** + * Flick starting at elem and moving by x and y at specified speed. + * + * @param {!webdriver.WebElement} elem The element where flick starts. + * @param {{x: number, y: number}} offset The offset to flick to. + * @param {number} speed The speed to flick at in pixels per second. + * @return {!webdriver.TouchSequence} A self reference. + */ +webdriver.TouchSequence.prototype.flickElement = function(elem, offset, speed) { + var command = new webdriver.Command(webdriver.CommandName.TOUCH_FLICK). + setParameter('element', elem.getRawId()). + setParameter('xoffset', offset.x). + setParameter('yoffset', offset.y). + setParameter('speed', speed); + + this.schedule_('flickElement', command); + return this; +}; + diff --git a/lib/webdriver/until.js b/lib/webdriver/until.js new file mode 100644 index 0000000..03d24bb --- /dev/null +++ b/lib/webdriver/until.js @@ -0,0 +1,412 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines common conditions for use with + * {@link webdriver.WebDriver#wait WebDriver wait}. + * + * Sample usage: + * + * driver.get('http://www.google.com/ncr'); + * + * var query = driver.wait(until.elementLocated(By.name('q'))); + * query.sendKeys('webdriver\n'); + * + * driver.wait(until.titleIs('webdriver - Google Search')); + * + * To define a custom condition, simply call WebDriver.wait with a function + * that will eventually return a truthy-value (neither null, undefined, false, + * 0, or the empty string): + * + * driver.wait(function() { + * return driver.getTitle().then(function(title) { + * return title === 'webdriver - Google Search'; + * }); + * }, 1000); + */ + +goog.provide('webdriver.until'); + +goog.require('bot.ErrorCode'); +goog.require('goog.array'); +goog.require('goog.string'); +goog.require('webdriver.Locator'); + + + +goog.scope(function() { + +var until = webdriver.until; + + +/** + * Defines a condition to + * @param {string} message A descriptive error message. Should complete the + * sentence "Waiting [...]" + * @param {function(!webdriver.WebDriver): OUT} fn The condition function to + * evaluate on each iteration of the wait loop. + * @constructor + * @struct + * @final + * @template OUT + */ +until.Condition = function(message, fn) { + /** @private {string} */ + this.description_ = 'Waiting ' + message; + + /** @type {function(!webdriver.WebDriver): OUT} */ + this.fn = fn; +}; + + +/** @return {string} A description of this condition. */ +until.Condition.prototype.description = function() { + return this.description_; +}; + + +/** + * Creates a condition that will wait until the input driver is able to switch + * to the designated frame. The target frame may be specified as + * + * 1. a numeric index into + * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames) + * for the currently selected frame. + * 2. a {@link webdriver.WebElement}, which must reference a FRAME or IFRAME + * element on the current page. + * 3. a locator which may be used to first locate a FRAME or IFRAME on the + * current page before attempting to switch to it. + * + * Upon successful resolution of this condition, the driver will be left + * focused on the new frame. + * + * @param {!(number|webdriver.WebElement| + * webdriver.Locator|webdriver.By.Hash| + * function(!webdriver.WebDriver): !webdriver.WebElement)} frame + * The frame identifier. + * @return {!until.Condition.} A new condition. + */ +until.ableToSwitchToFrame = function(frame) { + var condition; + if (goog.isNumber(frame) || frame instanceof webdriver.WebElement) { + condition = attemptToSwitchFrames; + } else { + condition = function(driver) { + var locator = + /** @type {!(webdriver.Locator|webdriver.By.Hash|Function)} */(frame); + return driver.findElements(locator).then(function(els) { + if (els.length) { + return attemptToSwitchFrames(driver, els[0]); + } + }); + }; + } + + return new until.Condition('to be able to switch to frame', condition); + + function attemptToSwitchFrames(driver, frame) { + return driver.switchTo().frame(frame).then( + function() { return true; }, + function(e) { + if (e && e.code !== bot.ErrorCode.NO_SUCH_FRAME) { + throw e; + } + }); + } +}; + + +/** + * Creates a condition that waits for an alert to be opened. Upon success, the + * returned promise will be fulfilled with the handle for the opened alert. + * + * @return {!until.Condition.} The new condition. + */ +until.alertIsPresent = function() { + return new until.Condition('for alert to be present', function(driver) { + return driver.switchTo().alert().thenCatch(function(e) { + if (e && e.code !== bot.ErrorCode.NO_SUCH_ALERT) { + throw e; + } + }); + }); +}; + + +/** + * Creates a condition that will wait for the current page's title to match the + * given value. + * + * @param {string} title The expected page title. + * @return {!until.Condition.} The new condition. + */ +until.titleIs = function(title) { + return new until.Condition( + 'for title to be ' + goog.string.quote(title), + function(driver) { + return driver.getTitle().then(function(t) { + return t === title; + }); + }); +}; + + +/** + * Creates a condition that will wait for the current page's title to contain + * the given substring. + * + * @param {string} substr The substring that should be present in the page + * title. + * @return {!until.Condition.} The new condition. + */ +until.titleContains = function(substr) { + return new until.Condition( + 'for title to contain ' + goog.string.quote(substr), + function(driver) { + return driver.getTitle().then(function(title) { + return title.indexOf(substr) !== -1; + }); + }); +}; + + +/** + * Creates a condition that will wait for the current page's title to match the + * given regular expression. + * + * @param {!RegExp} regex The regular expression to test against. + * @return {!until.Condition.} The new condition. + */ +until.titleMatches = function(regex) { + return new until.Condition('for title to match ' + regex, function(driver) { + return driver.getTitle().then(function(title) { + return regex.test(title); + }); + }); +}; + + +/** + * Creates a condition that will loop until an element is + * {@link webdriver.WebDriver#findElement found} with the given locator. + * + * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator + * to use. + * @return {!until.Condition.} The new condition. + */ +until.elementLocated = function(locator) { + locator = webdriver.Locator.checkLocator(locator); + var locatorStr = goog.isFunction(locator) ? 'by function()' : locator + ''; + return new until.Condition('for element to be located ' + locatorStr, + function(driver) { + return driver.findElements(locator).then(function(elements) { + return elements[0]; + }); + }); +}; + + +/** + * Creates a condition that will loop until at least one element is + * {@link webdriver.WebDriver#findElement found} with the given locator. + * + * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The locator + * to use. + * @return {!until.Condition.>} The new + * condition. + */ +until.elementsLocated = function(locator) { + locator = webdriver.Locator.checkLocator(locator); + var locatorStr = goog.isFunction(locator) ? 'by function()' : locator + ''; + return new until.Condition( + 'for at least one element to be located ' + locatorStr, + function(driver) { + return driver.findElements(locator).then(function(elements) { + return elements.length > 0 ? elements : null; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element to become stale. An + * element is considered stale once it is removed from the DOM, or a new page + * has loaded. + * + * @param {!webdriver.WebElement} element The element that should become stale. + * @return {!until.Condition.} The new condition. + */ +until.stalenessOf = function(element) { + return new until.Condition('element to become stale', function() { + return element.getTagName().then( + function() { return false; }, + function(e) { + if (e.code === bot.ErrorCode.STALE_ELEMENT_REFERENCE) { + return true; + } + throw e; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element to become visible. + * + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isDisplayed + */ +until.elementIsVisible = function(element) { + return new until.Condition('until element is visible', function() { + return element.isDisplayed(); + }); +}; + + +/** + * Creates a condition that will wait for the given element to be in the DOM, + * yet not visible to the user. + * + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isDisplayed + */ +until.elementIsNotVisible = function(element) { + return new until.Condition('until element is not visible', function() { + return element.isDisplayed().then(function(v) { + return !v; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element to be enabled. + * + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isEnabled + */ +until.elementIsEnabled = function(element) { + return new until.Condition('until element is enabled', function() { + return element.isEnabled(); + }); +}; + + +/** + * Creates a condition that will wait for the given element to be disabled. + * + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isEnabled + */ +until.elementIsDisabled = function(element) { + return new until.Condition('until element is disabled', function() { + return element.isEnabled().then(function(v) { + return !v; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element to be selected. + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isSelected + */ +until.elementIsSelected = function(element) { + return new until.Condition('until element is selected', function() { + return element.isSelected(); + }); +}; + + +/** + * Creates a condition that will wait for the given element to be deselected. + * + * @param {!webdriver.WebElement} element The element to test. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#isSelected + */ +until.elementIsNotSelected = function(element) { + return new until.Condition('until element is not selected', function() { + return element.isSelected().then(function(v) { + return !v; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element's + * {@link webdriver.WebDriver#getText visible text} to match the given + * {@code text} exactly. + * + * @param {!webdriver.WebElement} element The element to test. + * @param {string} text The expected text. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#getText + */ +until.elementTextIs = function(element, text) { + return new until.Condition('until element text is', function() { + return element.getText().then(function(t) { + return t === text; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element's + * {@link webdriver.WebDriver#getText visible text} to contain the given + * substring. + * + * @param {!webdriver.WebElement} element The element to test. + * @param {string} substr The substring to search for. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#getText + */ +until.elementTextContains = function(element, substr) { + return new until.Condition('until element text contains', function() { + return element.getText().then(function(t) { + return t.indexOf(substr) != -1; + }); + }); +}; + + +/** + * Creates a condition that will wait for the given element's + * {@link webdriver.WebDriver#getText visible text} to match a regular + * expression. + * + * @param {!webdriver.WebElement} element The element to test. + * @param {!RegExp} regex The regular expression to test against. + * @return {!until.Condition.} The new condition. + * @see webdriver.WebDriver#getText + */ +until.elementTextMatches = function(element, regex) { + return new until.Condition('until element text matches', function() { + return element.getText().then(function(t) { + return regex.test(t); + }); + }); +}; +}); // goog.scope diff --git a/lib/webdriver/webdriver.js b/lib/webdriver/webdriver.js index f666fb3..fd78032 100644 --- a/lib/webdriver/webdriver.js +++ b/lib/webdriver/webdriver.js @@ -1,25 +1,31 @@ -// Copyright 2011 Software Freedom Conservancy. All Rights Reserved. +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview The heart of the WebDriver JavaScript API. */ goog.provide('webdriver.Alert'); +goog.provide('webdriver.AlertPromise'); +goog.provide('webdriver.FileDetector'); goog.provide('webdriver.UnhandledAlertError'); goog.provide('webdriver.WebDriver'); goog.provide('webdriver.WebElement'); +goog.provide('webdriver.WebElementPromise'); goog.require('bot.Error'); goog.require('bot.ErrorCode'); @@ -31,9 +37,12 @@ goog.require('webdriver.Command'); goog.require('webdriver.CommandName'); goog.require('webdriver.Key'); goog.require('webdriver.Locator'); +goog.require('webdriver.Serializable'); goog.require('webdriver.Session'); +goog.require('webdriver.TouchSequence'); goog.require('webdriver.logging'); goog.require('webdriver.promise'); +goog.require('webdriver.until'); ////////////////////////////////////////////////////////////////////////////// @@ -52,16 +61,15 @@ goog.require('webdriver.promise'); * object to manipulate the command result or catch an expected error. Any * commands scheduled with a callback are considered sub-commands and will * execute before the next command in the current frame. For example: - *
        
        - *   var message = [];
        - *   driver.call(message.push, message, 'a').then(function() {
        - *     driver.call(message.push, message, 'b');
        - *   });
        - *   driver.call(message.push, message, 'c');
        - *   driver.call(function() {
        - *     alert('message is abc? ' + (message.join('') == 'abc'));
        - *   });
        - * 
        + * + * var message = []; + * driver.call(message.push, message, 'a').then(function() { + * driver.call(message.push, message, 'b'); + * }); + * driver.call(message.push, message, 'c'); + * driver.call(function() { + * alert('message is abc? ' + (message.join('') == 'abc')); + * }); * * @param {!(webdriver.Session|webdriver.promise.Promise)} session Either a * known session or a promise that will be resolved to a session. @@ -81,6 +89,9 @@ webdriver.WebDriver = function(session, executor, opt_flow) { /** @private {!webdriver.promise.ControlFlow} */ this.flow_ = opt_flow || webdriver.promise.controlFlow(); + + /** @private {webdriver.FileDetector} */ + this.fileDetector_ = null; }; @@ -89,13 +100,17 @@ webdriver.WebDriver = function(session, executor, opt_flow) { * @param {!webdriver.CommandExecutor} executor Command executor to use when * querying for session details. * @param {string} sessionId ID of the session to attach to. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver + * commands should execute under. Defaults to the + * {@link webdriver.promise.controlFlow() currently active} control flow. * @return {!webdriver.WebDriver} A new client for the specified session. */ -webdriver.WebDriver.attachToSession = function(executor, sessionId) { +webdriver.WebDriver.attachToSession = function(executor, sessionId, opt_flow) { return webdriver.WebDriver.acquireSession_(executor, new webdriver.Command(webdriver.CommandName.DESCRIBE_SESSION). setParameter('sessionId', sessionId), - 'WebDriver.attachToSession()'); + 'WebDriver.attachToSession()', + opt_flow); }; @@ -105,13 +120,19 @@ webdriver.WebDriver.attachToSession = function(executor, sessionId) { * session with. * @param {!webdriver.Capabilities} desiredCapabilities The desired * capabilities for the new session. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver + * commands should execute under, including the initial session creation. + * Defaults to the {@link webdriver.promise.controlFlow() currently active} + * control flow. * @return {!webdriver.WebDriver} The driver for the newly created session. */ -webdriver.WebDriver.createSession = function(executor, desiredCapabilities) { +webdriver.WebDriver.createSession = function( + executor, desiredCapabilities, opt_flow) { return webdriver.WebDriver.acquireSession_(executor, new webdriver.Command(webdriver.CommandName.NEW_SESSION). setParameter('desiredCapabilities', desiredCapabilities), - 'WebDriver.createSession()'); + 'WebDriver.createSession()', + opt_flow); }; @@ -124,11 +145,16 @@ webdriver.WebDriver.createSession = function(executor, desiredCapabilities) { * @param {!webdriver.Command} command The command to send to fetch the session * details. * @param {string} description A descriptive debug label for this action. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow all driver + * commands should execute under. Defaults to the + * {@link webdriver.promise.controlFlow() currently active} control flow. * @return {!webdriver.WebDriver} A new WebDriver client for the session. * @private */ -webdriver.WebDriver.acquireSession_ = function(executor, command, description) { - var session = webdriver.promise.controlFlow().execute(function() { +webdriver.WebDriver.acquireSession_ = function( + executor, command, description, opt_flow) { + var flow = opt_flow || webdriver.promise.controlFlow(); + var session = flow.execute(function() { return webdriver.WebDriver.executeCommand_(executor, command). then(function(response) { bot.response.checkResponse(response); @@ -136,7 +162,7 @@ webdriver.WebDriver.acquireSession_ = function(executor, command, description) { response['value']); }); }, description); - return new webdriver.WebDriver(session, executor); + return new webdriver.WebDriver(session, executor, flow); }; @@ -144,48 +170,101 @@ webdriver.WebDriver.acquireSession_ = function(executor, command, description) { * Converts an object to its JSON representation in the WebDriver wire protocol. * When converting values of type object, the following steps will be taken: *
          - *
        1. if the object provides a "toWireValue" function, the return value will - * be returned in its fully resolved state (e.g. this function may return - * promise values)
        2. - *
        3. if the object provides a "toJSON" function, the return value of this - * function will be returned
        4. + *
        5. if the object is a WebElement, the return value will be the element's + * server ID + *
        6. if the object is a Serializable, its + * {@link webdriver.Serializable#serialize} function will be invoked and + * this algorithm will recursively be applied to the result + *
        7. if the object provides a "toJSON" function, this algorithm will + * recursively be applied to the result of that function *
        8. otherwise, the value of each key will be recursively converted according - * to the rules above.
        9. + * to the rules above. *
        * * @param {*} obj The object to convert. - * @return {!webdriver.promise.Promise} A promise that will resolve to the + * @return {!webdriver.promise.Promise.} A promise that will resolve to the * input value's JSON representation. * @private - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol */ webdriver.WebDriver.toWireValue_ = function(obj) { - switch (goog.typeOf(obj)) { - case 'array': - return webdriver.promise.fullyResolved( - goog.array.map(/** @type {!Array} */ (obj), - webdriver.WebDriver.toWireValue_)); - case 'object': - if (goog.isFunction(obj.toWireValue)) { - return webdriver.promise.fullyResolved(obj.toWireValue()); + if (webdriver.promise.isPromise(obj)) { + return obj.then(webdriver.WebDriver.toWireValue_); + } + return webdriver.promise.fulfilled(convertValue(obj)); + + function convertValue(value) { + switch (goog.typeOf(value)) { + case 'array': + return convertKeys(value, true); + case 'object': + // NB: WebElement is a Serializable, but we know its serialized form + // is a promise for its wire format. This is a micro optimization to + // avoid creating extra promises by recursing on the promised id. + if (value instanceof webdriver.WebElement) { + return value.getId(); + } + if (value instanceof webdriver.Serializable) { + return webdriver.WebDriver.toWireValue_(value.serialize()); + } + if (goog.isFunction(value.toJSON)) { + return webdriver.WebDriver.toWireValue_(value.toJSON()); + } + if (goog.isNumber(value.nodeType) && goog.isString(value.nodeName)) { + throw new TypeError( + 'Invalid argument type: ' + value.nodeName + + '(' + value.nodeType + ')'); + } + return convertKeys(value, false); + case 'function': + return '' + value; + case 'undefined': + return null; + default: + return value; + } + } + + function convertKeys(obj, isArray) { + var numKeys = isArray ? obj.length : goog.object.getCount(obj); + var ret = isArray ? new Array(numKeys) : {}; + if (!numKeys) { + return webdriver.promise.fulfilled(ret); + } + + var numResolved = 0; + var done = webdriver.promise.defer(); + + // forEach will stop iteration at undefined, where we want to convert + // these to null and keep iterating. + var forEachKey = !isArray ? goog.object.forEach : function(arr, fn) { + var n = arr.length; + for (var i = 0; i < n; i++) { + fn(arr[i], i); } - if (goog.isFunction(obj.toJSON)) { - return webdriver.promise.fulfilled(obj.toJSON()); + }; + + forEachKey(obj, function(value, key) { + if (webdriver.promise.isPromise(value)) { + value.then(webdriver.WebDriver.toWireValue_). + then(setValue, done.reject); + } else { + webdriver.promise.asap(convertValue(value), setValue, done.reject); } - if (goog.isNumber(obj.nodeType) && goog.isString(obj.nodeName)) { - throw Error([ - 'Invalid argument type: ', obj.nodeName, '(', obj.nodeType, ')' - ].join('')); + + function setValue(value) { + ret[key] = value; + maybeFulfill(); + } + }); + + return done.promise; + + function maybeFulfill() { + if (++numResolved === numKeys) { + done.fulfill(ret); } - return webdriver.promise.fullyResolved( - goog.object.map(/** @type {!Object} */ (obj), - webdriver.WebDriver.toWireValue_)); - case 'function': - return webdriver.promise.fulfilled('' + obj); - case 'undefined': - return webdriver.promise.fulfilled(null); - default: - return webdriver.promise.fulfilled(obj); + } } }; @@ -200,7 +279,7 @@ webdriver.WebDriver.toWireValue_ = function(obj) { * parent of any unwrapped {@code webdriver.WebElement} values. * @param {*} value The value to convert. * @return {*} The converted value. - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol * @private */ webdriver.WebDriver.fromWireValue_ = function(driver, value) { @@ -209,8 +288,7 @@ webdriver.WebDriver.fromWireValue_ = function(driver, value) { goog.partial(webdriver.WebDriver.fromWireValue_, driver)); } else if (value && goog.isObject(value) && !goog.isFunction(value)) { if (webdriver.WebElement.ELEMENT_KEY in value) { - value = new webdriver.WebElement(driver, - value[webdriver.WebElement.ELEMENT_KEY]); + value = new webdriver.WebElement(driver, value); } else { value = goog.object.map(/**@type {!Object}*/ (value), goog.partial(webdriver.WebDriver.fromWireValue_, driver)); @@ -230,8 +308,7 @@ webdriver.WebDriver.fromWireValue_ = function(driver, value) { * @private */ webdriver.WebDriver.executeCommand_ = function(executor, command) { - return webdriver.promise.fullyResolved(command.getParameters()). - then(webdriver.WebDriver.toWireValue_). + return webdriver.WebDriver.toWireValue_(command.getParameters()). then(function(parameters) { command.setParameters(parameters); return webdriver.promise.checkedNodeCall( @@ -254,8 +331,9 @@ webdriver.WebDriver.prototype.controlFlow = function() { * {@code webdriver.CommandExecutor}. * @param {!webdriver.Command} command The command to schedule. * @param {string} description A description of the command for debugging. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * the command result. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * with the command result. + * @template T */ webdriver.WebDriver.prototype.schedule = function(command, description) { var self = this; @@ -263,22 +341,47 @@ webdriver.WebDriver.prototype.schedule = function(command, description) { checkHasNotQuit(); command.setParameter('sessionId', this.session_); + // If any of the command parameters are rejected promises, those + // rejections may be reported as unhandled before the control flow + // attempts to execute the command. To ensure parameters errors + // propagate through the command itself, we resolve all of the + // command parameters now, but suppress any errors until the ControlFlow + // actually executes the command. This addresses scenarios like catching + // an element not found error in: + // + // driver.findElement(By.id('foo')).click().thenCatch(function(e) { + // if (e.code === bot.ErrorCode.NO_SUCH_ELEMENT) { + // // Do something. + // } + // }); + var prepCommand = webdriver.WebDriver.toWireValue_(command.getParameters()); + prepCommand.thenCatch(goog.nullFunction); + var flow = this.flow_; + var executor = this.executor_; return flow.execute(function() { // A call to WebDriver.quit() may have been scheduled in the same event // loop as this |command|, which would prevent us from detecting that the // driver has quit above. Therefore, we need to make another quick check. // We still check above so we can fail as early as possible. checkHasNotQuit(); - return webdriver.WebDriver.executeCommand_(self.executor_, command); + + // Retrieve resolved command parameters; any previously suppressed errors + // will now propagate up through the control flow as part of the command + // execution. + return prepCommand.then(function(parameters) { + command.setParameters(parameters); + return webdriver.promise.checkedNodeCall( + goog.bind(executor.execute, executor, command)); + }); }, description).then(function(response) { try { bot.response.checkResponse(response); } catch (ex) { var value = response['value']; - if (ex.code === bot.ErrorCode.MODAL_DIALOG_OPENED) { + if (ex.code === bot.ErrorCode.UNEXPECTED_ALERT_OPEN) { var text = value && value['alert'] ? value['alert']['text'] : ''; - throw new webdriver.UnhandledAlertError(ex.message, + throw new webdriver.UnhandledAlertError(ex.message, text, new webdriver.Alert(self, text)); } throw ex; @@ -296,13 +399,24 @@ webdriver.WebDriver.prototype.schedule = function(command, description) { }; +/** + * Sets the {@linkplain webdriver.FileDetector file detector} that should be + * used with this instance. + * @param {webdriver.FileDetector} detector The detector to use or {@code null}. + */ +webdriver.WebDriver.prototype.setFileDetector = function(detector) { + this.fileDetector_ = detector; +}; + + // ---------------------------------------------------------------------------- // Client command functions: // ---------------------------------------------------------------------------- /** - * @return {!webdriver.promise.Promise} A promise for this client's session. + * @return {!webdriver.promise.Promise.} A promise for this + * client's session. */ webdriver.WebDriver.prototype.getSession = function() { return webdriver.promise.when(this.session_); @@ -310,8 +424,8 @@ webdriver.WebDriver.prototype.getSession = function() { /** - * @return {!webdriver.promise.Promise} A promise that will resolve with the - * this instance's capabilities. + * @return {!webdriver.promise.Promise.} A promise + * that will resolve with the this instance's capabilities. */ webdriver.WebDriver.prototype.getCapabilities = function() { return webdriver.promise.when(this.session_, function(session) { @@ -324,8 +438,8 @@ webdriver.WebDriver.prototype.getCapabilities = function() { * Schedules a command to quit the current session. After calling quit, this * instance will be invalidated and may no longer be used to issue commands * against the browser. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * the command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the command has completed. */ webdriver.WebDriver.prototype.quit = function() { var result = this.schedule( @@ -343,13 +457,13 @@ webdriver.WebDriver.prototype.quit = function() { * Creates a new action sequence using this driver. The sequence will not be * scheduled for execution until {@link webdriver.ActionSequence#perform} is * called. Example: - *
        
        - *   driver.actions().
        - *       mouseDown(element1).
        - *       mouseMove(element2).
        - *       mouseUp().
        - *       perform();
        - * 
        + * + * driver.actions(). + * mouseDown(element1). + * mouseMove(element2). + * mouseUp(). + * perform(); + * * @return {!webdriver.ActionSequence} A new action sequence for this instance. */ webdriver.WebDriver.prototype.actions = function() { @@ -357,6 +471,23 @@ webdriver.WebDriver.prototype.actions = function() { }; +/** + * Creates a new touch sequence using this driver. The sequence will not be + * scheduled for execution until {@link webdriver.TouchSequence#perform} is + * called. Example: + * + * driver.touchActions(). + * tap(element1). + * doubleTap(element2). + * perform(); + * + * @return {!webdriver.TouchSequence} A new touch sequence for this instance. + */ +webdriver.WebDriver.prototype.touchActions = function() { + return new webdriver.TouchSequence(this); +}; + + /** * Schedules a command to execute JavaScript in the context of the currently * selected frame or window. The script fragment will be executed as the body @@ -379,29 +510,30 @@ webdriver.WebDriver.prototype.actions = function() { * If the script has a return value (i.e. if the script contains a return * statement), then the following steps will be taken for resolving this * functions return value: - *
          - *
        • For a HTML element, the value will resolve to a - * {@code webdriver.WebElement}
        • - *
        • Null and undefined return values will resolve to null
        • - *
        • Booleans, numbers, and strings will resolve as is
        • - *
        • Functions will resolve to their string representation
        • - *
        • For arrays and objects, each member item will be converted according to - * the rules above
        • - *
        + * + * - For a HTML element, the value will resolve to a + * {@link webdriver.WebElement} + * - Null and undefined return values will resolve to null + * - Booleans, numbers, and strings will resolve as is + * - Functions will resolve to their string representation + * - For arrays and objects, each member item will be converted according to + * the rules above * * @param {!(string|Function)} script The script to execute. * @param {...*} var_args The arguments to pass to the script. - * @return {!webdriver.promise.Promise} A promise that will resolve to the + * @return {!webdriver.promise.Promise.} A promise that will resolve to the * scripts return value. + * @template T */ webdriver.WebDriver.prototype.executeScript = function(script, var_args) { if (goog.isFunction(script)) { script = 'return (' + script + ').apply(null, arguments);'; } + var args = arguments.length > 1 ? goog.array.slice(arguments, 1) : []; return this.schedule( new webdriver.Command(webdriver.CommandName.EXECUTE_SCRIPT). setParameter('script', script). - setParameter('args', goog.array.slice(arguments, 1)), + setParameter('args', args), 'WebDriver.executeScript()'); }; @@ -419,71 +551,68 @@ webdriver.WebDriver.prototype.executeScript = function(script, var_args) { * Arrays and objects may also be used as script arguments as long as each item * adheres to the types previously mentioned. * - * Unlike executing synchronous JavaScript with - * {@code webdriver.WebDriver.prototype.executeScript}, scripts executed with - * this function must explicitly signal they are finished by invoking the - * provided callback. This callback will always be injected into the - * executed function as the last argument, and thus may be referenced with - * {@code arguments[arguments.length - 1]}. The following steps will be taken - * for resolving this functions return value against the first argument to the - * script's callback function: - *
          - *
        • For a HTML element, the value will resolve to a - * {@code webdriver.WebElement}
        • - *
        • Null and undefined return values will resolve to null
        • - *
        • Booleans, numbers, and strings will resolve as is
        • - *
        • Functions will resolve to their string representation
        • - *
        • For arrays and objects, each member item will be converted according to - * the rules above
        • - *
        - * - * Example #1: Performing a sleep that is synchronized with the currently + * Unlike executing synchronous JavaScript with {@link #executeScript}, + * scripts executed with this function must explicitly signal they are finished + * by invoking the provided callback. This callback will always be injected + * into the executed function as the last argument, and thus may be referenced + * with {@code arguments[arguments.length - 1]}. The following steps will be + * taken for resolving this functions return value against the first argument + * to the script's callback function: + * + * - For a HTML element, the value will resolve to a + * {@link webdriver.WebElement} + * - Null and undefined return values will resolve to null + * - Booleans, numbers, and strings will resolve as is + * - Functions will resolve to their string representation + * - For arrays and objects, each member item will be converted according to + * the rules above + * + * __Example #1:__ Performing a sleep that is synchronized with the currently * selected window: - *
        - * var start = new Date().getTime();
        - * driver.executeAsyncScript(
        - *     'window.setTimeout(arguments[arguments.length - 1], 500);').
        - *     then(function() {
        - *       console.log('Elapsed time: ' + (new Date().getTime() - start) + ' ms');
        - *     });
        - * 
        - * - * Example #2: Synchronizing a test with an AJAX application: - *
        - * var button = driver.findElement(By.id('compose-button'));
        - * button.click();
        - * driver.executeAsyncScript(
        - *     'var callback = arguments[arguments.length - 1];' +
        - *     'mailClient.getComposeWindowWidget().onload(callback);');
        - * driver.switchTo().frame('composeWidget');
        - * driver.findElement(By.id('to')).sendKEys('dog@example.com');
        - * 
        - * - * Example #3: Injecting a XMLHttpRequest and waiting for the result. In this - * example, the inject script is specified with a function literal. When using - * this format, the function is converted to a string for injection, so it + * + * var start = new Date().getTime(); + * driver.executeAsyncScript( + * 'window.setTimeout(arguments[arguments.length - 1], 500);'). + * then(function() { + * console.log( + * 'Elapsed time: ' + (new Date().getTime() - start) + ' ms'); + * }); + * + * __Example #2:__ Synchronizing a test with an AJAX application: + * + * var button = driver.findElement(By.id('compose-button')); + * button.click(); + * driver.executeAsyncScript( + * 'var callback = arguments[arguments.length - 1];' + + * 'mailClient.getComposeWindowWidget().onload(callback);'); + * driver.switchTo().frame('composeWidget'); + * driver.findElement(By.id('to')).sendKeys('dog@example.com'); + * + * __Example #3:__ Injecting a XMLHttpRequest and waiting for the result. In + * this example, the inject script is specified with a function literal. When + * using this format, the function is converted to a string for injection, so it * should not reference any symbols not defined in the scope of the page under * test. - *
        - * driver.executeAsyncScript(function() {
        - *   var callback = arguments[arguments.length - 1];
        - *   var xhr = new XMLHttpRequest();
        - *   xhr.open("GET", "/resource/data.json", true);
        - *   xhr.onreadystatechange = function() {
        - *     if (xhr.readyState == 4) {
        - *       callback(xhr.resposneText);
        - *     }
        - *   }
        - *   xhr.send('');
        - * }).then(function(str) {
        - *   console.log(JSON.parse(str)['food']);
        - * });
        - * 
        + * + * driver.executeAsyncScript(function() { + * var callback = arguments[arguments.length - 1]; + * var xhr = new XMLHttpRequest(); + * xhr.open("GET", "/resource/data.json", true); + * xhr.onreadystatechange = function() { + * if (xhr.readyState == 4) { + * callback(xhr.responseText); + * } + * } + * xhr.send(''); + * }).then(function(str) { + * console.log(JSON.parse(str)['food']); + * }); * * @param {!(string|Function)} script The script to execute. * @param {...*} var_args The arguments to pass to the script. - * @return {!webdriver.promise.Promise} A promise that will resolve to the + * @return {!webdriver.promise.Promise.} A promise that will resolve to the * scripts return value. + * @template T */ webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) { if (goog.isFunction(script)) { @@ -499,17 +628,23 @@ webdriver.WebDriver.prototype.executeAsyncScript = function(script, var_args) { /** * Schedules a command to execute a custom function. - * @param {!Function} fn The function to execute. + * @param {function(...): (T|webdriver.promise.Promise.)} fn The function to + * execute. * @param {Object=} opt_scope The object in whose scope to execute the function. * @param {...*} var_args Any arguments to pass to the function. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * function's result. + * @return {!webdriver.promise.Promise.} A promise that will be resolved' + * with the function's result. + * @template T */ webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) { var args = goog.array.slice(arguments, 2); var flow = this.flow_; return flow.execute(function() { return webdriver.promise.fullyResolved(args).then(function(args) { + if (webdriver.promise.isGenerator(fn)) { + args.unshift(fn, opt_scope); + return webdriver.promise.consume.apply(null, args); + } return fn.apply(opt_scope, args); }); }, 'WebDriver.call(' + (fn.name || 'function') + ')'); @@ -517,26 +652,81 @@ webdriver.WebDriver.prototype.call = function(fn, opt_scope, var_args) { /** - * Schedules a command to wait for a condition to hold, as defined by some - * user supplied function. If any errors occur while evaluating the wait, they - * will be allowed to propagate. - * @param {function():boolean} fn The function to evaluate as a wait condition. - * @param {number} timeout How long to wait for the condition to be true. + * Schedules a command to wait for a condition to hold. The condition may be + * specified by a {@link webdriver.until.Condition}, as a custom function, or + * as a {@link webdriver.promise.Promise}. + * + * For a {@link webdriver.until.Condition} or function, the wait will repeatedly + * evaluate the condition until it returns a truthy value. If any errors occur + * while evaluating the condition, they will be allowed to propagate. In the + * event a condition returns a {@link webdriver.promise.Promise promise}, the + * polling loop will wait for it to be resolved and use the resolved value for + * whether the condition has been satisified. Note the resolution time for + * a promise is factored into whether a wait has timed out. + * + * *Example:* waiting up to 10 seconds for an element to be present and visible + * on the page. + * + * var button = driver.wait(until.elementLocated(By.id('foo')), 10000); + * button.click(); + * + * This function may also be used to block the command flow on the resolution + * of a {@link webdriver.promise.Promise promise}. When given a promise, the + * command will simply wait for its resolution before completing. A timeout may + * be provided to fail the command if the promise does not resolve before the + * timeout expires. + * + * *Example:* Suppose you have a function, `startTestServer`, that returns a + * promise for when a server is ready for requests. You can block a `WebDriver` + * client on this promise with: + * + * var started = startTestServer(); + * driver.wait(started, 5 * 1000, 'Server should start within 5 seconds'); + * driver.get(getServerUrl()); + * + * @param {!(webdriver.promise.Promise| + * webdriver.until.Condition| + * function(!webdriver.WebDriver): T)} condition The condition to + * wait on, defined as a promise, condition object, or a function to + * evaluate as a condition. + * @param {number=} opt_timeout How long to wait for the condition to be true. * @param {string=} opt_message An optional message to use if the wait times * out. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * wait condition has been satisfied. - */ -webdriver.WebDriver.prototype.wait = function(fn, timeout, opt_message) { - return this.flow_.wait(fn, timeout, opt_message); + * @return {!webdriver.promise.Promise} A promise that will be fulfilled + * with the first truthy value returned by the condition function, or + * rejected if the condition times out. + * @template T + */ +webdriver.WebDriver.prototype.wait = function( + condition, opt_timeout, opt_message) { + if (webdriver.promise.isPromise(condition)) { + return this.flow_.wait( + /** @type {!webdriver.promise.Promise} */(condition), + opt_timeout, opt_message); + } + + var message = opt_message; + var fn = /** @type {!Function} */(condition); + if (condition instanceof webdriver.until.Condition) { + message = message || condition.description(); + fn = condition.fn; + } + + var driver = this; + return this.flow_.wait(function() { + if (webdriver.promise.isGenerator(fn)) { + return webdriver.promise.consume(fn, null, [driver]); + } + return fn(driver); + }, opt_timeout, message); }; /** * Schedules a command to make the driver sleep for the given amount of time. * @param {number} ms The amount of time, in milliseconds, to sleep. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * sleep has finished. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the sleep has finished. */ webdriver.WebDriver.prototype.sleep = function(ms) { return this.flow_.timeout(ms, 'WebDriver.sleep(' + ms + ')'); @@ -545,8 +735,8 @@ webdriver.WebDriver.prototype.sleep = function(ms) { /** * Schedules a command to retrieve they current window handle. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * current window handle. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the current window handle. */ webdriver.WebDriver.prototype.getWindowHandle = function() { return this.schedule( @@ -557,8 +747,8 @@ webdriver.WebDriver.prototype.getWindowHandle = function() { /** * Schedules a command to retrieve the current list of available window handles. - * @return {!webdriver.promise.Promise} A promise that will be resolved with an - * array of window handles. + * @return {!webdriver.promise.Promise.>} A promise that will + * be resolved with an array of window handles. */ webdriver.WebDriver.prototype.getAllWindowHandles = function() { return this.schedule( @@ -572,8 +762,8 @@ webdriver.WebDriver.prototype.getAllWindowHandles = function() { * returned is a representation of the underlying DOM: do not expect it to be * formatted or escaped in the same way as the response sent from the web * server. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * current page source. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the current page source. */ webdriver.WebDriver.prototype.getPageSource = function() { return this.schedule( @@ -584,8 +774,8 @@ webdriver.WebDriver.prototype.getPageSource = function() { /** * Schedules a command to close the current window. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * this command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when this command has completed. */ webdriver.WebDriver.prototype.close = function() { return this.schedule(new webdriver.Command(webdriver.CommandName.CLOSE), @@ -596,8 +786,8 @@ webdriver.WebDriver.prototype.close = function() { /** * Schedules a command to navigate to the given URL. * @param {string} url The fully qualified URL to open. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * document has finished loading. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the document has finished loading. */ webdriver.WebDriver.prototype.get = function(url) { return this.navigate().to(url); @@ -606,8 +796,8 @@ webdriver.WebDriver.prototype.get = function(url) { /** * Schedules a command to retrieve the URL of the current page. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * current URL. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the current URL. */ webdriver.WebDriver.prototype.getCurrentUrl = function() { return this.schedule( @@ -618,8 +808,8 @@ webdriver.WebDriver.prototype.getCurrentUrl = function() { /** * Schedules a command to retrieve the current page's title. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * current page's title. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the current page's title. */ webdriver.WebDriver.prototype.getTitle = function() { return this.schedule(new webdriver.Command(webdriver.CommandName.GET_TITLE), @@ -635,33 +825,31 @@ webdriver.WebDriver.prototype.getTitle = function() { * that the element is present on the page. To test whether an element is * present on the page, use {@link #isElementPresent} instead. * - *

        The search criteria for an element may be defined using one of the + * The search criteria for an element may be defined using one of the * factories in the {@link webdriver.By} namespace, or as a short-hand * {@link webdriver.By.Hash} object. For example, the following two statements * are equivalent: - *

        - * var e1 = driver.findElement(By.id('foo'));
        - * var e2 = driver.findElement({id:'foo'});
        - * 
        * - *

        You may also provide a custom locator function, which takes as input + * var e1 = driver.findElement(By.id('foo')); + * var e2 = driver.findElement({id:'foo'}); + * + * You may also provide a custom locator function, which takes as input * this WebDriver instance and returns a {@link webdriver.WebElement}, or a * promise that will resolve to a WebElement. For example, to find the first * visible link on a page, you could write: - *

        - * var link = driver.findElement(firstVisibleLink);
        - *
        - * function firstVisibleLink(driver) {
        - *   var links = driver.findElements(By.tagName('a'));
        - *   return webdriver.promise.filter(links, function(link) {
        - *     return links.isDisplayed();
        - *   }).then(function(visibleLinks) {
        - *     return visibleLinks[0];
        - *   });
        - * }
        - * 
        - * - *

        When running in the browser, a WebDriver cannot manipulate DOM elements + * + * var link = driver.findElement(firstVisibleLink); + * + * function firstVisibleLink(driver) { + * var links = driver.findElements(By.tagName('a')); + * return webdriver.promise.filter(links, function(link) { + * return links.isDisplayed(); + * }).then(function(visibleLinks) { + * return visibleLinks[0]; + * }); + * } + * + * When running in the browser, a WebDriver cannot manipulate DOM elements * directly; it may do so only through a {@link webdriver.WebElement} reference. * This function may be used to generate a WebElement from a DOM element. A * reference to the DOM element will be stored in a known location and this @@ -680,15 +868,14 @@ webdriver.WebDriver.prototype.findElement = function(locator) { var id; if ('nodeType' in locator && 'ownerDocument' in locator) { var element = /** @type {!Element} */ (locator); - id = this.findDomElement_(element). - then(function(elements) { - if (!elements.length) { - throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT, - 'Unable to locate element. Is WebDriver focused on its ' + - 'ownerDocument\'s frame?'); - } - return elements[0]; - }); + id = this.findDomElement_(element).then(function(element) { + if (!element) { + throw new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT, + 'Unable to locate element. Is WebDriver focused on its ' + + 'ownerDocument\'s frame?'); + } + return element; + }); } else { locator = webdriver.Locator.checkLocator(locator); if (goog.isFunction(locator)) { @@ -700,7 +887,7 @@ webdriver.WebDriver.prototype.findElement = function(locator) { id = this.schedule(command, 'WebDriver.findElement(' + locator + ')'); } } - return new webdriver.WebElement(this, id); + return new webdriver.WebElementPromise(this, id); }; @@ -735,8 +922,9 @@ webdriver.WebDriver.prototype.findElementInternal_ = function( * ownerDocument's window+frame. * @param {!Element} element The element to locate. - * @return {!webdriver.promise.Promise} A promise that will be resolved - * with the located WebElement. + * @return {!webdriver.promise.Promise.} A promise that + * will be fulfilled with the located element, or null if the element + * could not be found. * @private */ webdriver.WebDriver.prototype.findDomElement_ = function(element) { @@ -758,26 +946,22 @@ webdriver.WebDriver.prototype.findDomElement_ = function(element) { var element = store[id]; if (!element || element[id] !== id) { - return []; + return null; } - return [element]; + return element; } - return this.executeScript(lookupElement, id). - then(function(value) { - cleanUp(); - if (value.length && !(value[0] instanceof webdriver.WebElement)) { - throw new Error('JS locator script result was not a WebElement'); - } - return value; - }, cleanUp); + /** @type {!webdriver.promise.Promise.} */ + var foundElement = this.executeScript(lookupElement, id); + foundElement.thenFinally(cleanUp); + return foundElement; }; /** * Schedules a command to test if an element is present on the page. * - *

        If given a DOM element, this function will check if it belongs to the + * If given a DOM element, this function will check if it belongs to the * document the driver is currently focused on. Otherwise, the function will * test if at least one element can be found with the given search criteria. * @@ -788,13 +972,14 @@ webdriver.WebDriver.prototype.findDomElement_ = function(element) { * with whether the element is present on the page. */ webdriver.WebDriver.prototype.isElementPresent = function(locatorOrElement) { - var findElement = - ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) ? - this.findDomElement_(/** @type {!Element} */ (locatorOrElement)) : - this.findElements.apply(this, arguments); - return findElement.then(function(result) { - return !!result.length; - }); + if ('nodeType' in locatorOrElement && 'ownerDocument' in locatorOrElement) { + return this.findDomElement_(/** @type {!Element} */ (locatorOrElement)). + then(function(result) { return !!result; }); + } else { + return this.findElements.apply(this, arguments).then(function(result) { + return !!result.length; + }); + } }; @@ -855,8 +1040,8 @@ webdriver.WebDriver.prototype.findElementsInternal_ = function( *

      • The screenshot of the entire display containing the browser * * - * @return {!webdriver.promise.Promise} A promise that will be resolved to the - * screenshot as a base-64 encoded PNG. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved to the screenshot as a base-64 encoded PNG. */ webdriver.WebDriver.prototype.takeScreenshot = function() { return this.schedule(new webdriver.Command(webdriver.CommandName.SCREENSHOT), @@ -907,8 +1092,8 @@ webdriver.WebDriver.Navigation = function(driver) { /** * Schedules a command to navigate to a new URL. * @param {string} url The URL to navigate to. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * URL has been loaded. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the URL has been loaded. */ webdriver.WebDriver.Navigation.prototype.to = function(url) { return this.driver_.schedule( @@ -920,8 +1105,8 @@ webdriver.WebDriver.Navigation.prototype.to = function(url) { /** * Schedules a command to move backwards in the browser history. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * navigation event has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the navigation event has completed. */ webdriver.WebDriver.Navigation.prototype.back = function() { return this.driver_.schedule( @@ -932,8 +1117,8 @@ webdriver.WebDriver.Navigation.prototype.back = function() { /** * Schedules a command to move forwards in the browser history. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * navigation event has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the navigation event has completed. */ webdriver.WebDriver.Navigation.prototype.forward = function() { return this.driver_.schedule( @@ -944,8 +1129,8 @@ webdriver.WebDriver.Navigation.prototype.forward = function() { /** * Schedules a command to refresh the current page. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * navigation event has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the navigation event has completed. */ webdriver.WebDriver.Navigation.prototype.refresh = function() { return this.driver_.schedule( @@ -967,6 +1152,21 @@ webdriver.WebDriver.Options = function(driver) { }; +/** + * A JSON description of a browser cookie. + * @typedef {{ + * name: string, + * value: string, + * path: (string|undefined), + * domain: (string|undefined), + * secure: (boolean|undefined), + * expiry: (number|undefined) + * }} + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object + */ +webdriver.WebDriver.Options.Cookie; + + /** * Schedules a command to add a cookie. * @param {string} name The cookie name. @@ -976,8 +1176,8 @@ webdriver.WebDriver.Options = function(driver) { * @param {boolean=} opt_isSecure Whether the cookie is secure. * @param {(number|!Date)=} opt_expiry When the cookie expires. If specified as * a number, should be in milliseconds since midnight, January 1, 1970 UTC. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * cookie has been added to the page. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the cookie has been added to the page. */ webdriver.WebDriver.Options.prototype.addCookie = function( name, value, opt_path, opt_domain, opt_isSecure, opt_expiry) { @@ -1026,8 +1226,8 @@ webdriver.WebDriver.Options.prototype.addCookie = function( /** * Schedules a command to delete all cookies visible to the current page. - * @return {!webdriver.promise.Promise} A promise that will be resolved when all - * cookies have been deleted. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when all cookies have been deleted. */ webdriver.WebDriver.Options.prototype.deleteAllCookies = function() { return this.driver_.schedule( @@ -1041,8 +1241,8 @@ webdriver.WebDriver.Options.prototype.deleteAllCookies = function() { * a no-op if there is no cookie with the given name visible to the current * page. * @param {string} name The name of the cookie to delete. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * cookie has been deleted. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the cookie has been deleted. */ webdriver.WebDriver.Options.prototype.deleteCookie = function(name) { return this.driver_.schedule( @@ -1056,9 +1256,10 @@ webdriver.WebDriver.Options.prototype.deleteCookie = function(name) { * Schedules a command to retrieve all cookies visible to the current page. * Each cookie will be returned as a JSON object as described by the WebDriver * wire protocol. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * cookies visible to the current page. - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object + * @return {!webdriver.promise.Promise.< + * !Array.>} A promise that will be + * resolved with the cookies visible to the current page. + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object */ webdriver.WebDriver.Options.prototype.getCookies = function() { return this.driver_.schedule( @@ -1072,9 +1273,10 @@ webdriver.WebDriver.Options.prototype.getCookies = function() { * if there is no such cookie. The cookie will be returned as a JSON object as * described by the WebDriver wire protocol. * @param {string} name The name of the cookie to retrieve. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * named cookie, or {@code null} if there is no such cookie. - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol#Cookie_JSON_Object + * @return {!webdriver.promise.Promise.} A + * promise that will be resolved with the named cookie, or {@code null} + * if there is no such cookie. + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#cookie-json-object */ webdriver.WebDriver.Options.prototype.getCookie = function(name) { return this.getCookies().then(function(cookies) { @@ -1128,23 +1330,23 @@ webdriver.WebDriver.Timeouts = function(driver) { /** * Specifies the amount of time the driver should wait when searching for an * element if it is not immediately present. - *

        + * * When searching for a single element, the driver should poll the page * until the element has been found, or this timeout expires before failing - * with a {@code bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching + * with a {@link bot.ErrorCode.NO_SUCH_ELEMENT} error. When searching * for multiple elements, the driver should poll the page until at least one * element has been found or this timeout has expired. - *

        + * * Setting the wait timeout to 0 (its default value), disables implicit * waiting. - *

        + * * Increasing the implicit wait timeout should be used judiciously as it * will have an adverse effect on test run time, especially when used with * slower location strategies like XPath. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * implicit wait timeout has been set. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the implicit wait timeout has been set. */ webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) { return this.driver_.schedule( @@ -1160,8 +1362,8 @@ webdriver.WebDriver.Timeouts.prototype.implicitlyWait = function(ms) { * equal to 0, the script will be allowed to run indefinitely. * * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * script timeout has been set. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the script timeout has been set. */ webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) { return this.driver_.schedule( @@ -1175,8 +1377,8 @@ webdriver.WebDriver.Timeouts.prototype.setScriptTimeout = function(ms) { * Sets the amount of time to wait for a page load to complete before returning * an error. If the timeout is negative, page loads may be indefinite. * @param {number} ms The amount of time to wait, in milliseconds. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * the timeout has been set. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the timeout has been set. */ webdriver.WebDriver.Timeouts.prototype.pageLoadTimeout = function(ms) { return this.driver_.schedule( @@ -1203,8 +1405,9 @@ webdriver.WebDriver.Window = function(driver) { /** * Retrieves the window's current position, relative to the top left corner of * the screen. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * window's position in the form of a {x:number, y:number} object literal. + * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that + * will be resolved with the window's position in the form of a + * {x:number, y:number} object literal. */ webdriver.WebDriver.Window.prototype.getPosition = function() { return this.driver_.schedule( @@ -1220,8 +1423,8 @@ webdriver.WebDriver.Window.prototype.getPosition = function() { * of the screen. * @param {number} y The desired vertical position, relative to the top of the * of the screen. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the command has completed. */ webdriver.WebDriver.Window.prototype.setPosition = function(x, y) { return this.driver_.schedule( @@ -1235,9 +1438,9 @@ webdriver.WebDriver.Window.prototype.setPosition = function(x, y) { /** * Retrieves the window's current size. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * window's size in the form of a {width:number, height:number} object - * literal. + * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A + * promise that will be resolved with the window's size in the form of a + * {width:number, height:number} object literal. */ webdriver.WebDriver.Window.prototype.getSize = function() { return this.driver_.schedule( @@ -1251,8 +1454,8 @@ webdriver.WebDriver.Window.prototype.getSize = function() { * Resizes the current window. * @param {number} width The desired window width. * @param {number} height The desired window height. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the command has completed. */ webdriver.WebDriver.Window.prototype.setSize = function(width, height) { return this.driver_.schedule( @@ -1266,8 +1469,8 @@ webdriver.WebDriver.Window.prototype.setSize = function(width, height) { /** * Maximizes the current window. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the command has completed. */ webdriver.WebDriver.Window.prototype.maximize = function() { return this.driver_.schedule( @@ -1292,11 +1495,10 @@ webdriver.WebDriver.Logs = function(driver) { /** * Fetches available log entries for the given type. * - *

        Note that log buffers are reset after each call, meaning that - * available log entries correspond to those entries not yet returned for a - * given log type. In practice, this means that this call will return the - * available log entries since the last call, or from the start of the - * session. + * Note that log buffers are reset after each call, meaning that available + * log entries correspond to those entries not yet returned for a given log + * type. In practice, this means that this call will return the available log + * entries since the last call, or from the start of the session. * * @param {!webdriver.logging.Type} type The desired log type. * @return {!webdriver.promise.Promise.>} A @@ -1312,7 +1514,8 @@ webdriver.WebDriver.Logs.prototype.get = function(type) { return goog.array.map(entries, function(entry) { if (!(entry instanceof webdriver.logging.Entry)) { return new webdriver.logging.Entry( - entry['level'], entry['message'], entry['timestamp']); + entry['level'], entry['message'], entry['timestamp'], + entry['type']); } return entry; }); @@ -1349,21 +1552,21 @@ webdriver.WebDriver.TargetLocator = function(driver) { * Schedules a command retrieve the {@code document.activeElement} element on * the current document, or {@code document.body} if activeElement is not * available. - * @return {!webdriver.WebElement} The active element. + * @return {!webdriver.WebElementPromise} The active element. */ webdriver.WebDriver.TargetLocator.prototype.activeElement = function() { var id = this.driver_.schedule( new webdriver.Command(webdriver.CommandName.GET_ACTIVE_ELEMENT), 'WebDriver.switchTo().activeElement()'); - return new webdriver.WebElement(this.driver_, id); + return new webdriver.WebElementPromise(this.driver_, id); }; /** * Schedules a command to switch focus of all future commands to the first frame * on the page. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * driver has changed focus to the default content. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the driver has changed focus to the default content. */ webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() { return this.driver_.schedule( @@ -1376,20 +1579,22 @@ webdriver.WebDriver.TargetLocator.prototype.defaultContent = function() { /** * Schedules a command to switch the focus of all future commands to another * frame on the page. - *

        + * * If the frame is specified by a number, the command will switch to the frame - * by its (zero-based) index into the {@code window.frames} collection. - *

        + * by its (zero-based) index into + * [window.frames](https://developer.mozilla.org/en-US/docs/Web/API/Window.frames). + * * If the frame is specified by a string, the command will select the frame by * its name or ID. To select sub-frames, simply separate the frame names/IDs by * dots. As an example, "main.child" will select the frame with the name "main" * and then its child "child". - *

        + * * If the specified frame can not be found, the deferred result will errback - * with a {@code bot.ErrorCode.NO_SUCH_FRAME} error. + * with a {@link bot.ErrorCode.NO_SUCH_FRAME} error. + * * @param {string|number} nameOrIndex The frame locator. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * driver has changed focus to the specified frame. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the driver has changed focus to the specified frame. */ webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) { return this.driver_.schedule( @@ -1402,14 +1607,15 @@ webdriver.WebDriver.TargetLocator.prototype.frame = function(nameOrIndex) { /** * Schedules a command to switch the focus of all future commands to another * window. Windows may be specified by their {@code window.name} attribute or - * by its handle (as returned by {@code webdriver.WebDriver#getWindowHandles}). - *

        + * by its handle (as returned by {@link webdriver.WebDriver#getWindowHandles}). + * * If the specificed window can not be found, the deferred result will errback - * with a {@code bot.ErrorCode.NO_SUCH_WINDOW} error. + * with a {@link bot.ErrorCode.NO_SUCH_WINDOW} error. + * * @param {string} nameOrHandle The name or window handle of the window to * switch focus to. - * @return {!webdriver.promise.Promise} A promise that will be resolved when the - * driver has changed focus to the specified window. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the driver has changed focus to the specified window. */ webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) { return this.driver_.schedule( @@ -1421,15 +1627,18 @@ webdriver.WebDriver.TargetLocator.prototype.window = function(nameOrHandle) { /** * Schedules a command to change focus to the active alert dialog. This command - * will return a {@link bot.ErrorCode.NO_MODAL_DIALOG_OPEN} error if a modal - * dialog is not currently open. - * @return {!webdriver.Alert} The open alert. + * will return a {@link bot.ErrorCode.NO_SUCH_ALERT} error if an alert dialog + * is not currently open. + * @return {!webdriver.AlertPromise} The open alert. */ webdriver.WebDriver.TargetLocator.prototype.alert = function() { var text = this.driver_.schedule( new webdriver.Command(webdriver.CommandName.GET_ALERT_TEXT), 'WebDriver.switchTo().alert()'); - return new webdriver.Alert(this.driver_, text); + var driver = this.driver_; + return new webdriver.AlertPromise(driver, text.then(function(text) { + return new webdriver.Alert(driver, text); + })); }; @@ -1467,75 +1676,52 @@ webdriver.Key.chord = function(var_args) { /** * Represents a DOM element. WebElements can be found by searching from the - * document root using a {@code webdriver.WebDriver} instance, or by searching - * under another {@code webdriver.WebElement}: - *

        
        - *   driver.get('http://www.google.com');
        - *   var searchForm = driver.findElement(By.tagName('form'));
        - *   var searchBox = searchForm.findElement(By.name('q'));
        - *   searchBox.sendKeys('webdriver');
        - * 
        + * document root using a {@link webdriver.WebDriver} instance, or by searching + * under another WebElement: + * + * driver.get('http://www.google.com'); + * var searchForm = driver.findElement(By.tagName('form')); + * var searchBox = searchForm.findElement(By.name('q')); + * searchBox.sendKeys('webdriver'); * * The WebElement is implemented as a promise for compatibility with the promise * API. It will always resolve itself when its internal state has been fully * resolved and commands may be issued against the element. This can be used to * catch errors when an element cannot be located on the page: - *
        
        - *   driver.findElement(By.id('not-there')).then(function(element) {
        - *     alert('Found an element that was not expected to be there!');
        - *   }, function(error) {
        - *     alert('The element was not found, as expected');
        - *   });
        - * 
        + * + * driver.findElement(By.id('not-there')).then(function(element) { + * alert('Found an element that was not expected to be there!'); + * }, function(error) { + * alert('The element was not found, as expected'); + * }); * * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this * element. - * @param {!(string|webdriver.promise.Promise)} id Either the opaque ID for the - * underlying DOM element assigned by the server, or a promise that will - * resolve to that ID or another WebElement. + * @param {!(webdriver.promise.Promise.| + * webdriver.WebElement.Id)} id The server-assigned opaque ID for the + * underlying DOM element. * @constructor - * @extends {webdriver.promise.Deferred} + * @extends {webdriver.Serializable.} */ webdriver.WebElement = function(driver, id) { - webdriver.promise.Deferred.call(this, null, driver.controlFlow()); + webdriver.Serializable.call(this); - /** - * The parent WebDriver instance for this element. - * @private {!webdriver.WebDriver} - */ + /** @private {!webdriver.WebDriver} */ this.driver_ = driver; - // This class is responsible for resolving itself; delete the resolve and - // reject methods so they may not be accessed by consumers of this class. - var fulfill = goog.partial(this.fulfill, this); - var reject = this.reject; - delete this.promise; - delete this.fulfill; - delete this.reject; - - /** - * A promise that resolves to the JSON representation of this WebElement's - * ID, as defined by the WebDriver wire protocol. - * @private {!webdriver.promise.Promise} - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol - */ - this.id_ = webdriver.promise.when(id, function(id) { - if (id instanceof webdriver.WebElement) { - return id.id_; - } else if (goog.isDef(id[webdriver.WebElement.ELEMENT_KEY])) { - return id; - } + /** @private {!webdriver.promise.Promise.} */ + this.id_ = id instanceof webdriver.promise.Promise ? + id : webdriver.promise.fulfilled(id); +}; +goog.inherits(webdriver.WebElement, webdriver.Serializable); - var json = {}; - json[webdriver.WebElement.ELEMENT_KEY] = id; - return json; - }); - // This WebElement should not be resolved until its ID has been - // fully resolved. - this.id_.then(fulfill, reject); -}; -goog.inherits(webdriver.WebElement, webdriver.promise.Deferred); +/** + * Wire protocol definition of a WebElement ID. + * @typedef {{ELEMENT: string}} + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol + */ +webdriver.WebElement.Id; /** @@ -1551,14 +1737,15 @@ webdriver.WebElement.ELEMENT_KEY = 'ELEMENT'; * Compares to WebElements for equality. * @param {!webdriver.WebElement} a A WebElement. * @param {!webdriver.WebElement} b A WebElement. - * @return {!webdriver.promise.Promise} A promise that will be resolved to - * whether the two WebElements are equal. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved to whether the two WebElements are equal. */ webdriver.WebElement.equals = function(a, b) { if (a == b) { return webdriver.promise.fulfilled(true); } - return webdriver.promise.fullyResolved([a.id_, b.id_]).then(function(ids) { + var ids = [a.getId(), b.getId()]; + return webdriver.promise.all(ids).then(function(ids) { // If the two element's have the same ID, they should be considered // equal. Otherwise, they may still be equivalent, but we'll need to // ask the server to check for us. @@ -1567,10 +1754,10 @@ webdriver.WebElement.equals = function(a, b) { return true; } - var command = new webdriver.Command( - webdriver.CommandName.ELEMENT_EQUALS); - command.setParameter('other', b); - return a.schedule_(command, 'webdriver.WebElement.equals()'); + var command = new webdriver.Command(webdriver.CommandName.ELEMENT_EQUALS); + command.setParameter('id', ids[0]); + command.setParameter('other', ids[1]); + return a.driver_.schedule(command, 'webdriver.WebElement.equals()'); }); }; @@ -1584,65 +1771,84 @@ webdriver.WebElement.prototype.getDriver = function() { /** - * @return {!webdriver.promise.Promise} A promise that resolves to this - * element's JSON representation as defined by the WebDriver wire protocol. - * @see http://code.google.com/p/selenium/wiki/JsonWireProtocol + * @return {!webdriver.promise.Promise.} A promise + * that resolves to this element's JSON representation as defined by the + * WebDriver wire protocol. + * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol */ -webdriver.WebElement.prototype.toWireValue = function() { +webdriver.WebElement.prototype.getId = function() { return this.id_; }; +/** + * Returns the raw ID string ID for this element. + * @return {!webdriver.promise.Promise} A promise that resolves to this + * element's raw ID as a string value. + * @package + */ +webdriver.WebElement.prototype.getRawId = function() { + return this.getId().then(function(value) { + return value['ELEMENT']; + }); +}; + + +/** @override */ +webdriver.WebElement.prototype.serialize = function() { + return this.getId(); +}; + + /** * Schedules a command that targets this element with the parent WebDriver * instance. Will ensure this element's ID is included in the command parameters * under the "id" key. * @param {!webdriver.Command} command The command to schedule. * @param {string} description A description of the command for debugging. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * the command result. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * with the command result. + * @template T * @see webdriver.WebDriver.prototype.schedule * @private */ webdriver.WebElement.prototype.schedule_ = function(command, description) { - command.setParameter('id', this.id_); + command.setParameter('id', this.getId()); return this.driver_.schedule(command, description); }; /** * Schedule a command to find a descendant of this element. If the element - * cannot be found, a {@code bot.ErrorCode.NO_SUCH_ELEMENT} result will + * cannot be found, a {@link bot.ErrorCode.NO_SUCH_ELEMENT} result will * be returned by the driver. Unlike other commands, this error cannot be * suppressed. In other words, scheduling a command to find an element doubles * as an assert that the element is present on the page. To test whether an - * element is present on the page, use {@code #isElementPresent} instead. + * element is present on the page, use {@link #isElementPresent} instead. * - *

        The search criteria for an element may be defined using one of the + * The search criteria for an element may be defined using one of the * factories in the {@link webdriver.By} namespace, or as a short-hand * {@link webdriver.By.Hash} object. For example, the following two statements * are equivalent: - *

        - * var e1 = element.findElement(By.id('foo'));
        - * var e2 = element.findElement({id:'foo'});
        - * 
        * - *

        You may also provide a custom locator function, which takes as input + * var e1 = element.findElement(By.id('foo')); + * var e2 = element.findElement({id:'foo'}); + * + * You may also provide a custom locator function, which takes as input * this WebDriver instance and returns a {@link webdriver.WebElement}, or a * promise that will resolve to a WebElement. For example, to find the first * visible link on a page, you could write: - *

        - * var link = element.findElement(firstVisibleLink);
        - *
        - * function firstVisibleLink(element) {
        - *   var links = element.findElements(By.tagName('a'));
        - *   return webdriver.promise.filter(links, function(link) {
        - *     return links.isDisplayed();
        - *   }).then(function(visibleLinks) {
        - *     return visibleLinks[0];
        - *   });
        - * }
        - * 
        + * + * var link = element.findElement(firstVisibleLink); + * + * function firstVisibleLink(element) { + * var links = element.findElements(By.tagName('a')); + * return webdriver.promise.filter(links, function(link) { + * return links.isDisplayed(); + * }).then(function(visibleLinks) { + * return visibleLinks[0]; + * }); + * } * * @param {!(webdriver.Locator|webdriver.By.Hash|Function)} locator The * locator strategy to use when searching for the element. @@ -1662,7 +1868,7 @@ webdriver.WebElement.prototype.findElement = function(locator) { setParameter('value', locator.value); id = this.schedule_(command, 'WebElement.findElement(' + locator + ')'); } - return new webdriver.WebElement(this.driver_, id); + return new webdriver.WebElementPromise(this.driver_, id); }; @@ -1707,8 +1913,8 @@ webdriver.WebElement.prototype.findElements = function(locator) { /** * Schedules a command to click on this element. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * the click command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the click command has completed. */ webdriver.WebElement.prototype.click = function() { return this.schedule_( @@ -1720,64 +1926,93 @@ webdriver.WebElement.prototype.click = function() { /** * Schedules a command to type a sequence on the DOM element represented by this * instance. - *

        + * * Modifier keys (SHIFT, CONTROL, ALT, META) are stateful; once a modifier is * processed in the keysequence, that key state is toggled until one of the * following occurs: - *

          - *
        • The modifier key is encountered again in the sequence. At this point the - * state of the key is toggled (along with the appropriate keyup/down events). - *
        • - *
        • The {@code webdriver.Key.NULL} key is encountered in the sequence. When - * this key is encountered, all modifier keys current in the down state are - * released (with accompanying keyup events). The NULL key can be used to - * simulate common keyboard shortcuts: - *
          - *     element.sendKeys("text was",
          - *                      webdriver.Key.CONTROL, "a", webdriver.Key.NULL,
          - *                      "now text is");
          - *     // Alternatively:
          - *     element.sendKeys("text was",
          - *                      webdriver.Key.chord(webdriver.Key.CONTROL, "a"),
          - *                      "now text is");
          - * 
        • - *
        • The end of the keysequence is encountered. When there are no more keys - * to type, all depressed modifier keys are released (with accompanying keyup - * events). - *
        • - *
        - * Note: On browsers where native keyboard events are not yet - * supported (e.g. Firefox on OS X), key events will be synthesized. Special + * + * - The modifier key is encountered again in the sequence. At this point the + * state of the key is toggled (along with the appropriate keyup/down events). + * - The {@link webdriver.Key.NULL} key is encountered in the sequence. When + * this key is encountered, all modifier keys current in the down state are + * released (with accompanying keyup events). The NULL key can be used to + * simulate common keyboard shortcuts: + * + * element.sendKeys("text was", + * webdriver.Key.CONTROL, "a", webdriver.Key.NULL, + * "now text is"); + * // Alternatively: + * element.sendKeys("text was", + * webdriver.Key.chord(webdriver.Key.CONTROL, "a"), + * "now text is"); + * + * - The end of the keysequence is encountered. When there are no more keys + * to type, all depressed modifier keys are released (with accompanying keyup + * events). + * + * If this element is a file input ({@code }), the + * specified key sequence should specify the path to the file to attach to + * the element. This is analgous to the user clicking "Browse..." and entering + * the path into the file select dialog. + * + * var form = driver.findElement(By.css('form')); + * var element = form.findElement(By.css('input[type=file]')); + * element.sendKeys('/path/to/file.txt'); + * form.submit(); + * + * For uploads to function correctly, the entered path must reference a file + * on the _browser's_ machine, not the local machine running this script. When + * running against a remote Selenium server, a {@link webdriver.FileDetector} + * may be used to transparently copy files to the remote machine before + * attempting to upload them in the browser. + * + * __Note:__ On browsers where native keyboard events are not supported + * (e.g. Firefox on OS X), key events will be synthesized. Special * punctionation keys will be synthesized according to a standard QWERTY en-us * keyboard layout. * - * @param {...string} var_args The sequence of keys to - * type. All arguments will be joined into a single sequence (var_args is - * permitted for convenience). - * @return {!webdriver.promise.Promise} A promise that will be resolved when all - * keys have been typed. + * @param {...(string|!webdriver.promise.Promise)} var_args The sequence + * of keys to type. All arguments will be joined into a single sequence. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when all keys have been typed. */ webdriver.WebElement.prototype.sendKeys = function(var_args) { // Coerce every argument to a string. This protects us from users that // ignore the jsdoc and give us a number (which ends up causing problems on // the server, which requires strings). - var keys = webdriver.promise.fullyResolved(goog.array.slice(arguments, 0)). - then(function(args) { - return goog.array.map(goog.array.slice(args, 0), function(key) { - return key + ''; - }); + var keys = webdriver.promise.all(goog.array.slice(arguments, 0)). + then(function(keys) { + return goog.array.map(keys, String); }); - return this.schedule_( - new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT). - setParameter('value', keys), - 'WebElement.sendKeys(' + keys + ')'); + if (!this.driver_.fileDetector_) { + return this.schedule_( + new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT). + setParameter('value', keys), + 'WebElement.sendKeys()'); + } + + // Suppress unhandled rejection errors until the flow executes the command. + keys.thenCatch(goog.nullFunction); + + var element = this; + return this.driver_.flow_.execute(function() { + return keys.then(function(keys) { + return element.driver_.fileDetector_ + .handleFile(element.driver_, keys.join('')); + }).then(function(keys) { + return element.schedule_( + new webdriver.Command(webdriver.CommandName.SEND_KEYS_TO_ELEMENT). + setParameter('value', [keys]), + 'WebElement.sendKeys()'); + }); + }, 'WebElement.sendKeys()'); }; /** * Schedules a command to query for the tag/node name of this element. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * element's tag name. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the element's tag name. */ webdriver.WebElement.prototype.getTagName = function() { return this.schedule_( @@ -1792,14 +2027,14 @@ webdriver.WebElement.prototype.getTagName = function() { * its parent, the parent will be queried for its value. Where possible, color * values will be converted to their hex representation (e.g. #00ff00 instead of * rgb(0, 255, 0)). - *

        - * Warning: the value returned will be as the browser interprets it, so + * + * _Warning:_ the value returned will be as the browser interprets it, so * it may be tricky to form a proper assertion. * * @param {string} cssStyleProperty The name of the CSS style property to look * up. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * requested CSS value. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the requested CSS value. */ webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) { var name = webdriver.CommandName.GET_ELEMENT_VALUE_OF_CSS_PROPERTY; @@ -1821,23 +2056,23 @@ webdriver.WebElement.prototype.getCssValue = function(cssStyleProperty) { * text representation with a trailing semi-colon. The following are deemed to * be "boolean" attributes and will return either "true" or null: * - *

        async, autofocus, autoplay, checked, compact, complete, controls, declare, + * async, autofocus, autoplay, checked, compact, complete, controls, declare, * defaultchecked, defaultselected, defer, disabled, draggable, ended, * formnovalidate, hidden, indeterminate, iscontenteditable, ismap, itemscope, * loop, multiple, muted, nohref, noresize, noshade, novalidate, nowrap, open, * paused, pubdate, readonly, required, reversed, scoped, seamless, seeking, * selected, spellcheck, truespeed, willvalidate * - *

        Finally, the following commonly mis-capitalized attribute/property names + * Finally, the following commonly mis-capitalized attribute/property names * are evaluated as expected: - *

          - *
        • "class" - *
        • "readonly" - *
        + * + * - "class" + * - "readonly" + * * @param {string} attributeName The name of the attribute to query. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * attribute's value. The returned value will always be either a string or - * null. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the attribute's value. The returned value will always be + * either a string or null. */ webdriver.WebElement.prototype.getAttribute = function(attributeName) { return this.schedule_( @@ -1850,8 +2085,8 @@ webdriver.WebElement.prototype.getAttribute = function(attributeName) { /** * Get the visible (i.e. not hidden by CSS) innerText of this element, including * sub-elements, without any leading or trailing whitespace. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * element's visible text. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the element's visible text. */ webdriver.WebElement.prototype.getText = function() { return this.schedule_( @@ -1863,8 +2098,9 @@ webdriver.WebElement.prototype.getText = function() { /** * Schedules a command to compute the size of this element's bounding box, in * pixels. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * element's size as a {@code {width:number, height:number}} object. + * @return {!webdriver.promise.Promise.<{width: number, height: number}>} A + * promise that will be resolved with the element's size as a + * {@code {width:number, height:number}} object. */ webdriver.WebElement.prototype.getSize = function() { return this.schedule_( @@ -1875,8 +2111,9 @@ webdriver.WebElement.prototype.getSize = function() { /** * Schedules a command to compute the location of this element in page space. - * @return {!webdriver.promise.Promise} A promise that will be resolved to the - * element's location as a {@code {x:number, y:number}} object. + * @return {!webdriver.promise.Promise.<{x: number, y: number}>} A promise that + * will be resolved to the element's location as a + * {@code {x:number, y:number}} object. */ webdriver.WebElement.prototype.getLocation = function() { return this.schedule_( @@ -1888,8 +2125,8 @@ webdriver.WebElement.prototype.getLocation = function() { /** * Schedules a command to query whether the DOM element represented by this * instance is enabled, as dicted by the {@code disabled} attribute. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * whether this element is currently enabled. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with whether this element is currently enabled. */ webdriver.WebElement.prototype.isEnabled = function() { return this.schedule_( @@ -1900,8 +2137,8 @@ webdriver.WebElement.prototype.isEnabled = function() { /** * Schedules a command to query whether this element is selected. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * whether this element is currently selected. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with whether this element is currently selected. */ webdriver.WebElement.prototype.isSelected = function() { return this.schedule_( @@ -1914,8 +2151,8 @@ webdriver.WebElement.prototype.isSelected = function() { * Schedules a command to submit the form containing this element (or this * element if it is a FORM element). This command is a no-op if the element is * not contained in a form. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * the form has been submitted. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the form has been submitted. */ webdriver.WebElement.prototype.submit = function() { return this.schedule_( @@ -1928,8 +2165,8 @@ webdriver.WebElement.prototype.submit = function() { * Schedules a command to clear the {@code value} of this element. This command * has no effect if the underlying DOM element is neither a text INPUT element * nor a TEXTAREA element. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * the element has been cleared. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when the element has been cleared. */ webdriver.WebElement.prototype.clear = function() { return this.schedule_( @@ -1940,8 +2177,8 @@ webdriver.WebElement.prototype.clear = function() { /** * Schedules a command to test whether this element is currently displayed. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * whether this element is currently visible on the page. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with whether this element is currently visible on the page. */ webdriver.WebElement.prototype.isDisplayed = function() { return this.schedule_( @@ -1952,8 +2189,8 @@ webdriver.WebElement.prototype.isDisplayed = function() { /** * Schedules a command to retrieve the outer HTML of this element. - * @return {!webdriver.promise.Promise} A promise that will be resolved with - * the element's outer HTML. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the element's outer HTML. */ webdriver.WebElement.prototype.getOuterHtml = function() { return this.driver_.executeScript(function() { @@ -1971,8 +2208,8 @@ webdriver.WebElement.prototype.getOuterHtml = function() { /** * Schedules a command to retrieve the inner HTML of this element. - * @return {!webdriver.promise.Promise} A promise that will be resolved with the - * element's inner HTML. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved with the element's inner HTML. */ webdriver.WebElement.prototype.getInnerHtml = function() { return this.driver_.executeScript('return arguments[0].innerHTML', this); @@ -1980,6 +2217,59 @@ webdriver.WebElement.prototype.getInnerHtml = function() { +/** + * WebElementPromise is a promise that will be fulfilled with a WebElement. + * This serves as a forward proxy on WebElement, allowing calls to be + * scheduled without directly on this instance before the underlying + * WebElement has been fulfilled. In other words, the following two statements + * are equivalent: + * + * driver.findElement({id: 'my-button'}).click(); + * driver.findElement({id: 'my-button'}).then(function(el) { + * return el.click(); + * }); + * + * @param {!webdriver.WebDriver} driver The parent WebDriver instance for this + * element. + * @param {!webdriver.promise.Promise.} el A promise + * that will resolve to the promised element. + * @constructor + * @extends {webdriver.WebElement} + * @implements {webdriver.promise.Thenable.} + * @final + */ +webdriver.WebElementPromise = function(driver, el) { + webdriver.WebElement.call(this, driver, {'ELEMENT': 'unused'}); + + /** @override */ + this.cancel = goog.bind(el.cancel, el); + + /** @override */ + this.isPending = goog.bind(el.isPending, el); + + /** @override */ + this.then = goog.bind(el.then, el); + + /** @override */ + this.thenCatch = goog.bind(el.thenCatch, el); + + /** @override */ + this.thenFinally = goog.bind(el.thenFinally, el); + + /** + * Defers returning the element ID until the wrapped WebElement has been + * resolved. + * @override + */ + this.getId = function() { + return el.then(function(el) { + return el.getId(); + }); + }; +}; +goog.inherits(webdriver.WebElementPromise, webdriver.WebElement); + + /** * Represents a modal dialog such as {@code alert}, {@code confirm}, or * {@code prompt}. Provides functions to retrieve the message displayed with @@ -1987,40 +2277,23 @@ webdriver.WebElement.prototype.getInnerHtml = function() { * case of {@code prompt}). * @param {!webdriver.WebDriver} driver The driver controlling the browser this * alert is attached to. - * @param {!(string|webdriver.promise.Promise)} text Either the message text - * displayed with this alert, or a promise that will be resolved to said - * text. + * @param {string} text The message text displayed with this alert. * @constructor - * @extends {webdriver.promise.Deferred} */ webdriver.Alert = function(driver, text) { - goog.base(this, null, driver.controlFlow()); - /** @private {!webdriver.WebDriver} */ this.driver_ = driver; - // This class is responsible for resolving itself; delete the resolve and - // reject methods so they may not be accessed by consumers of this class. - var fulfill = goog.partial(this.fulfill, this); - var reject = this.reject; - delete this.promise; - delete this.fulfill; - delete this.reject; - - /** @private {!webdriver.promise.Promise} */ + /** @private {!webdriver.promise.Promise.} */ this.text_ = webdriver.promise.when(text); - - // Make sure this instance is resolved when its displayed text is. - this.text_.then(fulfill, reject); }; -goog.inherits(webdriver.Alert, webdriver.promise.Deferred); /** * Retrieves the message text displayed with this alert. For instance, if the * alert were opened with alert("hello"), then this would return "hello". - * @return {!webdriver.promise.Promise} A promise that will be resolved to the - * text displayed with this alert. + * @return {!webdriver.promise.Promise.} A promise that will be + * resolved to the text displayed with this alert. */ webdriver.Alert.prototype.getText = function() { return this.text_; @@ -2029,8 +2302,8 @@ webdriver.Alert.prototype.getText = function() { /** * Accepts this alert. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * this command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when this command has completed. */ webdriver.Alert.prototype.accept = function() { return this.driver_.schedule( @@ -2041,8 +2314,8 @@ webdriver.Alert.prototype.accept = function() { /** * Dismisses this alert. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * this command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when this command has completed. */ webdriver.Alert.prototype.dismiss = function() { return this.driver_.schedule( @@ -2056,8 +2329,8 @@ webdriver.Alert.prototype.dismiss = function() { * the underlying alert does not support response text (e.g. window.alert and * window.confirm). * @param {string} text The text to set. - * @return {!webdriver.promise.Promise} A promise that will be resolved when - * this command has completed. + * @return {!webdriver.promise.Promise.} A promise that will be resolved + * when this command has completed. */ webdriver.Alert.prototype.sendKeys = function(text) { return this.driver_.schedule( @@ -2068,16 +2341,103 @@ webdriver.Alert.prototype.sendKeys = function(text) { +/** + * AlertPromise is a promise that will be fulfilled with an Alert. This promise + * serves as a forward proxy on an Alert, allowing calls to be scheduled + * directly on this instance before the underlying Alert has been fulfilled. In + * other words, the following two statements are equivalent: + * + * driver.switchTo().alert().dismiss(); + * driver.switchTo().alert().then(function(alert) { + * return alert.dismiss(); + * }); + * + * @param {!webdriver.WebDriver} driver The driver controlling the browser this + * alert is attached to. + * @param {!webdriver.promise.Thenable.} alert A thenable + * that will be fulfilled with the promised alert. + * @constructor + * @extends {webdriver.Alert} + * @implements {webdriver.promise.Thenable.} + * @final + */ +webdriver.AlertPromise = function(driver, alert) { + webdriver.Alert.call(this, driver, 'unused'); + + /** @override */ + this.cancel = goog.bind(alert.cancel, alert); + + /** @override */ + this.isPending = goog.bind(alert.isPending, alert); + + /** @override */ + this.then = goog.bind(alert.then, alert); + + /** @override */ + this.thenCatch = goog.bind(alert.thenCatch, alert); + + /** @override */ + this.thenFinally = goog.bind(alert.thenFinally, alert); + + /** + * Defer returning text until the promised alert has been resolved. + * @override + */ + this.getText = function() { + return alert.then(function(alert) { + return alert.getText(); + }); + }; + + /** + * Defers action until the alert has been located. + * @override + */ + this.accept = function() { + return alert.then(function(alert) { + return alert.accept(); + }); + }; + + /** + * Defers action until the alert has been located. + * @override + */ + this.dismiss = function() { + return alert.then(function(alert) { + return alert.dismiss(); + }); + }; + + /** + * Defers action until the alert has been located. + * @override + */ + this.sendKeys = function(text) { + return alert.then(function(alert) { + return alert.sendKeys(text); + }); + }; +}; +goog.inherits(webdriver.AlertPromise, webdriver.Alert); + + + /** * An error returned to indicate that there is an unhandled modal dialog on the * current page. * @param {string} message The error message. + * @param {string} text The text displayed with the unhandled alert. * @param {!webdriver.Alert} alert The alert handle. * @constructor * @extends {bot.Error} */ -webdriver.UnhandledAlertError = function(message, alert) { - goog.base(this, bot.ErrorCode.MODAL_DIALOG_OPENED, message); +webdriver.UnhandledAlertError = function(message, text, alert) { + webdriver.UnhandledAlertError.base( + this, 'constructor', bot.ErrorCode.UNEXPECTED_ALERT_OPEN, message); + + /** @private {string} */ + this.text_ = text; /** @private {!webdriver.Alert} */ this.alert_ = alert; @@ -2086,8 +2446,47 @@ goog.inherits(webdriver.UnhandledAlertError, bot.Error); /** - * @return {!webdriver.Alert} The open alert. + * @return {string} The text displayed with the unhandled alert. */ -webdriver.UnhandledAlertError.prototype.getAlert = function() { - return this.alert_; +webdriver.UnhandledAlertError.prototype.getAlertText = function() { + return this.text_; }; + + + +/** + * Used with {@link webdriver.WebElement#sendKeys WebElement#sendKeys} on file + * input elements ({@code }) to detect when the entered key + * sequence defines the path to a file. + * + * By default, {@linkplain webdriver.WebElement WebElement's} will enter all + * key sequences exactly as entered. You may set a + * {@linkplain webdriver.WebDriver#setFileDetector file detector} on the parent + * WebDriver instance to define custom behavior for handling file elements. Of + * particular note is the {@link selenium-webdriver/remote.FileDetector}, which + * should be used when running against a remote + * [Selenium Server](http://docs.seleniumhq.org/download/). + */ +webdriver.FileDetector = goog.defineClass(null, { + /** @constructor */ + constructor: function() {}, + + /** + * Handles the file specified by the given path, preparing it for use with + * the current browser. If the path does not refer to a valid file, it will + * be returned unchanged, otherwisee a path suitable for use with the current + * browser will be returned. + * + * This default implementation is a no-op. Subtypes may override this + * function for custom tailored file handling. + * + * @param {!webdriver.WebDriver} driver The driver for the current browser. + * @param {string} path The path to process. + * @return {!webdriver.promise.Promise} A promise for the processed + * file path. + * @package + */ + handleFile: function(driver, path) { + return webdriver.promise.fulfilled(path); + } +}); diff --git a/net/index.js b/net/index.js index 801ec28..4142ebc 100644 --- a/net/index.js +++ b/net/index.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -41,7 +43,8 @@ function getAddress(loopback, opt_family) { var interfaces; if (loopback) { - interfaces = [getLoInterface()]; + var lo = getLoInterface(); + interfaces = lo ? [lo] : null; } interfaces = interfaces || os.networkInterfaces(); for (var key in interfaces) { diff --git a/net/portprober.js b/net/portprober.js index ee57f12..5181167 100644 --- a/net/portprober.js +++ b/net/portprober.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -60,7 +62,7 @@ function findSystemPortRange() { /** * Executes a command and returns its output if it succeeds. * @param {string} cmd The command to execute. - * @return {!webdriver.promise.Promise} A promise that will resolve + * @return {!webdriver.promise.Promise.} A promise that will resolve * with the command's stdout data. */ function execute(cmd) { @@ -78,7 +80,7 @@ function execute(cmd) { /** * Computes the ephemeral port range for a Unix-like system. - * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise + * @return {!webdriver.promise.Promise.<{min: number, max: number}>} A promise * that will resolve with the ephemeral port range on the current system. */ function findUnixPortRange() { @@ -105,7 +107,7 @@ function findUnixPortRange() { /** * Computes the ephemeral port range for a Windows system. - * @return {!webdriver.promise.Promise<{min: number, max: number}>} A promise + * @return {!webdriver.promise.Promise.<{min: number, max: number}>} A promise * that will resolve with the ephemeral port range on the current system. */ function findWindowsPortRange() { diff --git a/opera.js b/opera.js new file mode 100644 index 0000000..e2579c2 --- /dev/null +++ b/opera.js @@ -0,0 +1,499 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines a {@linkplain Driver WebDriver} client for the + * Opera web browser (v26+). Before using this module, you must download the + * latest OperaDriver + * [release](https://github.com/operasoftware/operachromiumdriver/releases) and + * ensure it can be found on your system + * [PATH](http://en.wikipedia.org/wiki/PATH_%28variable%29). + * + * There are three primary classes exported by this module: + * + * 1. {@linkplain ServiceBuilder}: configures the + * {@link selenium-webdriver/remote.DriverService remote.DriverService} + * that manages the + * [OperaDriver](https://github.com/operasoftware/operachromiumdriver) + * child process. + * + * 2. {@linkplain Options}: defines configuration options for each new Opera + * session, such as which {@linkplain Options#setProxy proxy} to use, + * what {@linkplain Options#addExtensions extensions} to install, or + * what {@linkplain Options#addArguments command-line switches} to use when + * starting the browser. + * + * 3. {@linkplain Driver}: the WebDriver client; each new instance will control + * a unique browser session with a clean user profile (unless otherwise + * configured through the {@link Options} class). + * + * By default, every Opera session will use a single driver service, which is + * started the first time a {@link Driver} instance is created and terminated + * when this process exits. The default service will inherit its environment + * from the current process and direct all output to /dev/null. You may obtain + * a handle to this default service using + * {@link #getDefaultService getDefaultService()} and change its configuration + * with {@link #setDefaultService setDefaultService()}. + * + * You may also create a {@link Driver} with its own driver service. This is + * useful if you need to capture the server's log output for a specific session: + * + * var opera = require('selenium-webdriver/opera'); + * + * var service = new opera.ServiceBuilder() + * .loggingTo('/my/log/file.txt') + * .enableVerboseLogging() + * .build(); + * + * var options = new opera.Options(); + * // configure browser options ... + * + * var driver = new opera.Driver(options, service); + * + * Users should only instantiate the {@link Driver} class directly when they + * need a custom driver service configuration (as shown above). For normal + * operation, users should start Opera using the + * {@link selenium-webdriver.Builder}. + */ + +'use strict'; + +var fs = require('fs'), + util = require('util'); + +var webdriver = require('./index'), + executors = require('./executors'), + io = require('./io'), + portprober = require('./net/portprober'), + remote = require('./remote'); + + +/** + * Name of the OperaDriver executable. + * @type {string} + * @const + */ +var OPERADRIVER_EXE = + process.platform === 'win32' ? 'operadriver.exe' : 'operadriver'; + + +/** + * Creates {@link remote.DriverService} instances that manages an + * [OperaDriver](https://github.com/operasoftware/operachromiumdriver) + * server in a child process. + * + * @param {string=} opt_exe Path to the server executable to use. If omitted, + * the builder will attempt to locate the operadriver on the current + * PATH. + * @throws {Error} If provided executable does not exist, or the operadriver + * cannot be found on the PATH. + * @constructor + */ +var ServiceBuilder = function(opt_exe) { + /** @private {string} */ + this.exe_ = opt_exe || io.findInPath(OPERADRIVER_EXE, true); + if (!this.exe_) { + throw Error( + 'The OperaDriver could not be found on the current PATH. Please ' + + 'download the latest version of the OperaDriver from ' + + 'https://github.com/operasoftware/operachromiumdriver/releases and ' + + 'ensure it can be found on your PATH.'); + } + + if (!fs.existsSync(this.exe_)) { + throw Error('File does not exist: ' + this.exe_); + } + + /** @private {!Array.} */ + this.args_ = []; + this.stdio_ = 'ignore'; +}; + + +/** @private {number} */ +ServiceBuilder.prototype.port_ = 0; + + +/** @private {(string|!Array.)} */ +ServiceBuilder.prototype.stdio_ = 'ignore'; + + +/** @private {Object.} */ +ServiceBuilder.prototype.env_ = null; + + +/** + * Sets the port to start the OperaDriver on. + * @param {number} port The port to use, or 0 for any free port. + * @return {!ServiceBuilder} A self reference. + * @throws {Error} If the port is invalid. + */ +ServiceBuilder.prototype.usingPort = function(port) { + if (port < 0) { + throw Error('port must be >= 0: ' + port); + } + this.port_ = port; + return this; +}; + + +/** + * Sets the path of the log file the driver should log to. If a log file is + * not specified, the driver will log to stderr. + * @param {string} path Path of the log file to use. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.loggingTo = function(path) { + this.args_.push('--log-path=' + path); + return this; +}; + + +/** + * Enables verbose logging. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.enableVerboseLogging = function() { + this.args_.push('--verbose'); + return this; +}; + + +/** + * Silence sthe drivers output. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.silent = function() { + this.args_.push('--silent'); + return this; +}; + + +/** + * Defines the stdio configuration for the driver service. See + * {@code child_process.spawn} for more information. + * @param {(string|!Array.)} config The + * configuration to use. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.setStdio = function(config) { + this.stdio_ = config; + return this; +}; + + +/** + * Defines the environment to start the server under. This settings will be + * inherited by every browser session started by the server. + * @param {!Object.} env The environment to use. + * @return {!ServiceBuilder} A self reference. + */ +ServiceBuilder.prototype.withEnvironment = function(env) { + this.env_ = env; + return this; +}; + + +/** + * Creates a new DriverService using this instance's current configuration. + * @return {remote.DriverService} A new driver service using this instance's + * current configuration. + * @throws {Error} If the driver exectuable was not specified and a default + * could not be found on the current PATH. + */ +ServiceBuilder.prototype.build = function() { + var port = this.port_ || portprober.findFreePort(); + var args = this.args_.concat(); // Defensive copy. + + return new remote.DriverService(this.exe_, { + loopback: true, + port: port, + args: webdriver.promise.when(port, function(port) { + return args.concat('--port=' + port); + }), + env: this.env_, + stdio: this.stdio_ + }); +}; + + +/** @type {remote.DriverService} */ +var defaultService = null; + + +/** + * Sets the default service to use for new OperaDriver instances. + * @param {!remote.DriverService} service The service to use. + * @throws {Error} If the default service is currently running. + */ +function setDefaultService(service) { + if (defaultService && defaultService.isRunning()) { + throw Error( + 'The previously configured OperaDriver service is still running. ' + + 'You must shut it down before you may adjust its configuration.'); + } + defaultService = service; +} + + +/** + * Returns the default OperaDriver service. If such a service has not been + * configured, one will be constructed using the default configuration for + * a OperaDriver executable found on the system PATH. + * @return {!remote.DriverService} The default OperaDriver service. + */ +function getDefaultService() { + if (!defaultService) { + defaultService = new ServiceBuilder().build(); + } + return defaultService; +} + + +/** + * @type {string} + * @const + */ +var OPTIONS_CAPABILITY_KEY = 'chromeOptions'; + + +/** + * Class for managing {@link Driver OperaDriver} specific options. + * @constructor + * @extends {webdriver.Serializable} + */ +var Options = function() { + webdriver.Serializable.call(this); + + /** @private {!Array.} */ + this.args_ = []; + + /** @private {!Array.<(string|!Buffer)>} */ + this.extensions_ = []; +}; +util.inherits(Options, webdriver.Serializable); + + +/** + * Extracts the OperaDriver specific options from the given capabilities + * object. + * @param {!webdriver.Capabilities} capabilities The capabilities object. + * @return {!Options} The OperaDriver options. + */ +Options.fromCapabilities = function(capabilities) { + var options; + var o = capabilities.get(OPTIONS_CAPABILITY_KEY); + if (o instanceof Options) { + options = o; + } else if (o) { + options. + addArguments(o.args || []). + addExtensions(o.extensions || []). + setOperaBinaryPath(o.binary); + } else { + options = new Options; + } + + if (capabilities.has(webdriver.Capability.PROXY)) { + options.setProxy(capabilities.get(webdriver.Capability.PROXY)); + } + + if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) { + options.setLoggingPrefs( + capabilities.get(webdriver.Capability.LOGGING_PREFS)); + } + + return options; +}; + + +/** + * Add additional command line arguments to use when launching the Opera + * browser. Each argument may be specified with or without the "--" prefix + * (e.g. "--foo" and "foo"). Arguments with an associated value should be + * delimited by an "=": "foo=bar". + * @param {...(string|!Array.)} var_args The arguments to add. + * @return {!Options} A self reference. + */ +Options.prototype.addArguments = function(var_args) { + this.args_ = this.args_.concat.apply(this.args_, arguments); + return this; +}; + + +/** + * Add additional extensions to install when launching Opera. Each extension + * should be specified as the path to the packed CRX file, or a Buffer for an + * extension. + * @param {...(string|!Buffer|!Array.<(string|!Buffer)>)} var_args The + * extensions to add. + * @return {!Options} A self reference. + */ +Options.prototype.addExtensions = function(var_args) { + this.extensions_ = this.extensions_.concat.apply( + this.extensions_, arguments); + return this; +}; + + +/** + * Sets the path to the Opera binary to use. On Mac OS X, this path should + * reference the actual Opera executable, not just the application binary. The + * binary path be absolute or relative to the operadriver server executable, but + * it must exist on the machine that will launch Opera. + * + * @param {string} path The path to the Opera binary to use. + * @return {!Options} A self reference. + */ +Options.prototype.setOperaBinaryPath = function(path) { + this.binary_ = path; + return this; +}; + + +/** + * Sets the logging preferences for the new session. + * @param {!webdriver.logging.Preferences} prefs The logging preferences. + * @return {!Options} A self reference. + */ +Options.prototype.setLoggingPrefs = function(prefs) { + this.logPrefs_ = prefs; + return this; +}; + + +/** + * Sets the proxy settings for the new session. + * @param {webdriver.ProxyConfig} proxy The proxy configuration to use. + * @return {!Options} A self reference. + */ +Options.prototype.setProxy = function(proxy) { + this.proxy_ = proxy; + return this; +}; + + +/** + * Converts this options instance to a {@link webdriver.Capabilities} object. + * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge + * these options into, if any. + * @return {!webdriver.Capabilities} The capabilities. + */ +Options.prototype.toCapabilities = function(opt_capabilities) { + var capabilities = opt_capabilities || webdriver.Capabilities.opera(); + capabilities. + set(webdriver.Capability.PROXY, this.proxy_). + set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_). + set(OPTIONS_CAPABILITY_KEY, this); + return capabilities; +}; + + +/** + * Converts this instance to its JSON wire protocol representation. Note this + * function is an implementation not intended for general use. + * @return {{args: !Array., + * binary: (string|undefined), + * detach: boolean, + * extensions: !Array.<(string|!webdriver.promise.Promise.))>, + * localState: (Object|undefined), + * logPath: (string|undefined), + * prefs: (Object|undefined)}} The JSON wire protocol representation + * of this instance. + * @override + */ +Options.prototype.serialize = function() { + var json = { + args: this.args_, + extensions: this.extensions_.map(function(extension) { + if (Buffer.isBuffer(extension)) { + return extension.toString('base64'); + } + return webdriver.promise.checkedNodeCall( + fs.readFile, extension, 'base64'); + }) + }; + if (this.binary_) { + json.binary = this.binary_; + } + if (this.logFile_) { + json.logPath = this.logFile_; + } + if (this.prefs_) { + json.prefs = this.prefs_; + } + + return json; +}; + + +/** + * Creates a new WebDriver client for Opera. + * + * @param {(webdriver.Capabilities|Options)=} opt_config The configuration + * options. + * @param {remote.DriverService=} opt_service The session to use; will use + * the {@link getDefaultService default service} by default. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or + * {@code null} to use the currently active flow. + * @constructor + * @extends {webdriver.WebDriver} + */ +var Driver = function(opt_config, opt_service, opt_flow) { + var service = opt_service || getDefaultService(); + var executor = executors.createExecutor(service.start()); + + var capabilities = + opt_config instanceof Options ? opt_config.toCapabilities() : + (opt_config || webdriver.Capabilities.opera()); + + // On Linux, the OperaDriver does not look for Opera on the PATH, so we + // must explicitly find it. See: operachromiumdriver #9. + if (process.platform === 'linux') { + var options = Options.fromCapabilities(capabilities); + if (!options.binary_) { + options.setOperaBinaryPath(io.findInPath('opera', true)); + } + capabilities = options.toCapabilities(capabilities); + } + + var driver = webdriver.WebDriver.createSession( + executor, capabilities, opt_flow); + + webdriver.WebDriver.call( + this, driver.getSession(), executor, driver.controlFlow()); +}; +util.inherits(Driver, webdriver.WebDriver); + + +/** + * This function is a no-op as file detectors are not supported by this + * implementation. + * @override + */ +Driver.prototype.setFileDetector = function() { +}; + + +// PUBLIC API + + +exports.Driver = Driver; +exports.Options = Options; +exports.ServiceBuilder = ServiceBuilder; +exports.getDefaultService = getDefaultService; +exports.setDefaultService = setDefaultService; diff --git a/package.json b/package.json index f2e2a65..27c4b61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "browserstack-webdriver", - "version": "2.41.1", - "description": "BrowserStack WebDriver JavaScript bindings with keep alive support", + "version": "2.47.0", + "description": "Browserstack Webdriver Javascript bindings with keep alive support", + "license": "Apache-2.0", "keywords": [ "automation", "selenium", @@ -16,26 +17,26 @@ "main": "./index", "repository": { "type": "git", - "url": "https://github.com/browserstack/selenium-webdriver-nodejs" + "url": "https://https://github.com/browserstack/selenium-webdriver-nodejs.git" }, "engines": { - "node": ">= 0.8.x" + "node": ">= 0.12.x" + }, + "dependencies": { + "adm-zip": "0.4.4", + "rimraf": "^2.2.8", + "tmp": "0.0.24", + "ws": "^0.8.0", + "xml2js": "0.4.4" }, "devDependencies": { - "mocha": "~1.10.0" + "express": "^4.11.2", + "mocha": ">= 1.21.x", + "multer": "^0.1.7", + "promises-aplus-tests": "^2.1.0", + "serve-index": "^1.6.1" }, "scripts": { - "test": "node_modules/mocha/bin/mocha -R list --recursive test" - }, - "readme": "# browserstack-webdriver\n\n## Installation\n\nInstall the latest published version using `npm`:\n\n npm install browserstack-webdriver\n\nIn addition to the npm package, you will to download the WebDriver\nimplementations you wish to utilize. As of 2.34.0, `browserstack-webdriver`\nnatively supports the [ChromeDriver](http://chromedriver.storage.googleapis.com/index.html).\nSimply download a copy and make sure it can be found on your `PATH`. The other\ndrivers (e.g. Firefox, Internet Explorer, and Safari), still require the\n[standalone Selenium server](http://selenium-release.storage.googleapis.com/index.html).\n\n### Running the tests\n\nTo run the tests, you will need to download a copy of the\n[ChromeDriver](http://chromedriver.storage.googleapis.com/index.html) and make\nsure it can be found on your `PATH`.\n\n npm test browserstack-webdriver\n\nTo run the tests against multiple browsers, download the\n[Selenium server](http://selenium-release.storage.googleapis.com/index.html) and\nspecify its location through the `SELENIUM_SERVER_JAR` environment variable.\nYou can use the `SELENIUM_BROWSER` environment variable to define a\ncomma-separated list of browsers you wish to test against. For example:\n\n export SELENIUM_SERVER_JAR=path/to/selenium-server-standalone-2.33.0.jar\n SELENIUM_BROWSER=chrome,firefox npm test browserstack-webdriver\n\n## Usage\n\n\n var webdriver = require('browserstack-webdriver');\n\n var driver = new webdriver.Builder().\n withCapabilities(webdriver.Capabilities.chrome()).\n build();\n\n driver.get('http://www.google.com');\n driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');\n driver.findElement(webdriver.By.name('btnG')).click();\n driver.wait(function() {\n return driver.getTitle().then(function(title) {\n return title === 'webdriver - Google Search';\n });\n }, 1000);\n\n driver.quit();\n\n## Documentation\n\nFull documentation is available on the [Selenium project wiki](http://code.google.com/p/selenium/wiki/WebDriverJs \"User guide\").\n\n## Issues\n\nPlease report any issues using the [Selenium issue tracker](https://code.google.com/p/selenium/issues/list).\n\n## License\n\nCopyright 2009-2014 Software Freedom Conservancy\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n", - "readmeFilename": "README.md", - "_id": "browserstack-webdriver@2.41.0", - "dist": { - "shasum": "d59dd6297c13b821711bdd8775afa7dffef78ef4" - }, - "_from": "browserstack-webdriver@", - "_resolved": "https://registry.npmjs.org/browserstack-webdriver/-/browserstack-webdriver-2.41.0.tgz", - "dependencies" : { - "keep-alive-agent": "*" + "test": "node_modules/.bin/mocha --harmony -t 600000 --recursive test" } } diff --git a/phantomjs.js b/phantomjs.js index bee3572..b0cf1a4 100644 --- a/phantomjs.js +++ b/phantomjs.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -19,8 +21,8 @@ var fs = require('fs'), util = require('util'); var webdriver = require('./index'), - LogLevel = webdriver.logging.LevelName, executors = require('./executors'), + http = require('./http'), io = require('./io'), portprober = require('./net/portprober'), remote = require('./remote'); @@ -59,6 +61,15 @@ var CLI_ARGS_CAPABILITY = 'phantomjs.cli.args'; var DEFAULT_LOG_FILE = 'phantomjsdriver.log'; +/** + * Custom command names supported by PhantomJS. + * @enum {string} + */ +var Command = { + EXECUTE_PHANTOM_SCRIPT: 'executePhantomScript' +}; + + /** * Finds the PhantomJS executable. * @param {string=} opt_exe Path to the executable to use. @@ -85,30 +96,57 @@ function findExecutable(opt_exe) { /** * Maps WebDriver logging level name to those recognised by PhantomJS. - * @type {!Object.} + * @type {!Object.} * @const */ var WEBDRIVER_TO_PHANTOMJS_LEVEL = (function() { var map = {}; - map[LogLevel.ALL] = map[LogLevel.DEBUG] = 'DEBUG'; - map[LogLevel.INFO] = 'INFO'; - map[LogLevel.WARNING] = 'WARN'; - map[LogLevel.SEVERE] = map[LogLevel.OFF] = 'ERROR'; + map[webdriver.logging.Level.ALL.name] = 'DEBUG'; + map[webdriver.logging.Level.DEBUG.name] = 'DEBUG'; + map[webdriver.logging.Level.INFO.name] = 'INFO'; + map[webdriver.logging.Level.WARNING.name] = 'WARN'; + map[webdriver.logging.Level.SEVERE.name] = 'ERROR'; return map; })(); /** - * Creates a new PhantomJS WebDriver client. + * Creates a command executor with support for PhantomJS' custom commands. + * @param {!webdriver.promise.Promise} url The server's URL. + * @return {!webdriver.CommandExecutor} The new command executor. + */ +function createExecutor(url) { + return new executors.DeferredExecutor(url.then(function(url) { + var client = new http.HttpClient(url); + var executor = new http.Executor(client); + + executor.defineCommand( + Command.EXECUTE_PHANTOM_SCRIPT, + 'POST', '/session/:sessionId/phantom/execute'); + + return executor; + })); +} + +/** + * Creates a new WebDriver client for PhantomJS. + * * @param {webdriver.Capabilities=} opt_capabilities The desired capabilities. - * @return {!webdriver.WebDriver} A new WebDriver instance. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to use, or + * {@code null} to use the currently active flow. + * @constructor + * @extends {webdriver.WebDriver} */ -function createDriver(opt_capabilities) { +var Driver = function(opt_capabilities, opt_flow) { var capabilities = opt_capabilities || webdriver.Capabilities.phantomjs(); var exe = findExecutable(capabilities.get(BINARY_PATH_CAPABILITY)); var args = ['--webdriver-logfile=' + DEFAULT_LOG_FILE]; var logPrefs = capabilities.get(webdriver.Capability.LOGGING_PREFS); + if (logPrefs instanceof webdriver.logging.Preferences) { + logPrefs = logPrefs.toJSON(); + } + if (logPrefs && logPrefs[webdriver.logging.Type.DRIVER]) { var level = WEBDRIVER_TO_PHANTOMJS_LEVEL[ logPrefs[webdriver.logging.Type.DRIVER]]; @@ -148,17 +186,80 @@ function createDriver(opt_capabilities) { }) }); - var executor = executors.createExecutor(service.start()); - var driver = webdriver.WebDriver.createSession(executor, capabilities); - var boundQuit = driver.quit.bind(driver); - driver.quit = function() { + var executor = createExecutor(service.start()); + var driver = webdriver.WebDriver.createSession( + executor, capabilities, opt_flow); + + webdriver.WebDriver.call( + this, driver.getSession(), executor, driver.controlFlow()); + + var boundQuit = this.quit.bind(this); + + /** @override */ + this.quit = function() { return boundQuit().thenFinally(service.kill.bind(service)); }; - return driver; -} +}; +util.inherits(Driver, webdriver.WebDriver); -// PUBLIC API +/** + * This function is a no-op as file detectors are not supported by this + * implementation. + * @override + */ +Driver.prototype.setFileDetector = function() { +}; + +/** + * Executes a PhantomJS fragment. This method is similar to + * {@link #executeScript}, except it exposes the + * PhantomJS API to the injected + * script. + * + *

        The injected script will execute in the context of PhantomJS's + * {@code page} variable. If a page has not been loaded before calling this + * method, one will be created.

        + * + *

        Be sure to wrap callback definitions in a try/catch block, as failures + * may cause future WebDriver calls to fail.

        + * + *

        Certain callbacks are used by GhostDriver (the PhantomJS WebDriver + * implementation) and overriding these may cause the script to fail. It is + * recommended that you check for existing callbacks before defining your own. + *

        + * + * As with {@link #executeScript}, the injected script may be defined as + * a string for an anonymous function body (e.g. "return 123;"), or as a + * function. If a function is provided, it will be decompiled to its original + * source. Note that injecting functions is provided as a convenience to + * simplify defining complex scripts. Care must be taken that the function + * only references variables that will be defined in the page's scope and + * that the function does not override {@code Function.prototype.toString} + * (overriding toString() will interfere with how the function is + * decompiled. + * + * @param {(string|!Function)} script The script to execute. + * @param {...*} var_args The arguments to pass to the script. + * @return {!webdriver.promise.Promise} A promise that resolve to the + * script's return value. + * @template T + */ +Driver.prototype.executePhantomJS = function(script, args) { + if (typeof script === 'function') { + script = 'return (' + script + ').apply(this, arguments);'; + } + var args = arguments.length > 1 + ? Array.prototype.slice.call(arguments, 1) : []; + return this.schedule( + new webdriver.Command(Command.EXECUTE_PHANTOM_SCRIPT) + .setParameter('script', script) + .setParameter('args', args), + 'Driver.executePhantomJS()'); +}; + + +// PUBLIC API -exports.createDriver = createDriver; +exports.Driver = Driver; diff --git a/proxy.js b/proxy.js index 0341346..ba478f2 100644 --- a/proxy.js +++ b/proxy.js @@ -1,28 +1,30 @@ -// Copyright 2013 Selenium committers +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Defines functions for configuring a webdriver proxy: - *
        
        - * var webdriver = require('selenium-webdriver'),
        - *     proxy = require('selenium-webdriver/proxy');
          *
        - * var driver = new webdriver.Builder()
        - *     .withCapabilities(webdriver.Capabilities.chrome())
        - *     .setProxy(proxy.manual({http: 'host:1234'}))
        - *     .build();
        - * 
        + * var webdriver = require('selenium-webdriver'), + * proxy = require('selenium-webdriver/proxy'); + * + * var driver = new webdriver.Builder() + * .withCapabilities(webdriver.Capabilities.chrome()) + * .setProxy(proxy.manual({http: 'host:1234'})) + * .build(); */ 'use strict'; @@ -30,28 +32,13 @@ var util = require('util'); -/** - * Proxy configuration object, as defined by the WebDriver wire protocol. - * @typedef {( - * {proxyType: string}| - * {proxyType: string, - * proxyAutoconfigUrl: string}| - * {proxyType: string, - * ftpProxy: string, - * httpProxy: string, - * sslProxy: string, - * noProxy: string})} - */ -var ProxyConfig; - - // PUBLIC API /** * Configures WebDriver to bypass all browser proxies. - * @return {!ProxyConfig} A new proxy configuration object. + * @return {!webdriver.ProxyConfig} A new proxy configuration object. */ exports.direct = function() { return {proxyType: 'direct'}; @@ -61,14 +48,13 @@ exports.direct = function() { /** * Manually configures the browser proxy. The following options are * supported: - *
          - *
        • {@code ftp}: Proxy host to use for FTP requests - *
        • {@code http}: Proxy host to use for HTTP requests - *
        • {@code https}: Proxy host to use for HTTPS requests - *
        • {@code bypass}: A list of hosts requests should directly connect to, + * + * - `ftp`: Proxy host to use for FTP requests + * - `http`: Proxy host to use for HTTP requests + * - `https`: Proxy host to use for HTTPS requests + * - `bypass`: A list of hosts requests should directly connect to, * bypassing any other proxies for that request. May be specified as a * comma separated string, or a list of strings. - *
        * * Behavior is undefined for FTP, HTTP, and HTTPS requests if the * corresponding key is omitted from the configuration options. @@ -78,7 +64,7 @@ exports.direct = function() { * https: (string|undefined), * bypass: (string|!Array.|undefined)}} options Proxy * configuration options. - * @return {!ProxyConfig} A new proxy configuration object. + * @return {!webdriver.ProxyConfig} A new proxy configuration object. */ exports.manual = function(options) { return { @@ -96,7 +82,7 @@ exports.manual = function(options) { * Configures WebDriver to configure the browser proxy using the PAC file at * the given URL. * @param {string} url URL for the PAC proxy to use. - * @return {!ProxyConfig} A new proxy configuration object. + * @return {!webdriver.ProxyConfig} A new proxy configuration object. */ exports.pac = function(url) { return { @@ -108,7 +94,7 @@ exports.pac = function(url) { /** * Configures WebDriver to use the current system's proxy. - * @return {!ProxyConfig} A new proxy configuration object. + * @return {!webdriver.ProxyConfig} A new proxy configuration object. */ exports.system = function() { return {proxyType: 'system'}; diff --git a/remote/index.js b/remote/index.js index 8c45f8e..230392a 100644 --- a/remote/index.js +++ b/remote/index.js @@ -1,27 +1,33 @@ -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; -var spawn = require('child_process').spawn, - os = require('os'), +var AdmZip = require('adm-zip'), + fs = require('fs'), path = require('path'), url = require('url'), util = require('util'); -var promise = require('../').promise, +var _base = require('../_base'), + webdriver = require('../'), + promise = require('../').promise, httpUtil = require('../http/util'), + exec = require('../io/exec'), net = require('../net'), portprober = require('../net/portprober'); @@ -29,21 +35,20 @@ var promise = require('../').promise, /** * Configuration options for a DriverService instance. - *
          - *
        • {@code port} - The port to start the server on (must be > 0). If the - * port is provided as a promise, the service will wait for the promise to - * resolve before starting. - *
        • {@code args} - The arguments to pass to the service. If a promise is - * provided, the service will wait for it to resolve before starting. - *
        • {@code path} - The base path on the server for the WebDriver wire - * protocol (e.g. '/wd/hub'). Defaults to '/'. - *
        • {@code env} - The environment variables that should be visible to the - * server process. Defaults to inheriting the current process's - * environment. - *
        • {@code stdio} - IO configuration for the spawned server process. For - * more information, refer to the documentation of - * {@code child_process.spawn}. - *
        + * + * - `loopback` - Whether the service should only be accessed on this host's + * loopback address. + * - `port` - The port to start the server on (must be > 0). If the port is + * provided as a promise, the service will wait for the promise to resolve + * before starting. + * - `args` - The arguments to pass to the service. If a promise is provided, + * the service will wait for it to resolve before starting. + * - `path` - The base path on the server for the WebDriver wire protocol + * (e.g. '/wd/hub'). Defaults to '/'. + * - `env` - The environment variables that should be visible to the server + * process. Defaults to inheriting the current process's environment. + * - `stdio` - IO configuration for the spawned server process. For more + * information, refer to the documentation of `child_process.spawn`. * * @typedef {{ * port: (number|!webdriver.promise.Promise.), @@ -59,10 +64,10 @@ var ServiceOptions; /** * Manages the life and death of a native executable WebDriver server. * - *

        It is expected that the driver server implements the - * WebDriver - * Wire Protocol. Furthermore, the managed server should support multiple - * concurrent sessions, so that this class may be reused for multiple clients. + * It is expected that the driver server implements the + * https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol. + * Furthermore, the managed server should support multiple concurrent sessions, + * so that this class may be reused for multiple clients. * * @param {string} executable Path to the executable to run. * @param {!ServiceOptions} options Configuration options for the service. @@ -73,6 +78,9 @@ function DriverService(executable, options) { /** @private {string} */ this.executable_ = executable; + /** @private {boolean} */ + this.loopbackOnly_ = !!options.loopback; + /** @private {(number|!webdriver.promise.Promise.)} */ this.port_ = options.port; @@ -89,6 +97,21 @@ function DriverService(executable, options) { /** @private {(string|!Array.)} */ this.stdio_ = options.stdio || 'ignore'; + + /** + * A promise for the managed subprocess, or null if the server has not been + * started yet. This promise will never be rejected. + * @private {promise.Promise.} + */ + this.command_ = null; + + /** + * Promise that resolves to the server's address or null if the server has + * not been started. This promise will be rejected if the server terminates + * before it starts accepting WebDriver requests. + * @private {promise.Promise.} + */ + this.address_ = null; } @@ -100,26 +123,6 @@ function DriverService(executable, options) { DriverService.DEFAULT_START_TIMEOUT_MS = 30 * 1000; -/** @private {child_process.ChildProcess} */ -DriverService.prototype.process_ = null; - - -/** - * Promise that resolves to the server's address or null if the server has not - * been started. - * @private {webdriver.promise.Promise.} - */ -DriverService.prototype.address_ = null; - - -/** - * Promise that tracks the status of shutting down the server, or null if the - * server is not currently shutting down. - * @private {webdriver.promise.Promise} - */ -DriverService.prototype.shutdownHook_ = null; - - /** * @return {!webdriver.promise.Promise.} A promise that resolves to * the server's address. @@ -134,6 +137,8 @@ DriverService.prototype.address = function() { /** + * Returns whether the underlying process is still running. This does not take + * into account whether the process is in the process of shutting down. * @return {boolean} Whether the underlying service process is running. */ DriverService.prototype.isRunning = function() { @@ -145,7 +150,7 @@ DriverService.prototype.isRunning = function() { * Starts the server if it is not already running. * @param {number=} opt_timeoutMs How long to wait, in milliseconds, for the * server to start accepting requests. Defaults to 30 seconds. - * @return {!webdriver.promise.Promise.} A promise that will resolve + * @return {!promise.Promise.} A promise that will resolve * to the server's base URL when it has started accepting requests. If the * timeout expires before the server has started, the promise will be * rejected. @@ -158,56 +163,53 @@ DriverService.prototype.start = function(opt_timeoutMs) { var timeout = opt_timeoutMs || DriverService.DEFAULT_START_TIMEOUT_MS; var self = this; + this.command_ = promise.defer(); this.address_ = promise.defer(); this.address_.fulfill(promise.when(this.port_, function(port) { if (port <= 0) { throw Error('Port must be > 0: ' + port); } return promise.when(self.args_, function(args) { - self.process_ = spawn(self.executable_, args, { + var command = exec(self.executable_, { + args: args, env: self.env_, stdio: self.stdio_ - }).once('exit', onServerExit); + }); + + self.command_.fulfill(command); - // This process should not wait on the spawned child, however, we do - // want to ensure the child is killed when this process exits. - self.process_.unref(); - process.once('exit', killServer); + var earlyTermination = command.result().then(function(result) { + var error = result.code == null ? + Error('Server was killed with ' + result.signal) : + Error('Server terminated early with status ' + result.code); + self.address_.reject(error); + self.address_ = null; + self.command_ = null; + throw error; + }); var serverUrl = url.format({ protocol: 'http', - hostname: net.getAddress() || net.getLoopbackAddress(), + hostname: !self.loopbackOnly_ && net.getAddress() || + net.getLoopbackAddress(), port: port, pathname: self.path_ }); - return httpUtil.waitForServer(serverUrl, timeout).then(function() { + return new promise.Promise(function(fulfill, reject) { + var ready = httpUtil.waitForServer(serverUrl, timeout) + .then(fulfill, reject); + earlyTermination.thenCatch(function(e) { + ready.cancel(e); + reject(Error(e.message)); + }); + }).then(function() { return serverUrl; }); }); })); return this.address_; - - function onServerExit(code, signal) { - self.address_.reject(code == null ? - Error('Server was killed with ' + signal) : - Error('Server exited with ' + code)); - - if (self.shutdownHook_) { - self.shutdownHook_.fulfill(); - } - - self.shutdownHook_ = null; - self.address_ = null; - self.process_ = null; - process.removeListener('exit', killServer); - } - - function killServer() { - process.removeListener('exit', killServer); - self.process_ && self.process_.kill('SIGTERM'); - } }; @@ -219,26 +221,12 @@ DriverService.prototype.start = function(opt_timeoutMs) { * the server has been stopped. */ DriverService.prototype.kill = function() { - if (!this.address_) { + if (!this.address_ || !this.command_) { return promise.fulfilled(); // Not currently running. } - - if (!this.shutdownHook_) { - // No process: still starting; wait on address. - // Otherwise, kill the process now. Exit handler will resolve the - // shutdown hook. - if (this.process_) { - this.shutdownHook_ = promise.defer(); - this.process_.kill('SIGTERM'); - } else { - var self = this; - this.shutdownHook_ = this.address_.thenFinally(function() { - self.process_ && self.process_.kill('SIGTERM'); - }); - } - } - - return this.shutdownHook_; + return this.command_.then(function(command) { + command.kill('SIGTERM'); + }); }; @@ -255,18 +243,28 @@ DriverService.prototype.stop = function() { /** - * Manages the life and death of the Selenium standalone server. The server - * may be obtained from http://selenium-release.storage.googleapis.com/index.html. + * Manages the life and death of the + * + * standalone Selenium server. + * * @param {string} jar Path to the Selenium server jar. - * @param {!SeleniumServer.Options} options Configuration options for the + * @param {SeleniumServer.Options=} opt_options Configuration options for the * server. - * @throws {Error} If an invalid port is specified. + * @throws {Error} If the path to the Selenium jar is not specified or if an + * invalid port is specified. * @constructor * @extends {DriverService} */ -function SeleniumServer(jar, options) { - if (options.port < 0) +function SeleniumServer(jar, opt_options) { + if (!jar) { + throw Error('Path to the Selenium jar not specified'); + } + + var options = opt_options || {}; + + if (options.port < 0) { throw Error('Port must be >= 0: ' + options.port); + } var port = options.port || portprober.findFreePort(); var args = promise.when(options.jvmArgs || [], function(jvmArgs) { @@ -290,21 +288,18 @@ util.inherits(SeleniumServer, DriverService); /** * Options for the Selenium server: - *

          - *
        • {@code port} - The port to start the server on (must be > 0). If the - * port is provided as a promise, the service will wait for the promise to - * resolve before starting. - *
        • {@code args} - The arguments to pass to the service. If a promise is - * provided, the service will wait for it to resolve before starting. - *
        • {@code jvmArgs} - The arguments to pass to the JVM. If a promise is - * provided, the service will wait for it to resolve before starting. - *
        • {@code env} - The environment variables that should be visible to the - * server process. Defaults to inheriting the current process's - * environment. - *
        • {@code stdio} - IO configuration for the spawned server process. For - * more information, refer to the documentation of - * {@code child_process.spawn}. - *
        + * + * - `port` - The port to start the server on (must be > 0). If the port is + * provided as a promise, the service will wait for the promise to resolve + * before starting. + * - `args` - The arguments to pass to the service. If a promise is provided, + * the service will wait for it to resolve before starting. + * - `jvmArgs` - The arguments to pass to the JVM. If a promise is provided, + * the service will wait for it to resolve before starting. + * - `env` - The environment variables that should be visible to the server + * process. Defaults to inheriting the current process's environment. + * - `stdio` - IO configuration for the spawned server process. For more + * information, refer to the documentation of `child_process.spawn`. * * @typedef {{ * port: (number|!webdriver.promise.Promise.), @@ -319,7 +314,53 @@ util.inherits(SeleniumServer, DriverService); SeleniumServer.Options; + +/** + * A {@link webdriver.FileDetector} that may be used when running + * against a remote + * [Selenium server](http://selenium-release.storage.googleapis.com/index.html). + * + * When a file path on the local machine running this script is entered with + * {@link webdriver.WebElement#sendKeys WebElement#sendKeys}, this file detector + * will transfer the specified file to the Selenium server's host; the sendKeys + * command will be updated to use the transfered file's path. + * + * __Note:__ This class depends on a non-standard command supported on the + * Java Selenium server. The file detector will fail if used with a server that + * only supports standard WebDriver commands (such as the ChromeDriver). + * + * @constructor + * @extends {webdriver.FileDetector} + * @final + */ +var FileDetector = function() {}; +util.inherits(webdriver.FileDetector, FileDetector); + + +/** @override */ +FileDetector.prototype.handleFile = function(driver, filePath) { + return promise.checkedNodeCall(fs.stat, filePath).then(function(stats) { + if (stats.isDirectory()) { + throw TypeError('Uploading directories is not supported: ' + filePath); + } + + var zip = new AdmZip(); + zip.addLocalFile(filePath); + + var command = new webdriver.Command(webdriver.CommandName.UPLOAD_FILE) + .setParameter('file', zip.toBuffer().toString('base64')); + return driver.schedule(command, + 'remote.FileDetector.handleFile(' + filePath + ')'); + }, function(err) { + if (err.code === 'ENOENT') { + return filePath; // Not a file; return original input. + } + throw err; + }); +}; + // PUBLIC API exports.DriverService = DriverService; +exports.FileDetector = FileDetector; exports.SeleniumServer = SeleniumServer; diff --git a/safari.js b/safari.js new file mode 100644 index 0000000..d6780df --- /dev/null +++ b/safari.js @@ -0,0 +1,538 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +/** + * @fileoverview Defines a WebDriver client for Safari. Before using this + * module, you must install the + * [latest version](http://selenium-release.storage.googleapis.com/index.html) + * of the SafariDriver browser extension; using Safari for normal browsing is + * not recommended once the extension has been installed. You can, and should, + * disable the extension when the browser is not being used with WebDriver. + */ + +'use strict'; + +var events = require('events'); +var fs = require('fs'); +var http = require('http'); +var path = require('path'); +var url = require('url'); +var util = require('util'); +var ws = require('ws'); + +var webdriver = require('./'); +var promise = webdriver.promise; +var _base = require('./_base'); +var io = require('./io'); +var exec = require('./io/exec'); +var portprober = require('./net/portprober'); + + +/** @const */ +var CLIENT_PATH = _base.isDevMode() + ? path.join(__dirname, + '../../../build/javascript/safari-driver/client.js') + : path.join(__dirname, 'lib/safari/client.js'); + + +/** @const */ +var LIBRARY_DIR = process.platform === 'darwin' + ? path.join('/Users', process.env['USER'], 'Library/Safari') + : path.join(process.env['APPDATA'], 'Apple Computer', 'Safari'); + + +/** @const */ +var SESSION_DATA_FILES = (function() { + if (process.platform === 'darwin') { + var libraryDir = path.join('/Users', process.env['USER'], 'Library'); + return [ + path.join(libraryDir, 'Caches/com.apple.Safari/Cache.db'), + path.join(libraryDir, 'Cookies/Cookies.binarycookies'), + path.join(libraryDir, 'Cookies/Cookies.plist'), + path.join(libraryDir, 'Safari/History.plist'), + path.join(libraryDir, 'Safari/LastSession.plist'), + path.join(libraryDir, 'Safari/LocalStorage'), + path.join(libraryDir, 'Safari/Databases') + ]; + } else if (process.platform === 'win32') { + var appDataDir = path.join(process.env['APPDATA'], + 'Apple Computer', 'Safari'); + var localDataDir = path.join(process.env['LOCALAPPDATA'], + 'Apple Computer', 'Safari'); + return [ + path.join(appDataDir, 'History.plist'), + path.join(appDataDir, 'LastSession.plist'), + path.join(appDataDir, 'Cookies/Cookies.plist'), + path.join(appDataDir, 'Cookies/Cookies.binarycookies'), + path.join(localDataDir, 'Cache.db'), + path.join(localDataDir, 'Databases'), + path.join(localDataDir, 'LocalStorage') + ]; + } else { + return []; + } +})(); + + +/** @typedef {{port: number, address: string, family: string}} */ +var Host; + + +/** + * A basic HTTP/WebSocket server used to communicate with the SafariDriver + * browser extension. + * @constructor + * @extends {events.EventEmitter} + */ +var Server = function() { + events.EventEmitter.call(this); + + var server = http.createServer(function(req, res) { + if (req.url === '/favicon.ico') { + res.writeHead(204); + res.end(); + return; + } + + var query = url.parse(req.url).query || ''; + if (query.indexOf('url=') == -1) { + var address = server.address() + var host = address.address + ':' + address.port; + res.writeHead(302, {'Location': 'http://' + host + '?url=ws://' + host}); + res.end(); + } + + fs.readFile(CLIENT_PATH, 'utf8', function(err, data) { + if (err) { + res.writeHead(500, {'Content-Type': 'text/plain'}); + res.end(err.stack); + return; + } + var content = ''; + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + 'Content-Length': Buffer.byteLength(content, 'utf8'), + }); + res.end(content); + }); + }); + + var wss = new ws.Server({server: server}); + wss.on('connection', this.emit.bind(this, 'connection')); + + /** + * Starts the server on a random port. + * @return {!webdriver.promise.Promise} A promise that will resolve + * with the server host when it has fully started. + */ + this.start = function() { + if (server.address()) { + return promise.fulfilled(server.address()); + } + return portprober.findFreePort('localhost').then(function(port) { + return promise.checkedNodeCall( + server.listen.bind(server, port, 'localhost')); + }).then(function() { + return server.address(); + }); + }; + + /** + * Stops the server. + * @return {!webdriver.promise.Promise} A promise that will resolve when the + * server has closed all connections. + */ + this.stop = function() { + return new promise.Promise(function(fulfill) { + server.close(fulfill); + }); + }; + + /** + * @return {Host} This server's host info. + * @throws {Error} If the server is not running. + */ + this.address = function() { + var addr = server.address(); + if (!addr) { + throw Error('There server is not running!'); + } + return addr; + }; +}; +util.inherits(Server, events.EventEmitter); + + +/** + * @return {!promise.Promise} A promise that will resolve with the path + * to Safari on the current system. + */ +function findSafariExecutable() { + switch (process.platform) { + case 'darwin': + return promise.fulfilled( + '/Applications/Safari.app/Contents/MacOS/Safari'); + + case 'win32': + var files = [ + process.env['PROGRAMFILES'] || '\\Program Files', + process.env['PROGRAMFILES(X86)'] || '\\Program Files (x86)' + ].map(function(prefix) { + return path.join(prefix, 'Safari\\Safari.exe'); + }); + return io.exists(files[0]).then(function(exists) { + return exists ? files[0] : io.exists(files[1]).then(function(exists) { + if (exists) { + return files[1]; + } + throw Error('Unable to find Safari on the current system'); + }); + }); + + default: + return promise.rejected( + Error('Safari is not supported on the current platform: ' + + process.platform)); + } +} + + +/** + * @param {string} url The URL to connect to. + * @return {!promise.Promise} A promise for the path to a file that + * Safari can open on start-up to trigger a new connection to the WebSocket + * server. + */ +function createConnectFile(url) { + return io.tmpFile({postfix: '.html'}).then(function(f) { + var writeFile = promise.checkedNodeCall(fs.writeFile, + f, + '', + {encoding: 'utf8'}); + return writeFile.then(function() { + return f; + }); + }); +} + + +/** + * Deletes all session data files if so desired. + * @param {!Object} desiredCapabilities . + * @return {!Array} A list of promises for the deleted files. + */ +function cleanSession(desiredCapabilities) { + if (!desiredCapabilities) { + return []; + } + var options = desiredCapabilities[OPTIONS_CAPABILITY_KEY]; + if (!options) { + return []; + } + if (!options['cleanSession']) { + return []; + } + return SESSION_DATA_FILES.map(function(file) { + return io.unlink(file); + }); +} + + +/** + * @constructor + * @implements {webdriver.CommandExecutor} + */ +var CommandExecutor = function() { + /** @private {Server} */ + this.server_ = null; + + /** @private {ws.WebSocket} */ + this.socket_ = null; + + /** @private {promise.Promise.} */ + this.safari_ = null; +}; + + +/** @override */ +CommandExecutor.prototype.execute = function(command, callback) { + var safariCommand = JSON.stringify({ + 'origin': 'webdriver', + 'type': 'command', + 'command': { + 'id': _base.require('goog.string').getRandomString(), + 'name': command.getName(), + 'parameters': command.getParameters() + } + }); + var self = this; + + switch (command.getName()) { + case webdriver.CommandName.NEW_SESSION: + this.startSafari_(command).then(sendCommand, callback); + break; + + case webdriver.CommandName.QUIT: + this.destroySession_().then(function() { + callback(null, _base.require('bot.response').createResponse(null)); + }, callback); + break; + + default: + sendCommand(); + break; + } + + function sendCommand() { + new promise.Promise(function(fulfill, reject) { + // TODO: support reconnecting with the extension. + if (!self.socket_) { + self.destroySession_().thenFinally(function() { + reject(Error('The connection to the SafariDriver was closed')); + }); + return; + } + + self.socket_.send(safariCommand, function(err) { + if (err) { + reject(err); + return; + } + }); + + self.socket_.once('message', function(data) { + try { + data = JSON.parse(data); + } catch (ex) { + reject(Error('Failed to parse driver message: ' + data)); + return; + } + fulfill(data['response']); + }); + + }).then(function(value) { + callback(null, value); + }, callback); + } +}; + + +/** + * @param {!webdriver.Command} command . + * @private + */ +CommandExecutor.prototype.startSafari_ = function(command) { + this.server_ = new Server(); + + this.safari_ = this.server_.start().then(function(address) { + var tasks = cleanSession(command.getParameters()['desiredCapabilities']); + tasks.push( + findSafariExecutable(), + createConnectFile( + 'http://' + address.address + ':' + address.port)); + return promise.all(tasks).then(function(tasks) { + var exe = tasks[tasks.length - 2]; + var html = tasks[tasks.length - 1]; + return exec(exe, {args: [html]}); + }); + }); + + var connected = promise.defer(); + var self = this; + var start = Date.now(); + var timer = setTimeout(function() { + connected.reject(Error( + 'Failed to connect to the SafariDriver after ' + (Date.now() - start) + + ' ms; Have you installed the latest extension from ' + + 'http://selenium-release.storage.googleapis.com/index.html?')); + }, 10 * 1000); + this.server_.once('connection', function(socket) { + clearTimeout(timer); + self.socket_ = socket; + socket.once('close', function() { + self.socket_ = null; + }); + connected.fulfill(); + }); + return connected.promise; +}; + + +/** + * Destroys the active session by stopping the WebSocket server and killing the + * Safari subprocess. + * @private + */ +CommandExecutor.prototype.destroySession_ = function() { + var tasks = []; + if (this.server_) { + tasks.push(this.server_.stop()); + } + if (this.safari_) { + tasks.push(this.safari_.then(function(safari) { + safari.kill(); + return safari.result(); + })); + } + var self = this; + return promise.all(tasks).thenFinally(function() { + self.server_ = null; + self.socket_ = null; + self.safari_ = null; + }); +}; + + +/** @const */ +var OPTIONS_CAPABILITY_KEY = 'safari.options'; + + + +/** + * Configuration options specific to the {@link Driver SafariDriver}. + * @constructor + * @extends {webdriver.Serializable} + */ +var Options = function() { + webdriver.Serializable.call(this); + + /** @private {Object} */ + this.options_ = null; + + /** @private {webdriver.logging.Preferences} */ + this.logPrefs_ = null; +}; +util.inherits(Options, webdriver.Serializable); + + +/** + * Extracts the SafariDriver specific options from the given capabilities + * object. + * @param {!webdriver.Capabilities} capabilities The capabilities object. + * @return {!Options} The ChromeDriver options. + */ +Options.fromCapabilities = function(capabilities) { + var options = new Options(); + + var o = capabilities.get(OPTIONS_CAPABILITY_KEY); + if (o instanceof Options) { + options = o; + } else if (o) { + options.setCleanSession(o.cleanSession); + } + + if (capabilities.has(webdriver.Capability.LOGGING_PREFS)) { + options.setLoggingPrefs( + capabilities.get(webdriver.Capability.LOGGING_PREFS)); + } + + return options; +}; + + +/** + * Sets whether to force Safari to start with a clean session. Enabling this + * option will cause all global browser data to be deleted. + * @param {boolean} clean Whether to make sure the session has no cookies, + * cache entries, local storage, or databases. + * @return {!Options} A self reference. + */ +Options.prototype.setCleanSession = function(clean) { + if (!this.options_) { + this.options_ = {}; + } + this.options_['cleanSession'] = clean; + return this; +}; + + +/** + * Sets the logging preferences for the new session. + * @param {!webdriver.logging.Preferences} prefs The logging preferences. + * @return {!Options} A self reference. + */ +Options.prototype.setLoggingPrefs = function(prefs) { + this.logPrefs_ = prefs; + return this; +}; + + +/** + * Converts this options instance to a {@link webdriver.Capabilities} object. + * @param {webdriver.Capabilities=} opt_capabilities The capabilities to merge + * these options into, if any. + * @return {!webdriver.Capabilities} The capabilities. + */ +Options.prototype.toCapabilities = function(opt_capabilities) { + var capabilities = opt_capabilities || webdriver.Capabilities.safari(); + if (this.logPrefs_) { + capabilities.set(webdriver.Capability.LOGGING_PREFS, this.logPrefs_); + } + if (this.options_) { + capabilities.set(OPTIONS_CAPABILITY_KEY, this); + } + return capabilities; +}; + + +/** + * Converts this instance to its JSON wire protocol representation. Note this + * function is an implementation detail not intended for general use. + * @return {!Object} The JSON wire protocol representation of this + * instance. + * @override + */ +Options.prototype.serialize = function() { + return this.options_ || {}; +}; + + + +/** + * A WebDriver client for Safari. This class should never be instantiated + * directly; instead, use the {@link selenium-webdriver.Builder}: + * + * var driver = new Builder() + * .forBrowser('safari') + * .build(); + * + * @param {(Options|webdriver.Capabilities)=} opt_config The configuration + * options for the new session. + * @param {webdriver.promise.ControlFlow=} opt_flow The control flow to create + * the driver under. + * @constructor + * @extends {webdriver.WebDriver} + */ +var Driver = function(opt_config, opt_flow) { + var executor = new CommandExecutor(); + var capabilities = + opt_config instanceof Options ? opt_config.toCapabilities() : + (opt_config || webdriver.Capabilities.safari()); + + var driver = webdriver.WebDriver.createSession( + executor, capabilities, opt_flow); + webdriver.WebDriver.call( + this, driver.getSession(), executor, driver.controlFlow()); +}; +util.inherits(Driver, webdriver.WebDriver); + + +// Public API + + +exports.Driver = Driver; +exports.Options = Options; diff --git a/test/_base_test.js b/test/_base_test.js new file mode 100644 index 0000000..8765153 --- /dev/null +++ b/test/_base_test.js @@ -0,0 +1,129 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +var assert = require('assert'), + fs = require('fs'), + path = require('path'); + +var base = require('../_base'); + +describe('Context', function() { + it('does not pollute the global scope', function() { + assert.equal('undefined', typeof goog); + + var context = new base.Context(); + assert.equal('undefined', typeof goog); + assert.equal('object', typeof context.closure.goog); + + context.closure.goog.require('goog.array'); + assert.equal('undefined', typeof goog); + assert.equal('object', typeof context.closure.goog.array); + }); +}); + + +function haveGenerators() { + try { + // Test for generator support. + new Function('function* x() {}'); + return true; + } catch (ex) { + return false; + } +} + + +function runClosureTest(file) { + var name = path.basename(file); + name = name.substring(0, name.length - '.js'.length); + + // Generator tests will fail to parse in ES5, so mark those tests as + // pending under ES5. + if (name.indexOf('_generator_') != -1 && !haveGenerators()) { + it(name); + return; + } + + describe(name, function() { + var context = new base.Context(true); + context.closure.document.title = name; + if (process.env.VERBOSE == '1') { + context.closure.goog.require('webdriver.logging'); + context.closure.goog.module.get('webdriver.logging') + .installConsoleHandler(); + } else { + // Null out console so everything loads silently. + context.closure.console = null; + } + context.closure.CLOSURE_IMPORT_SCRIPT(file); + + var tc = context.closure.G_testRunner.testCase; + if (!tc) { + tc = new context.closure.goog.testing.TestCase(name); + tc.autoDiscoverTests(); + } + + var shouldRunTests = tc.shouldRunTests(); + var allTests = tc.getTests(); + allTests.forEach(function(test) { + if (!shouldRunTests) { + it(test.name); + return; + } + + it(test.name, function(done) { + tc.setTests([test]); + tc.setCompletedCallback(function() { + if (tc.isSuccess()) { + return done(); + } + var results = tc.getTestResults(); + done(Error('\n' + Object.keys(results).map(function(name) { + var msg = + [name + ': ' + (results[name].length ? 'FAILED' : 'PASSED')]; + if (results[name].length) { + msg = msg.concat(results[name]); + } + return msg.join('\n'); + }).join('\n'))); + }); + tc.runTests(); + }); + }); + }); +} + + +function findTests(dir) { + fs.readdirSync(dir).forEach(function(name) { + var file = path.join(dir, name); + + var stat = fs.statSync(file); + if (stat.isDirectory() && name !== 'atoms' && name !== 'e2e') { + findTests(file); + return; + } + + var l = file.length - '_test.js'.length; + if (l >= 0 && file.indexOf('_test.js', l) == l) { + runClosureTest(file); + } + }); +} + +findTests(path.join( + __dirname, base.isDevMode() ? '../../..' : '../lib', 'webdriver/test')); diff --git a/test/actions_test.js b/test/actions_test.js new file mode 100644 index 0000000..8d6794a --- /dev/null +++ b/test/actions_test.js @@ -0,0 +1,53 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var Browser = require('..').Browser, + By = require('..').By, + until = require('..').until, + test = require('../lib/test'), + fileServer = require('../lib/test/fileserver'); + + +test.suite(function(env) { + var driver; + test.beforeEach(function() { driver = env.builder().build(); }); + test.afterEach(function() { driver.quit(); }); + + test.ignore(env.browsers(Browser.PHANTOM_JS, Browser.SAFARI)). + describe('WebDriver.actions()', function() { + + test.it('can move to and click element in an iframe', function() { + driver.get(fileServer.whereIs('click_tests/click_in_iframe.html')); + + driver.wait(until.elementLocated(By.id('ifr')), 5000) + .then(function(frame) { + driver.switchTo().frame(frame); + }); + + var link = driver.findElement(By.id('link')); + driver.actions() + .mouseMove(link) + .click() + .perform(); + + driver.wait(until.titleIs('Submitted Successfully!'), 5000); + }); + + }); +}); diff --git a/test/chrome/options_test.js b/test/chrome/options_test.js index 2398ddf..980f2a9 100644 --- a/test/chrome/options_test.js +++ b/test/chrome/options_test.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -44,47 +46,51 @@ describe('chrome.Options', function() { }); it('should rebuild options from wire representation', function() { + var expectedExtension = fs.readFileSync(__filename, 'base64'); var caps = webdriver.Capabilities.chrome().set('chromeOptions', { args: ['a', 'b'], - extensions: [1, 2], + extensions: [__filename], binary: 'binaryPath', - logFile: 'logFilePath', + logPath: 'logFilePath', detach: true, localState: 'localStateValue', prefs: 'prefsValue' }); var options = chrome.Options.fromCapabilities(caps); - - assert(options.args_.length).equalTo(2); - assert(options.args_[0]).equalTo('a'); - assert(options.args_[1]).equalTo('b'); - assert(options.extensions_.length).equalTo(2); - assert(options.extensions_[0]).equalTo(1); - assert(options.extensions_[1]).equalTo(2); - assert(options.binary_).equalTo('binaryPath'); - assert(options.logFile_).equalTo('logFilePath'); - assert(options.detach_).equalTo(true); - assert(options.localState_).equalTo('localStateValue'); - assert(options.prefs_).equalTo('prefsValue'); + var json = options.serialize(); + + assert(json.args.length).equalTo(2); + assert(json.args[0]).equalTo('a'); + assert(json.args[1]).equalTo('b'); + assert(json.extensions.length).equalTo(1); + assert(json.extensions[0]).equalTo(expectedExtension); + assert(json.binary).equalTo('binaryPath'); + assert(json.logPath).equalTo('logFilePath'); + assert(json.detach).equalTo(true); + assert(json.localState).equalTo('localStateValue'); + assert(json.prefs).equalTo('prefsValue'); }); it('should rebuild options from incomplete wire representation', function() { var caps = webdriver.Capabilities.chrome().set('chromeOptions', { - logFile: 'logFilePath' + logPath: 'logFilePath' }); var options = chrome.Options.fromCapabilities(caps); - var json = options.toJSON(); - - assert(json.args.length).equalTo(0); + var json = options.serialize(); + assert(json.args).isUndefined(); assert(json.binary).isUndefined(); - assert(json.detach).isFalse(); - assert(json.extensions.length).equalTo(0); + assert(json.detach).isUndefined(); + assert(json.excludeSwitches).isUndefined(); + assert(json.extensions).isUndefined(); assert(json.localState).isUndefined(); - assert(json.logFile).equalTo('logFilePath'); + assert(json.logPath).equalTo('logFilePath'); assert(json.prefs).isUndefined(); + assert(json.minidumpPath).isUndefined(); + assert(json.mobileEmulation).isUndefined(); + assert(json.perfLoggingPrefs).isUndefined(); }); it('should extract supported WebDriver capabilities', function() { @@ -103,26 +109,28 @@ describe('chrome.Options', function() { describe('addArguments', function() { it('takes var_args', function() { var options = new chrome.Options(); - assert(options.args_.length).equalTo(0); + assert(options.serialize().args).isUndefined(); options.addArguments('a', 'b'); - assert(options.args_.length).equalTo(2); - assert(options.args_[0]).equalTo('a'); - assert(options.args_[1]).equalTo('b'); + var json = options.serialize(); + assert(json.args.length).equalTo(2); + assert(json.args[0]).equalTo('a'); + assert(json.args[1]).equalTo('b'); }); it('flattens input arrays', function() { var options = new chrome.Options(); - assert(options.args_.length).equalTo(0); + assert(options.serialize().args).isUndefined(); options.addArguments(['a', 'b'], 'c', [1, 2], 3); - assert(options.args_.length).equalTo(6); - assert(options.args_[0]).equalTo('a'); - assert(options.args_[1]).equalTo('b'); - assert(options.args_[2]).equalTo('c'); - assert(options.args_[3]).equalTo(1); - assert(options.args_[4]).equalTo(2); - assert(options.args_[5]).equalTo(3); + var json = options.serialize(); + assert(json.args.length).equalTo(6); + assert(json.args[0]).equalTo('a'); + assert(json.args[1]).equalTo('b'); + assert(json.args[2]).equalTo('c'); + assert(json.args[3]).equalTo(1); + assert(json.args[4]).equalTo(2); + assert(json.args[5]).equalTo(3); }); }); @@ -152,10 +160,10 @@ describe('chrome.Options', function() { }); }); - describe('toJSON', function() { + describe('serialize', function() { it('base64 encodes extensions', function() { var expected = fs.readFileSync(__filename, 'base64'); - var wire = new chrome.Options().addExtensions(__filename).toJSON(); + var wire = new chrome.Options().addExtensions(__filename).serialize(); assert(wire.extensions.length).equalTo(1); assert(wire.extensions[0]).equalTo(expected); }); @@ -181,7 +189,7 @@ describe('chrome.Options', function() { var proxyPrefs = {}; var loggingPrefs = {}; var options = new chrome.Options(). - setLoggingPreferences(loggingPrefs). + setLoggingPrefs(loggingPrefs). setProxy(proxyPrefs); var caps = options.toCapabilities(); @@ -192,14 +200,18 @@ describe('chrome.Options', function() { }); test.suite(function(env) { - env.autoCreateDriver = false; + var driver; + + test.afterEach(function() { + driver.quit(); + }); - describe('options', function() { + describe('Chrome options', function() { test.it('can start Chrome with custom args', function() { var options = new chrome.Options(). addArguments('user-agent=foo;bar'); - var driver = env.builder(). + driver = env.builder(). setChromeOptions(options). build(); diff --git a/test/chrome/service_test.js b/test/chrome/service_test.js new file mode 100644 index 0000000..0e0209e --- /dev/null +++ b/test/chrome/service_test.js @@ -0,0 +1,45 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var webdriver = require('../..'), + chrome = require('../../chrome'), + assert = require('../../testing/assert'); + +var test = require('../../lib/test'); + + +test.suite(function(env) { + describe('chromedriver', function() { + var service; + test.afterEach(function() { + if (service) { + return service.kill(); + } + }); + + test.it('can be started on a custom path', function() { + service = new chrome.ServiceBuilder() + .setUrlBasePath('/foo/bar/baz') + .build(); + return service.start().then(function(url) { + assert(url).endsWith('/foo/bar/baz'); + }); + }); + }); +}, {browsers: ['chrome']}); \ No newline at end of file diff --git a/test/cookie_test.js b/test/cookie_test.js index 700e424..fe4c839 100644 --- a/test/cookie_test.js +++ b/test/cookie_test.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -20,13 +22,20 @@ var assert = require('assert'), var test = require('../lib/test'), fileserver = require('../lib/test/fileserver'), - Browser = test.Browser, + Browser = require('..').Browser, Pages = test.Pages; test.suite(function(env) { var driver; - beforeEach(function() { driver = env.driver; }); + + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); test.ignore(env.browsers(Browser.SAFARI)). // Cookie handling is broken. describe('Cookie Management;', function() { @@ -56,7 +65,7 @@ test.suite(function(env) { assertHasCookies(cookie1, cookie2); }); - test.ignore(env.browsers(Browser.OPERA)). + test.ignore(env.browsers(Browser.IE)). it('only returns cookies visible to the current page', function() { var cookie1 = createCookieSpec(); var cookie2 = createCookieSpec(); @@ -141,8 +150,7 @@ test.suite(function(env) { assertHasCookies(); }); - test.ignore(env.browsers( - Browser.ANDROID, Browser.FIREFOX, Browser.IE, Browser.OPERA)). + test.ignore(env.browsers(Browser.ANDROID, Browser.FIREFOX, Browser.IE)). it('should retain cookie expiry', function() { var cookie = createCookieSpec(); var expirationDelay = 5 * 1000; diff --git a/test/element_finding_test.js b/test/element_finding_test.js index d1e95f9..02fc8c8 100644 --- a/test/element_finding_test.js +++ b/test/element_finding_test.js @@ -1,36 +1,45 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; var fail = require('assert').fail; -var By = require('..').By, +var Browser = require('..').Browser, + By = require('..').By, error = require('..').error, + until = require('..').until, test = require('../lib/test'), assert = require('../testing/assert'), - Browser = test.Browser, Pages = test.Pages; test.suite(function(env) { - var browsers = env.browsers, - waitForTitleToBe = env.waitForTitleToBe; + var browsers = env.browsers; var driver; - beforeEach(function() { driver = env.driver; }); + + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); describe('finding elements', function() { @@ -40,14 +49,14 @@ test.suite(function(env) { driver.get(Pages.formPage); driver.get(Pages.xhtmlTestPage); driver.findElement(By.linkText('click me')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); }); describe('By.id()', function() { test.it('should work', function() { driver.get(Pages.xhtmlTestPage); driver.findElement(By.id('linkId')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); }); test.it('should fail if ID not present on page', function() { @@ -58,9 +67,9 @@ test.suite(function(env) { }); }); - test.ignore(browsers(Browser.ANDROID)).it( - 'should find multiple elements by ID even though that ' + - 'is malformed HTML', + test.it( + 'should find multiple elements by ID even though that is ' + + 'malformed HTML', function() { driver.get(Pages.nestedPage); driver.findElements(By.id('2')).then(function(elements) { @@ -73,14 +82,14 @@ test.suite(function(env) { test.it('should be able to click on link identified by text', function() { driver.get(Pages.xhtmlTestPage); driver.findElement(By.linkText('click me')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); }); test.it( 'should be able to find elements by partial link text', function() { driver.get(Pages.xhtmlTestPage); driver.findElement(By.partialLinkText('ick me')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); }); test.it('should work when link text contains equals sign', function() { @@ -143,8 +152,7 @@ test.suite(function(env) { }); }); - test.ignore(browsers(Browser.OPERA)). - it('works on XHTML pages', function() { + test.it('works on XHTML pages', function() { driver.get(test.whereIs('actualXhtmlPage.xhtml')); var el = driver.findElement(By.linkText('Foo')); diff --git a/test/execute_script_test.js b/test/execute_script_test.js new file mode 100644 index 0000000..caf8ddc --- /dev/null +++ b/test/execute_script_test.js @@ -0,0 +1,322 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var path = require('path'); + +var webdriver = require('..'), + Browser = webdriver.Browser, + By = webdriver.By, + assert = require('../testing/assert'), + test = require('../lib/test'); + + +test.suite(function(env) { + var driver; + + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); + + test.beforeEach(function() { + driver.get(test.Pages.echoPage); + }); + + describe('executeScript;', function() { + var shouldHaveFailed = new Error('Should have failed'); + + test.it('fails if script throws', function() { + execute('throw new Error("boom")') + .then(function() { throw shoudlHaveFailed; }) + .thenCatch(function(e) { + // The java WebDriver server adds a bunch of crap to error messages. + // Error message will just be "JavaScript error" for IE. + assert(e.message).matches(/.*(JavaScript error|boom).*/); + }); + }); + + test.it('fails if script does not parse', function() { + execute('throw function\\*') + .then(function() { throw shoudlHaveFailed; }) + .thenCatch(function(e) { + assert(e).not.equalTo(shouldHaveFailed); + }); + }); + + describe('scripts;', function() { + test.it('do not pollute the global scope', function() { + execute('var x = 1;'); + assert(execute('return typeof x;')).equalTo('undefined'); + }); + + test.it('can set global variables', function() { + execute('window.x = 1234;'); + assert(execute('return x;')).equalTo(1234); + }); + + test.it('may be defined as a function expression', function() { + assert(execute(function() { + return 1234 + 'abc'; + })).equalTo('1234abc'); + }); + }); + + describe('return values;', function() { + + test.it('returns undefined as null', function() { + assert(execute('var x; return x;')).isNull(); + }); + + test.it('can return null', function() { + assert(execute('return null;')).isNull(); + }); + + test.it('can return numbers', function() { + assert(execute('return 1234')).equalTo(1234); + assert(execute('return 3.1456')).equalTo(3.1456); + }); + + test.it('can return strings', function() { + assert(execute('return "hello"')).equalTo('hello'); + }); + + test.it('can return booleans', function() { + assert(execute('return true')).equalTo(true); + assert(execute('return false')).equalTo(false); + }); + + test.it('can return an array of primitives', function() { + execute('var x; return [1, false, null, 3.14, x]') + .then(verifyJson([1, false, null, 3.14, null])); + }); + + test.it('can return nested arrays', function() { + execute('return [[1, 2, [3]]]') + .then(verifyJson([[1, 2, [3]]])); + }); + + test.ignore(env.browsers(Browser.IE, Browser.SAFARI)). + it('can return empty object literal', function() { + execute('return {}').then(verifyJson({})); + }); + + test.it('can return object literals', function() { + execute('return {a: 1, b: false, c: null}').then(function(result) { + verifyJson(['a', 'b', 'c'])(Object.keys(result).sort()); + assert(result.a).equalTo(1); + assert(result.b).equalTo(false); + assert(result.c).isNull(); + }); + }); + + test.it('can return complex object literals', function() { + execute('return {a:{b: "hello"}}').then(verifyJson({a:{b: 'hello'}})); + }); + + test.it('can return dom elements as web elements', function() { + execute('return document.querySelector(".header.host")') + .then(function(result) { + assert(result).instanceOf(webdriver.WebElement); + assert(result.getText()).startsWith('host: '); + }); + }); + + test.it('can return array of dom elements', function() { + execute('var nodes = document.querySelectorAll(".request,.host");' + + 'return [nodes[0], nodes[1]];') + .then(function(result) { + assert(result.length).equalTo(2); + + assert(result[0]).instanceOf(webdriver.WebElement); + assert(result[0].getText()).startsWith('GET '); + + assert(result[1]).instanceOf(webdriver.WebElement); + assert(result[1].getText()).startsWith('host: '); + }); + }); + + test.it('can return a NodeList as an array of web elements', function() { + execute('return document.querySelectorAll(".request,.host");') + .then(function(result) { + assert(result.length).equalTo(2); + + assert(result[0]).instanceOf(webdriver.WebElement); + assert(result[0].getText()).startsWith('GET '); + + assert(result[1]).instanceOf(webdriver.WebElement); + assert(result[1].getText()).startsWith('host: '); + }); + }); + + test.it('can return object literal with element property', function() { + execute('return {a: document.body}').then(function(result) { + assert(result.a).instanceOf(webdriver.WebElement); + assert(result.a.getTagName()).equalTo('body'); + }); + }); + }); + + describe('parameters;', function() { + test.it('can pass numeric arguments', function() { + assert(execute('return arguments[0]', 12)).equalTo(12); + assert(execute('return arguments[0]', 3.14)).equalTo(3.14); + }); + + test.it('can pass boolean arguments', function() { + assert(execute('return arguments[0]', true)).equalTo(true); + assert(execute('return arguments[0]', false)).equalTo(false); + }); + + test.it('can pass string arguments', function() { + assert(execute('return arguments[0]', 'hi')).equalTo('hi'); + }); + + test.it('can pass null arguments', function() { + assert(execute('return arguments[0] === null', null)).equalTo(true); + assert(execute('return arguments[0]', null)).equalTo(null); + }); + + test.it('passes undefined as a null argument', function() { + var x; + assert(execute('return arguments[0] === null', x)).equalTo(true); + assert(execute('return arguments[0]', x)).equalTo(null); + }); + + test.it('can pass multiple arguments', function() { + assert(execute('return arguments.length')).equalTo(0); + assert(execute('return arguments.length', 1, 'a', false)).equalTo(3); + }); + + test.it('can return arguments object as array', function() { + execute('return arguments', 1, 'a', false).then(function(val) { + assert(val.length).equalTo(3); + assert(val[0]).equalTo(1); + assert(val[1]).equalTo('a'); + assert(val[2]).equalTo(false); + }); + }); + + test.it('can pass object literal', function() { + execute( + 'return [typeof arguments[0], arguments[0].a]', {a: 'hello'}) + .then(function(result) { + assert(result[0]).equalTo('object'); + assert(result[1]).equalTo('hello'); + }); + }); + + test.it('WebElement arguments are passed as DOM elements', function() { + var el = driver.findElement(By.tagName('div')); + assert(execute('return arguments[0].tagName.toLowerCase();', el)) + .equalTo('div'); + }); + + test.it('can pass array containing object literals', function() { + execute('return arguments[0]', [{color: "red"}]).then(function(result) { + assert(result.length).equalTo(1); + assert(result[0].color).equalTo('red'); + }); + }); + + test.it('does not modify object literal parameters', function() { + var input = {color: 'red'}; + execute('return arguments[0];', input).then(verifyJson(input)); + }); + }); + + // See https://code.google.com/p/selenium/issues/detail?id=8223. + describe('issue 8223;', function() { + describe('using for..in loops;', function() { + test.it('can return array built from for-loop index', function() { + execute(function() { + var ret = []; + for (var i = 0; i < 3; i++) { + ret.push(i); + } + return ret; + }).then(verifyJson[0, 1, 2]); + }); + + test.it('can copy input array contents', function() { + execute(function(input) { + var ret = []; + for (var i in input) { + ret.push(input[i]); + } + return ret; + }, ['fa', 'fe', 'fi']).then(verifyJson(['fa', 'fe', 'fi'])); + }); + + test.it('can iterate over input object keys', function() { + execute(function(thing) { + var ret = []; + for (var w in thing.words) { + ret.push(thing.words[w].word); + } + return ret; + }, {words: [{word: 'fa'}, {word: 'fe'}, {word: 'fi'}]}) + .then(verifyJson(['fa', 'fe', 'fi'])); + }); + + describe('recursive functions;', function() { + test.it('can build array from input', function() { + var input = ['fa', 'fe', 'fi']; + execute(function(thearray) { + var ret = []; + function build_response(thearray, ret) { + ret.push(thearray.shift()); + return (!thearray.length && ret + || build_response(thearray, ret)); + } + return build_response(thearray, ret); + }, input).then(verifyJson(input)); + }); + + test.it('can build array from elements in object', function() { + var input = {words: [{word: 'fa'}, {word: 'fe'}, {word: 'fi'}]}; + execute(function(thing) { + var ret = []; + function build_response(thing, ret) { + var item = thing.words.shift(); + ret.push(item.word); + return (!thing.words.length && ret + || build_response(thing, ret)); + } + return build_response(thing, ret); + }, input).then(verifyJson(['fa', 'fe', 'fi'])); + }); + }); + }); + }); + + }); + + function verifyJson(expected) { + return function(actual) { + assert(JSON.stringify(actual)).equalTo(JSON.stringify(expected)); + }; + } + + function execute() { + return driver.executeScript.apply(driver, arguments); + } +}); diff --git a/test/fingerprint_test.js b/test/fingerprint_test.js new file mode 100644 index 0000000..5a64494 --- /dev/null +++ b/test/fingerprint_test.js @@ -0,0 +1,57 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var assert = require('../testing/assert'), + test = require('../lib/test'), + Pages = test.Pages; + + +test.suite(function(env) { + var browsers = env.browsers; + + var driver; + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); + + describe('fingerprinting', function() { + test.it('it should fingerprint the navigator object', function() { + driver.get(Pages.simpleTestPage); + assert(driver.executeScript('return navigator.webdriver')).equalTo(true); + }); + + test.it('fingerprint must not be writable', function() { + driver.get(Pages.simpleTestPage); + assert(driver.executeScript( + 'navigator.webdriver = "ohai"; return navigator.webdriver')) + .equalTo(true); + }); + + test.it('leaves fingerprint on svg pages', function() { + driver.get(Pages.svgPage); + assert(driver.executeScript('return navigator.webdriver')).equalTo(true); + }); + }); + +// Currently only implemented in firefox. +}, {browsers: ['firefox']}); diff --git a/test/firefox/extension_test.js b/test/firefox/extension_test.js new file mode 100644 index 0000000..50936f7 --- /dev/null +++ b/test/firefox/extension_test.js @@ -0,0 +1,96 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var AdmZip = require('adm-zip'), + assert = require('assert'), + crypto = require('crypto'), + fs = require('fs'), + path = require('path'); + +var extension = require('../../firefox/extension'), + io = require('../../io'), + it = require('../../testing').it; + + +var JETPACK_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/jetpack-sample.xpi'); +var NORMAL_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/sample.xpi'); + +var JETPACK_EXTENSION_ID = 'jid1-EaXX7k0wwiZR7w@jetpack'; +var NORMAL_EXTENSION_ID = 'sample@seleniumhq.org'; + + +describe('extension', function() { + it('can install a jetpack xpi file', function() { + return io.tmpDir().then(function(dir) { + return extension.install(JETPACK_EXTENSION, dir).then(function(id) { + assert.equal(JETPACK_EXTENSION_ID, id); + var file = path.join(dir, id + '.xpi'); + assert.ok(fs.existsSync(file), 'no such file: ' + file); + assert.ok(!fs.statSync(file).isDirectory()); + + var copiedSha1 = crypto.createHash('sha1') + .update(fs.readFileSync(file)) + .digest('hex'); + + var goldenSha1 = crypto.createHash('sha1') + .update(fs.readFileSync(JETPACK_EXTENSION)) + .digest('hex'); + + assert.equal(copiedSha1, goldenSha1); + }); + }); + }); + + it('can install a normal xpi file', function() { + return io.tmpDir().then(function(dir) { + return extension.install(NORMAL_EXTENSION, dir).then(function(id) { + assert.equal(NORMAL_EXTENSION_ID, id); + + var file = path.join(dir, NORMAL_EXTENSION_ID); + assert.ok(fs.statSync(file).isDirectory()); + + assert.ok(fs.existsSync(path.join(file, 'chrome.manifest'))); + assert.ok(fs.existsSync(path.join(file, 'content/overlay.xul'))); + assert.ok(fs.existsSync(path.join(file, 'content/overlay.js'))); + assert.ok(fs.existsSync(path.join(file, 'install.rdf'))); + }); + }); + }); + + it('can install an extension from a directory', function() { + return io.tmpDir().then(function(srcDir) { + var buf = fs.readFileSync(NORMAL_EXTENSION); + new AdmZip(buf).extractAllTo(srcDir, true); + return io.tmpDir().then(function(dstDir) { + return extension.install(srcDir, dstDir).then(function(id) { + assert.equal(NORMAL_EXTENSION_ID, id); + + var dir = path.join(dstDir, NORMAL_EXTENSION_ID); + + assert.ok(fs.existsSync(path.join(dir, 'chrome.manifest'))); + assert.ok(fs.existsSync(path.join(dir, 'content/overlay.xul'))); + assert.ok(fs.existsSync(path.join(dir, 'content/overlay.js'))); + assert.ok(fs.existsSync(path.join(dir, 'install.rdf'))); + }); + }); + }); + }); +}); diff --git a/test/firefox/firefox_test.js b/test/firefox/firefox_test.js new file mode 100644 index 0000000..3fd7443 --- /dev/null +++ b/test/firefox/firefox_test.js @@ -0,0 +1,159 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var path = require('path'); + +var firefox = require('../../firefox'), + io = require('../../io'), + test = require('../../lib/test'), + assert = require('../../testing/assert'); + + +var JETPACK_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/jetpack-sample.xpi'); +var NORMAL_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/sample.xpi'); + + +test.suite(function(env) { + describe('firefox', function() { + describe('Options', function() { + var driver; + + test.beforeEach(function() { + driver = null; + }); + + test.afterEach(function() { + if (driver) { + driver.quit(); + } + }); + + test.it('can start Firefox with custom preferences', function() { + var profile = new firefox.Profile(); + profile.setPreference('general.useragent.override', 'foo;bar'); + + var options = new firefox.Options().setProfile(profile); + + driver = env.builder(). + setFirefoxOptions(options). + build(); + + driver.get('data:text/html,
        content
        '); + + var userAgent = driver.executeScript( + 'return window.navigator.userAgent'); + assert(userAgent).equalTo('foo;bar'); + }); + + test.it('can start Firefox with a jetpack extension', function() { + var profile = new firefox.Profile(); + profile.addExtension(JETPACK_EXTENSION); + + var options = new firefox.Options().setProfile(profile); + + driver = env.builder(). + setFirefoxOptions(options). + build(); + + loadJetpackPage(driver, + 'data:text/html;charset=UTF-8,
        content
        '); + assert(driver.findElement({id: 'jetpack-sample-banner'}).getText()) + .equalTo('Hello, world!'); + }); + + test.it('can start Firefox with a normal extension', function() { + var profile = new firefox.Profile(); + profile.addExtension(NORMAL_EXTENSION); + + var options = new firefox.Options().setProfile(profile); + + driver = env.builder(). + setFirefoxOptions(options). + build(); + + driver.get('data:text/html,
        content
        '); + assert(driver.findElement({id: 'sample-extension-footer'}).getText()) + .equalTo('Goodbye'); + }); + + test.it('can start Firefox with multiple extensions', function() { + var profile = new firefox.Profile(); + profile.addExtension(JETPACK_EXTENSION); + profile.addExtension(NORMAL_EXTENSION); + + var options = new firefox.Options().setProfile(profile); + + driver = env.builder(). + setFirefoxOptions(options). + build(); + + loadJetpackPage(driver, + 'data:text/html;charset=UTF-8,
        content
        '); + assert(driver.findElement({id: 'jetpack-sample-banner'}).getText()) + .equalTo('Hello, world!'); + assert(driver.findElement({id: 'sample-extension-footer'}).getText()) + .equalTo('Goodbye'); + }); + + function loadJetpackPage(driver, url) { + // On linux the jetpack extension does not always run the first time + // we load a page. If this happens, just reload the page (a simple + // refresh doesn't appear to work). + driver.wait(function() { + driver.get(url); + return driver.isElementPresent({id: 'jetpack-sample-banner'}); + }, 3000); + } + }); + + describe('profile management', function() { + var driver; + + test.beforeEach(function() { + driver = null; + }); + + test.afterEach(function() { + if (driver) { + driver.quit(); + } + }); + + test.ignore(env.isRemote). + it('deletes the temp profile on quit', function() { + driver = env.builder().build(); + + var profilePath = driver.call(function() { + var path = driver.profilePath_; + assert(io.exists(path)).isTrue(); + return path; + }); + + return driver.quit().then(function() { + driver = null; + return profilePath; + }).then(function(path) { + assert(io.exists(path)).isFalse(); + }); + }); + }); + }); +}, {browsers: ['firefox']}); diff --git a/test/firefox/profile_test.js b/test/firefox/profile_test.js new file mode 100644 index 0000000..feaa42f --- /dev/null +++ b/test/firefox/profile_test.js @@ -0,0 +1,187 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var AdmZip = require('adm-zip'), + assert = require('assert'), + fs = require('fs'), + path = require('path'); + +var promise = require('../..').promise, + Profile = require('../../firefox/profile').Profile, + decode = require('../../firefox/profile').decode, + loadUserPrefs = require('../../firefox/profile').loadUserPrefs, + io = require('../../io'), + it = require('../../testing').it; + + +var JETPACK_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/jetpack-sample.xpi'); +var NORMAL_EXTENSION = path.join(__dirname, + '../../lib/test/data/firefox/sample.xpi'); + +var JETPACK_EXTENSION_ID = 'jid1-EaXX7k0wwiZR7w@jetpack.xpi'; +var NORMAL_EXTENSION_ID = 'sample@seleniumhq.org'; +var WEBDRIVER_EXTENSION_ID = 'fxdriver@googlecode.com'; + + + +describe('Profile', function() { + describe('setPreference', function() { + it('allows setting custom properties', function() { + var profile = new Profile(); + assert.equal(undefined, profile.getPreference('foo')); + + profile.setPreference('foo', 'bar'); + assert.equal('bar', profile.getPreference('foo')); + }); + + it('allows overriding mutable properties', function() { + var profile = new Profile(); + assert.equal('about:blank', profile.getPreference('browser.newtab.url')); + + profile.setPreference('browser.newtab.url', 'http://www.example.com'); + assert.equal('http://www.example.com', + profile.getPreference('browser.newtab.url')); + }); + + it('throws if setting a frozen preference', function() { + var profile = new Profile(); + assert.throws(function() { + profile.setPreference('app.update.auto', true); + }); + }); + }); + + describe('writeToDisk', function() { + it('copies template directory recursively', function() { + var templateDir; + return io.tmpDir().then(function(td) { + templateDir = td; + var foo = path.join(templateDir, 'foo'); + fs.writeFileSync(foo, 'Hello, world'); + + var bar = path.join(templateDir, 'subfolder/bar'); + fs.mkdirSync(path.dirname(bar)); + fs.writeFileSync(bar, 'Goodbye, world!'); + + return new Profile(templateDir).writeToDisk(); + }).then(function(profileDir) { + assert.notEqual(profileDir, templateDir); + + assert.equal('Hello, world', + fs.readFileSync(path.join(profileDir, 'foo'))); + assert.equal('Goodbye, world!', + fs.readFileSync(path.join(profileDir, 'subfolder/bar'))); + }); + }); + + it('does not copy lock files', function() { + return io.tmpDir().then(function(dir) { + fs.writeFileSync(path.join(dir, 'parent.lock'), 'lock'); + fs.writeFileSync(path.join(dir, 'lock'), 'lock'); + fs.writeFileSync(path.join(dir, '.parentlock'), 'lock'); + return new Profile(dir).writeToDisk(); + }).then(function(dir) { + assert.ok(fs.existsSync(dir)); + assert.ok(!fs.existsSync(path.join(dir, 'parent.lock'))); + assert.ok(!fs.existsSync(path.join(dir, 'lock'))); + assert.ok(!fs.existsSync(path.join(dir, '.parentlock'))); + }); + }); + + describe('user.js', function() { + + it('writes defaults', function() { + return new Profile().writeToDisk().then(function(dir) { + return loadUserPrefs(path.join(dir, 'user.js')); + }).then(function(prefs) { + // Just check a few. + assert.equal(false, prefs['app.update.auto']); + assert.equal(true, prefs['browser.EULA.override']); + assert.equal(false, prefs['extensions.update.enabled']); + assert.equal('about:blank', prefs['browser.newtab.url']); + assert.equal(30, prefs['dom.max_script_run_time']); + }); + }); + + it('merges template user.js into preferences', function() { + return io.tmpDir().then(function(dir) { + fs.writeFileSync(path.join(dir, 'user.js'), [ + 'user_pref("browser.newtab.url", "http://www.example.com")', + 'user_pref("dom.max_script_run_time", 1234)' + ].join('\n')); + + return new Profile(dir).writeToDisk(); + }).then(function(profile) { + return loadUserPrefs(path.join(profile, 'user.js')); + }).then(function(prefs) { + assert.equal('http://www.example.com', prefs['browser.newtab.url']); + assert.equal(1234, prefs['dom.max_script_run_time']); + }); + }); + + it('ignores frozen preferences when merging template user.js', + function() { + return io.tmpDir().then(function(dir) { + fs.writeFileSync(path.join(dir, 'user.js'), + 'user_pref("app.update.auto", true)'); + return new Profile(dir).writeToDisk(); + }).then(function(profile) { + return loadUserPrefs(path.join(profile, 'user.js')); + }).then(function(prefs) { + assert.equal(false, prefs['app.update.auto']); + }); + }); + }); + + describe('extensions', function() { + it('are copied into new profile directory', function() { + var profile = new Profile(); + profile.addExtension(JETPACK_EXTENSION); + profile.addExtension(NORMAL_EXTENSION); + + return profile.writeToDisk().then(function(dir) { + dir = path.join(dir, 'extensions'); + assert.ok(fs.existsSync(path.join(dir, JETPACK_EXTENSION_ID))); + assert.ok(fs.existsSync(path.join(dir, NORMAL_EXTENSION_ID))); + assert.ok(fs.existsSync(path.join(dir, WEBDRIVER_EXTENSION_ID))); + }); + }); + }); + }); + + describe('encode', function() { + it('excludes the bundled WebDriver extension', function() { + return new Profile().encode().then(function(data) { + return decode(data); + }).then(function(dir) { + assert.ok(fs.existsSync(path.join(dir, 'user.js'))); + assert.ok(fs.existsSync(path.join(dir, 'extensions'))); + return loadUserPrefs(path.join(dir, 'user.js')); + }).then(function(prefs) { + // Just check a few. + assert.equal(false, prefs['app.update.auto']); + assert.equal(true, prefs['browser.EULA.override']); + assert.equal(false, prefs['extensions.update.enabled']); + assert.equal('about:blank', prefs['browser.newtab.url']); + assert.equal(30, prefs['dom.max_script_run_time']); + }); + }); + }); +}); diff --git a/test/http/http_test.js b/test/http/http_test.js new file mode 100644 index 0000000..358dad5 --- /dev/null +++ b/test/http/http_test.js @@ -0,0 +1,132 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +var assert = require('assert'); +var http = require('http'); + +var HttpClient = require('../../http').HttpClient; +var HttpRequest = require('../../_base').require('webdriver.http.Request'); +var Server = require('../../lib/test/httpserver').Server; +var promise = require('../..').promise; +var test = require('../../lib/test'); + +describe('HttpClient', function() { + this.timeout(4*1000); + + var server = new Server(function(req, res) { + if (req.method == 'GET' && req.url == '/echo') { + res.writeHead(200, req.headers); + res.end(); + + } else if (req.method == 'GET' && req.url == '/redirect') { + res.writeHead(303, {'Location': server.url('/hello')}); + res.end(); + + } else if (req.method == 'GET' && req.url == '/hello') { + res.writeHead(200, {'content-type': 'text/plain'}); + res.end('hello, world!'); + + } else if (req.method == 'GET' && req.url == '/badredirect') { + res.writeHead(303, {}); + res.end(); + + } else if (req.method == 'GET' && req.url == '/proxy') { + res.writeHead(200, req.headers); + res.end(); + + } else if (req.method == 'GET' && req.url == '/proxy/redirect') { + res.writeHead(303, {'Location': '/proxy'}); + res.end(); + + } else { + res.writeHead(404, {}); + res.end(); + } + }); + + test.before(function() { + return server.start(); + }); + + test.after(function() { + return server.stop(); + }); + + test.it('can send a basic HTTP request', function() { + var request = new HttpRequest('GET', '/echo'); + request.headers['Foo'] = 'Bar'; + + var agent = new http.Agent(); + agent.maxSockets = 1; // Only making 1 request. + + var client = new HttpClient(server.url(), agent); + return promise.checkedNodeCall(client.send.bind(client, request)) + .then(function(response) { + assert.equal(200, response.status); + assert.equal( + 'application/json; charset=utf-8', response.headers['accept']); + assert.equal('Bar', response.headers['foo']); + assert.equal('0', response.headers['content-length']); + assert.equal('keep-alive', response.headers['connection']); + assert.equal(server.host(), response.headers['host']); + }); + }); + + test.it('automatically follows redirects', function() { + var request = new HttpRequest('GET', '/redirect'); + var client = new HttpClient(server.url()); + return promise.checkedNodeCall(client.send.bind(client, request)) + .then(function(response) { + assert.equal(200, response.status); + assert.equal('text/plain', response.headers['content-type']); + assert.equal('hello, world!', response.body); + }); + }); + + test.it('handles malformed redirect responses', function() { + var request = new HttpRequest('GET', '/badredirect'); + var client = new HttpClient(server.url()); + return promise.checkedNodeCall(client.send.bind(client, request)) + .thenCatch(function(err) { + assert.ok(/Failed to parse "Location"/.test(err.message), + 'Not the expected error: ' + err.message); + }); + }); + + test.it('proxies requests through the webdriver proxy', function() { + var request = new HttpRequest('GET', '/proxy'); + var client = new HttpClient( + 'http://another.server.com', undefined, server.url()); + return promise.checkedNodeCall(client.send.bind(client, request)) + .then(function(response) { + assert.equal(200, response.status); + assert.equal('another.server.com', response.headers['host']); + }); + }); + + test.it( + 'proxies requests through the webdriver proxy on redirect', function() { + var request = new HttpRequest('GET', '/proxy/redirect'); + var client = new HttpClient( + 'http://another.server.com', undefined, server.url()); + return promise.checkedNodeCall(client.send.bind(client, request)) + .then(function(response) { + assert.equal(200, response.status); + assert.equal('another.server.com', response.headers['host']); + }); + }); +}); diff --git a/test/http/util_test.js b/test/http/util_test.js index 37835b4..4876297 100644 --- a/test/http/util_test.js +++ b/test/http/util_test.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -125,12 +127,12 @@ describe('selenium-webdriver/http/util', function() { var isReady = util.waitForServer(baseUrl, 200). then(function() { done('Did not expect to succeed'); }). then(null, function(e) { - assert.equal(err, e); + assert.equal('cancelled!', e.message); }). then(function() { done(); }, done); setTimeout(function() { - isReady.cancel(err); + isReady.cancel('cancelled!'); }, 50); }); }); @@ -165,16 +167,15 @@ describe('selenium-webdriver/http/util', function() { it('can cancel wait', function(done) { responseCode = 404; - var err = Error('cancelled!'); var isReady = util.waitForUrl(baseUrl, 200). then(function() { done('Did not expect to succeed'); }). then(null, function(e) { - assert.equal(err, e); + assert.equal('cancelled!', e.message); }). then(function() { done(); }, done); setTimeout(function() { - isReady.cancel(err); + isReady.cancel('cancelled!'); }, 50); }); }); diff --git a/test/io_test.js b/test/io_test.js new file mode 100644 index 0000000..2fe4475 --- /dev/null +++ b/test/io_test.js @@ -0,0 +1,227 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var assert = require('assert'), + fs = require('fs'), + path = require('path'), + tmp = require('tmp'); + +var io = require('../io'), + before = require('../testing').before, + beforeEach = require('../testing').beforeEach, + it = require('../testing').it; + + +describe('io', function() { + describe('copy', function() { + var tmpDir; + + before(function() { + return io.tmpDir().then(function(d) { + tmpDir = d; + + fs.writeFileSync(path.join(d, 'foo'), 'Hello, world'); + fs.symlinkSync(path.join(d, 'foo'), path.join(d, 'symlinked-foo')); + }); + }); + + it('can copy one file to another', function() { + return io.tmpFile().then(function(f) { + return io.copy(path.join(tmpDir, 'foo'), f).then(function(p) { + assert.equal(p, f); + assert.equal('Hello, world', fs.readFileSync(p)); + }); + }); + }); + + it('can copy symlink to destination', function() { + return io.tmpFile().then(function(f) { + return io.copy(path.join(tmpDir, 'symlinked-foo'), f).then(function(p) { + assert.equal(p, f); + assert.equal('Hello, world', fs.readFileSync(p)); + }); + }); + }); + + it('fails if given a directory as a source', function() { + return io.tmpFile().then(function(f) { + return io.copy(tmpDir, f); + }).then(function() { + throw Error('Should have failed with a type error'); + }, function() { + // Do nothing; expected. + }); + }); + }); + + describe('copyDir', function() { + it('copies recursively', function() { + return io.tmpDir().then(function(dir) { + fs.writeFileSync(path.join(dir, 'file1'), 'hello'); + fs.mkdirSync(path.join(dir, 'sub')); + fs.mkdirSync(path.join(dir, 'sub/folder')); + fs.writeFileSync(path.join(dir, 'sub/folder/file2'), 'goodbye'); + + return io.tmpDir().then(function(dst) { + return io.copyDir(dir, dst).then(function(ret) { + assert.equal(dst, ret); + + assert.equal('hello', + fs.readFileSync(path.join(dst, 'file1'))); + assert.equal('goodbye', + fs.readFileSync(path.join(dst, 'sub/folder/file2'))); + }); + }); + }); + }); + + it('creates destination dir if necessary', function() { + return io.tmpDir().then(function(srcDir) { + fs.writeFileSync(path.join(srcDir, 'foo'), 'hi'); + return io.tmpDir().then(function(dstDir) { + return io.copyDir(srcDir, path.join(dstDir, 'sub')); + }); + }).then(function(p) { + assert.equal('sub', path.basename(p)); + assert.equal('hi', fs.readFileSync(path.join(p, 'foo'))); + }); + }); + + it('supports regex exclusion filter', function() { + return io.tmpDir().then(function(src) { + fs.writeFileSync(path.join(src, 'foo'), 'a'); + fs.writeFileSync(path.join(src, 'bar'), 'b'); + fs.writeFileSync(path.join(src, 'baz'), 'c'); + fs.mkdirSync(path.join(src, 'sub')); + fs.writeFileSync(path.join(src, 'sub/quux'), 'd'); + fs.writeFileSync(path.join(src, 'sub/quot'), 'e'); + + return io.tmpDir().then(function(dst) { + return io.copyDir(src, dst, /(bar|quux)/); + }); + }).then(function(dir) { + assert.equal('a', fs.readFileSync(path.join(dir, 'foo'))); + assert.equal('c', fs.readFileSync(path.join(dir, 'baz'))); + assert.equal('e', fs.readFileSync(path.join(dir, 'sub/quot'))); + + assert.ok(!fs.existsSync(path.join(dir, 'bar'))); + assert.ok(!fs.existsSync(path.join(dir, 'sub/quux'))); + }); + }); + + it('supports exclusion filter function', function() { + return io.tmpDir().then(function(src) { + fs.writeFileSync(path.join(src, 'foo'), 'a'); + fs.writeFileSync(path.join(src, 'bar'), 'b'); + fs.writeFileSync(path.join(src, 'baz'), 'c'); + fs.mkdirSync(path.join(src, 'sub')); + fs.writeFileSync(path.join(src, 'sub/quux'), 'd'); + fs.writeFileSync(path.join(src, 'sub/quot'), 'e'); + + return io.tmpDir().then(function(dst) { + return io.copyDir(src, dst, function(f) { + return f !== path.join(src, 'foo') + && f !== path.join(src, 'sub/quot'); + }); + }); + }).then(function(dir) { + assert.equal('b', fs.readFileSync(path.join(dir, 'bar'))); + assert.equal('c', fs.readFileSync(path.join(dir, 'baz'))); + assert.equal('d', fs.readFileSync(path.join(dir, 'sub/quux'))); + + assert.ok(!fs.existsSync(path.join(dir, 'foo'))); + assert.ok(!fs.existsSync(path.join(dir, 'sub/quot'))); + }); + }); + }); + + describe('exists', function() { + var dir; + + before(function() { + return io.tmpDir().then(function(d) { + dir = d; + }); + }); + + it('works for directories', function() { + return io.exists(dir).then(assert.ok); + }); + + it('works for files', function() { + var file = path.join(dir, 'foo'); + fs.writeFileSync(file, ''); + return io.exists(file).then(assert.ok); + }); + + it('does not return a rejected promise if file does not exist', function() { + return io.exists(path.join(dir, 'not-there')).then(function(exists) { + assert.ok(!exists); + }); + }); + }); + + describe('unlink', function() { + var dir; + + before(function() { + return io.tmpDir().then(function(d) { + dir = d; + }); + }); + + it('silently succeeds if the path does not exist', function() { + return io.unlink(path.join(dir, 'not-there')); + }); + + it('deletes files', function() { + var file = path.join(dir, 'foo'); + fs.writeFileSync(file, ''); + return io.exists(file).then(assert.ok).then(function() { + return io.unlink(file); + }).then(function() { + return io.exists(file); + }).then(function(exists) { + return assert.ok(!exists); + }); + }); + }); + + describe('rmDir', function() { + it('succeeds if the designated directory does not exist', function() { + return io.tmpDir().then(function(d) { + return io.rmDir(path.join(d, 'i/do/not/exist')); + }); + }); + + it('deletes recursively', function() { + return io.tmpDir().then(function(dir) { + fs.writeFileSync(path.join(dir, 'file1'), 'hello'); + fs.mkdirSync(path.join(dir, 'sub')); + fs.mkdirSync(path.join(dir, 'sub/folder')); + fs.writeFileSync(path.join(dir, 'sub/folder/file2'), 'goodbye'); + + return io.rmDir(dir).then(function() { + assert.ok(!fs.existsSync(dir)); + assert.ok(!fs.existsSync(path.join(dir, 'sub/folder/file2'))); + }); + }); + }); + }); +}); diff --git a/test/logging_test.js b/test/logging_test.js new file mode 100644 index 0000000..fb8cdb2 --- /dev/null +++ b/test/logging_test.js @@ -0,0 +1,167 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var Browser = require('..').Browser, + By = require('..').By, + logging = require('..').logging, + assert = require('../testing/assert'), + test = require('../lib/test'); + +test.suite(function(env) { + // Logging API has numerous issues with PhantomJS: + // - does not support adjusting log levels for type "browser". + // - does not return proper log level for "browser" messages. + // - does not delete logs after retrieval + // Logging API is not supported in IE. + // Tests depend on opening data URLs, which is broken in Safari (issue 7586) + test.ignore(env.browsers(Browser.PHANTOM_JS, Browser.IE, Browser.SAFARI)). + describe('logging', function() { + var driver; + + test.beforeEach(function() { + driver = null; + }); + + test.afterEach(function() { + if (driver) { + driver.quit(); + } + }); + + test.it('can be disabled', function() { + var prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.OFF); + + driver = env.builder() + .setLoggingPrefs(prefs) + .build(); + + driver.get(dataUrl( + '')); + driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { + assert(entries.length).equalTo(0); + }); + }); + + // Firefox does not capture JS error console log messages. + test.ignore(env.browsers(Browser.FIREFOX)). + it('can be turned down', function() { + var prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.SEVERE); + + driver = env.builder() + .setLoggingPrefs(prefs) + .build(); + + driver.get(dataUrl( + '')); + driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { + assert(entries.length).equalTo(1); + assert(entries[0].level.name).equalTo('SEVERE'); + assert(entries[0].message).endsWith('and this is an error'); + }); + }); + + // Firefox does not capture JS error console log messages. + test.ignore(env.browsers(Browser.FIREFOX)). + it('can be made verbose', function() { + var prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); + + driver = env.builder() + .setLoggingPrefs(prefs) + .build(); + + driver.get(dataUrl( + '')); + driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { + assert(entries.length).equalTo(3); + assert(entries[0].level.name).equalTo('DEBUG'); + assert(entries[0].message).endsWith('hello'); + + assert(entries[1].level.name).equalTo('WARNING'); + assert(entries[1].message).endsWith('this is a warning'); + + assert(entries[2].level.name).equalTo('SEVERE'); + assert(entries[2].message).endsWith('and this is an error'); + }); + }); + + // Firefox does not capture JS error console log messages. + test.ignore(env.browsers(Browser.FIREFOX)). + it('clears records after retrieval', function() { + var prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); + + driver = env.builder() + .setLoggingPrefs(prefs) + .build(); + + driver.get(dataUrl( + '')); + driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { + assert(entries.length).equalTo(3); + }); + driver.manage().logs().get(logging.Type.BROWSER).then(function(entries) { + assert(entries.length).equalTo(0); + }); + }); + + test.it('does not mix log types', function() { + var prefs = new logging.Preferences(); + prefs.setLevel(logging.Type.BROWSER, logging.Level.DEBUG); + prefs.setLevel(logging.Type.DRIVER, logging.Level.SEVERE); + + driver = env.builder() + .setLoggingPrefs(prefs) + .build(); + + driver.get(dataUrl( + '')); + driver.manage().logs().get(logging.Type.DRIVER).then(function(entries) { + assert(entries.length).equalTo(0); + }); + }); + }); + + function dataUrl(var_args) { + return 'data:text/html,' + + Array.prototype.slice.call(arguments, 0).join(''); + } +}); diff --git a/test/net/portprober_test.js b/test/net/portprober_test.js index ae58cb6..03a2f7a 100644 --- a/test/net/portprober_test.js +++ b/test/net/portprober_test.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; diff --git a/test/page_loading_test.js b/test/page_loading_test.js index 812faae..df67d76 100644 --- a/test/page_loading_test.js +++ b/test/page_loading_test.js @@ -1,34 +1,42 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; -var By = require('..').By, +var Browser = require('..').Browser, + By = require('..').By, ErrorCode = require('..').error.ErrorCode, + until = require('..').until, assert = require('../testing/assert'), test = require('../lib/test'), - Browser = test.Browser, Pages = test.Pages; test.suite(function(env) { - var browsers = env.browsers, - waitForTitleToBe = env.waitForTitleToBe; + var browsers = env.browsers; var driver; - beforeEach(function() { driver = env.driver; }); + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); test.it('should wait for document to be loaded', function() { driver.get(Pages.simpleTestPage); @@ -41,8 +49,7 @@ test.suite(function(env) { assert(driver.getTitle()).equalTo('We Arrive Here'); }); - test.ignore(browsers(Browser.ANDROID)).it('should follow meta redirects', - function() { + test.it('should follow meta redirects', function() { driver.get(Pages.metaRedirectPage); assert(driver.getTitle()).equalTo('We Arrive Here'); }); @@ -53,7 +60,7 @@ test.suite(function(env) { driver.findElement(By.id('id1')); }); - test.ignore(browsers(Browser.ANDROID, Browser.IOS)). + test.ignore(browsers(Browser.IPAD, Browser.IPHONE)). it('should wait for all frames to load in a frameset', function() { driver.get(Pages.framesetPage); driver.switchTo().frame(0); @@ -69,12 +76,12 @@ test.suite(function(env) { }); }); - test.ignore(browsers(Browser.ANDROID, Browser.SAFARI)). + test.ignore(browsers(Browser.SAFARI)). it('should be able to navigate back in browser history', function() { driver.get(Pages.formPage); driver.findElement(By.id('imageButton')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); driver.navigate().back(); assert(driver.getTitle()).equalTo('We Leave From Here'); @@ -85,27 +92,29 @@ test.suite(function(env) { driver.get(Pages.xhtmlTestPage); driver.findElement(By.name('sameWindow')).click(); - waitForTitleToBe('This page has iframes'); + driver.wait(until.titleIs('This page has iframes'), 5000); driver.navigate().back(); assert(driver.getTitle()).equalTo('XHTML Test Page'); }); - test.ignore(browsers(Browser.ANDROID, Browser.SAFARI)). + test.ignore(browsers(Browser.SAFARI)). it('should be able to navigate forwards in browser history', function() { driver.get(Pages.formPage); driver.findElement(By.id('imageButton')).click(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); driver.navigate().back(); - waitForTitleToBe('We Leave From Here'); + driver.wait(until.titleIs('We Leave From Here'), 5000); driver.navigate().forward(); - waitForTitleToBe('We Arrive Here'); + driver.wait(until.titleIs('We Arrive Here'), 5000); }); - test.it('should be able to refresh a page', function() { + // PhantomJS 2.0 does not properly reload pages on refresh. + test.ignore(browsers(Browser.PHANTOM_JS)). + it('should be able to refresh a page', function() { driver.get(Pages.xhtmlTestPage); driver.navigate().refresh(); @@ -123,12 +132,12 @@ test.suite(function(env) { // Only implemented in Firefox. test.ignore(browsers( - Browser.ANDROID, Browser.CHROME, Browser.IE, - Browser.IOS, + Browser.IPAD, + Browser.IPHONE, Browser.OPERA, - Browser.PHANTOMJS, + Browser.PHANTOM_JS, Browser.SAFARI)). it('should timeout if page load timeout is set', function() { driver.call(function() { @@ -137,7 +146,12 @@ test.suite(function(env) { then(function() { throw Error('Should have timed out on page load'); }, function(e) { - assert(e.code).equalTo(ErrorCode.SCRIPT_TIMEOUT); + // The FirefoxDriver returns TIMEOUT directly, where as the + // java server returns SCRIPT_TIMEOUT (bug?). + if (e.code !== ErrorCode.SCRIPT_TIMEOUT && + e.code !== ErrorCode.TIMEOUT) { + throw Error('Unexpected error response: ' + e); + } }); }).then(resetPageLoad, function(err) { resetPageLoad().thenFinally(function() { diff --git a/test/phantomjs/execute_phantomjs_test.js b/test/phantomjs/execute_phantomjs_test.js new file mode 100644 index 0000000..22a9a22 --- /dev/null +++ b/test/phantomjs/execute_phantomjs_test.js @@ -0,0 +1,73 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var assert = require('assert'); +var path = require('path'); +var test = require('../../lib/test'); + +test.suite(function(env) { + var driver; + + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + driver.quit(); + }); + + var testPageUrl = + 'data:text/html,

        ' + path.basename(__filename) + '

        '; + + test.beforeEach(function() { + driver.get(testPageUrl); + }); + + describe('phantomjs.Driver', function() { + describe('#executePhantomJS()', function() { + + test.it('can execute scripts using PhantomJS API', function() { + return driver.executePhantomJS('return this.url;').then(function(url) { + assert.equal(testPageUrl, decodeURIComponent(url)); + }); + }); + + test.it('can execute scripts as functions', function() { + driver.executePhantomJS(function(a, b) { + return a + b; + }, 1, 2).then(function(result) { + assert.equal(3, result); + }); + }); + + test.it('can manipulate the current page', function() { + driver.manage().addCookie('foo', 'bar'); + driver.manage().getCookie('foo').then(function(cookie) { + assert.equal('bar', cookie.value); + }); + driver.executePhantomJS(function() { + this.clearCookies(); + }); + driver.manage().getCookie('foo').then(function(cookie) { + assert.equal(null, cookie); + }); + }); + }); + }); +}, {browsers: ['phantomjs']}); diff --git a/test/promise_aplus_test.js b/test/promise_aplus_test.js new file mode 100644 index 0000000..b641296 --- /dev/null +++ b/test/promise_aplus_test.js @@ -0,0 +1,46 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +describe('Promises/A+ Compliance Tests', function() { + var promise = require('../_base').require('webdriver.promise'); + + var nullFunction = function() {}; + + before(function() { + promise.controlFlow().on('uncaughtException', nullFunction); + }); + + after(function() { + promise.controlFlow().removeListener('uncaughtException', nullFunction); + }); + + require('promises-aplus-tests').mocha({ + resolved: promise.fulfilled, + rejected: promise.rejected, + deferred: function() { + var d = promise.defer(); + return { + resolve: d.fulfill, + reject: d.reject, + promise: d.promise + }; + } + }); + +}); \ No newline at end of file diff --git a/test/proxy_test.js b/test/proxy_test.js index 89d66dd..b7a894f 100644 --- a/test/proxy_test.js +++ b/test/proxy_test.js @@ -1,35 +1,35 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; var http = require('http'), url = require('url'); -var promise = require('..').promise, +var Browser = require('..').Browser, + promise = require('..').promise, proxy = require('../proxy'), assert = require('../testing/assert'), test = require('../lib/test'), Server = require('../lib/test/httpserver').Server, - Browser = test.Browser, Pages = test.Pages; test.suite(function(env) { - env.autoCreateDriver = false; - function writeResponse(res, body, encoding, contentType) { res.writeHead(200, { 'Content-Length': Buffer.byteLength(body, encoding), @@ -78,23 +78,35 @@ test.suite(function(env) { ].join(''), 'utf8', 'text/html; charset=UTF-8'); }); - test.before(proxyServer.start.bind(proxyServer)); - test.before(helloServer.start.bind(helloServer)); - test.before(goodbyeServer.start.bind(helloServer)); + // Cannot pass start directly to mocha's before, as mocha will interpret the optional + // port parameter as an async callback parameter. + function mkStartFunc(server) { + return function() { + return server.start(); + }; + } + + + test.before(mkStartFunc(proxyServer)); + test.before(mkStartFunc(helloServer)); + test.before(mkStartFunc(goodbyeServer)); test.after(proxyServer.stop.bind(proxyServer)); test.after(helloServer.stop.bind(helloServer)); test.after(goodbyeServer.stop.bind(goodbyeServer)); - test.afterEach(env.dispose.bind(env)); + var driver; + test.beforeEach(function() { driver = null; }); + test.afterEach(function() { driver && driver.quit(); }); - test.ignore(env.browsers(Browser.SAFARI)). // Proxy support not implemented. + // Proxy support not implemented. + test.ignore(env.browsers(Browser.IE, Browser.OPERA, Browser.SAFARI)). describe('manual proxy settings', function() { // phantomjs 1.9.1 in webdriver mode does not appear to respect proxy // settings. - test.ignore(env.browsers(Browser.PHANTOMJS)). + test.ignore(env.browsers(Browser.PHANTOM_JS)). it('can configure HTTP proxy host', function() { - var driver = env.builder(). + driver = env.builder(). setProxy(proxy.manual({ http: proxyServer.host() })). @@ -107,9 +119,9 @@ test.suite(function(env) { }); // PhantomJS does not support bypassing the proxy for individual hosts. - test.ignore(env.browsers(Browser.PHANTOMJS)). + test.ignore(env.browsers(Browser.PHANTOM_JS)). it('can bypass proxy for specific hosts', function() { - var driver = env.builder(). + driver = env.builder(). setProxy(proxy.manual({ http: proxyServer.host(), bypass: helloServer.host() @@ -132,10 +144,11 @@ test.suite(function(env) { // PhantomJS does not support PAC file proxy configuration. // Safari does not support proxies. - test.ignore(env.browsers(Browser.PHANTOMJS, Browser.SAFARI)). + test.ignore(env.browsers( + Browser.IE, Browser.OPERA, Browser.PHANTOM_JS, Browser.SAFARI)). describe('pac proxy settings', function() { test.it('can configure proxy through PAC file', function() { - var driver = env.builder(). + driver = env.builder(). setProxy(proxy.pac(proxyServer.url('/proxy.pac'))). build(); diff --git a/test/remote_test.js b/test/remote_test.js new file mode 100644 index 0000000..c103d81 --- /dev/null +++ b/test/remote_test.js @@ -0,0 +1,72 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +var assert = require('assert'); + +var promise = require('../').promise; +var remote = require('../remote'); + +describe('DriverService', function() { + describe('start()', function() { + var service; + + beforeEach(function() { + service = new remote.DriverService(process.execPath, { + port: 1234, + args: ['-e', 'process.exit(1)'] + }); + }); + + afterEach(function(done) { + service.kill().thenFinally(function() { + done(); + }); + }); + + it('fails if child-process dies', function(done) { + this.timeout(1000); + service.start(500) + .then(expectFailure.bind(null, done), verifyFailure.bind(null, done)); + }); + + it('failures propagate through control flow if child-process dies', + function(done) { + this.timeout(1000); + + promise.controlFlow().execute(function() { + promise.controlFlow().execute(function() { + return service.start(500); + }); + }) + .then(expectFailure.bind(null, done), verifyFailure.bind(null, done)); + }); + + function verifyFailure(done, e) { + try { + assert.ok(!(e instanceof promise.CancellationError)); + assert.equal('Server terminated early with status 1', e.message); + done(); + } catch (ex) { + done(ex); + } + } + + function expectFailure(done) { + done(Error('expected to fail')); + } + }); +}); diff --git a/test/stale_element_test.js b/test/stale_element_test.js index 81dacc7..5a9fa6f 100644 --- a/test/stale_element_test.js +++ b/test/stale_element_test.js @@ -1,33 +1,37 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; var fail = require('assert').fail; -var By = require('..').By, +var Browser = require('..').Browser, + By = require('..').By, error = require('..').error, + until = require('..').until, assert = require('../testing/assert'), test = require('../lib/test'), - Browser = test.Browser, Pages = test.Pages; test.suite(function(env) { var driver; - beforeEach(function() { driver = env.driver; }); + test.before(function() { driver = env.builder().build(); }); + test.after(function() { driver.quit(); }); test.it( 'dynamically removing elements from the DOM trigger a ' + @@ -39,16 +43,7 @@ test.suite(function(env) { assert(toBeDeleted.isDisplayed()).isTrue(); driver.findElement(By.id('delete')).click(); - driver.wait(function() { - return toBeDeleted.isDisplayed(). - then(function() { return false; }). - then(null, function(e) { - if (e.code === error.ErrorCode.STALE_ELEMENT_REFERENCE) { - return true; - } - throw e; - }); - }, 5000, 'Element should be stale at this point'); + driver.wait(until.stalenessOf(toBeDeleted), 5000); }); test.it('an element found in a different frame is stale', function() { diff --git a/test/tag_name_test.js b/test/tag_name_test.js index c4f1672..d5e18a9 100644 --- a/test/tag_name_test.js +++ b/test/tag_name_test.js @@ -1,17 +1,19 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; @@ -21,9 +23,12 @@ var By = require('..').By, test.suite(function(env) { + var driver; + test.after(function() { driver.quit(); }); + test.it('should return lower case tag name', function() { - env.driver.get(test.Pages.formPage); - assert(env.driver.findElement(By.id('cheese')).getTagName()). - equalTo('input'); + driver = env.builder().build(); + driver.get(test.Pages.formPage); + assert(driver.findElement(By.id('cheese')).getTagName()).equalTo('input'); }); }); diff --git a/test/testing/index_test.js b/test/testing/index_test.js index bcd1d69..7a4b265 100644 --- a/test/testing/index_test.js +++ b/test/testing/index_test.js @@ -1,21 +1,24 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; var assert = require('assert'); +var promise = require('../..').promise; var test = require('../../testing'); @@ -38,4 +41,140 @@ describe('Mocha Integration', function() { test.it('', function() { this.x = 2; }); afterEach(function() { assert.equal(this.x, 2); }); }); + + describe('timeout handling', function() { + describe('it does not reset the control flow on a non-timeout', function() { + var flowReset = false; + + beforeEach(function() { + flowReset = false; + promise.controlFlow().once( + promise.ControlFlow.EventType.RESET, onreset); + }); + + test.it('', function() { + this.timeout(100); + return promise.delayed(50); + }); + + afterEach(function() { + assert.ok(!flowReset); + promise.controlFlow().removeListener( + promise.ControlFlow.EventType.RESET, onreset); + }); + + function onreset() { + flowReset = true; + } + }); + + describe('it resets the control flow after a timeout' ,function() { + var timeoutErr, flowReset; + + beforeEach(function() { + flowReset = false; + promise.controlFlow().once( + promise.ControlFlow.EventType.RESET, onreset); + }); + + test.it('', function() { + var callback = this.runnable().callback; + var test = this; + this.runnable().callback = function(err) { + timeoutErr = err; + // Reset our timeout to 0 so Mocha does not fail the test. + test.timeout(0); + // When we invoke the real callback, do not pass along the error so + // Mocha does not fail the test. + return callback.call(this); + }; + + test.timeout(50); + return promise.defer().promise; + }); + + afterEach(function() { + promise.controlFlow().removeListener( + promise.ControlFlow.EventType.RESET, onreset); + assert.ok(flowReset, 'control flow was not reset after a timeout'); + }); + + function onreset() { + flowReset = true; + } + }); + }); +}); + +describe('Mocha async "done" support', function() { + this.timeout(2*1000); + + var waited = false; + var DELAY = 100; // ms enough to notice + + // Each test asynchronously sets waited to true, so clear/check waited + // before/after: + beforeEach(function() { + waited = false; + }); + + afterEach(function() { + assert.strictEqual(waited, true); + }); + + // --- First, vanilla mocha "it" should support the "done" callback correctly. + + // This 'it' should block until 'done' is invoked + it('vanilla delayed', function(done) { + setTimeout(function delayedVanillaTimeout() { + waited = true; + done(); + }, DELAY); + }); + + // --- Now with the webdriver wrappers for 'it' should support the "done" callback: + + test.it('delayed', function(done) { + assert(done); + assert.strictEqual(typeof done, 'function'); + //console.log(done.name); + //console.log(done.toString()); + setTimeout(function delayedTimeoutCallback() { + waited = true; + done(); + }, DELAY); + }); + + // --- And test that the webdriver wrapper for 'it' works with a returned promise, too: + + test.it('delayed by promise', function() { + var defer = promise.defer(); + setTimeout(function delayedPromiseCallback() { + waited = true; + defer.fulfill('ignored'); + }); + return defer.promise; + }); + +}); + +describe('ControlFlow and "done" work together', function() { + var flow, order; + before(function() { + order = []; + flow = promise.controlFlow(); + flow.execute(function() { order.push(1); }); + }); + + test.it('control flow updates and async done', function(done) { + flow.execute(function() { order.push(2); }); + flow.execute(function() { order.push(3); }).then(function() { + order.push(4); + }); + done(); + }) + + after(function() { + assert.deepEqual([1, 2, 3, 4], order); + }) }); diff --git a/test/upload_test.js b/test/upload_test.js new file mode 100644 index 0000000..f7fd907 --- /dev/null +++ b/test/upload_test.js @@ -0,0 +1,85 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +'use strict'; + +var fs = require('fs'); + +var Browser = require('..').Browser, + By = require('..').By, + until = require('..').until, + io = require('../io'), + remote = require('../remote'), + assert = require('../testing/assert'), + test = require('../lib/test'), + Pages = test.Pages; + +test.suite(function(env) { + var LOREM_IPSUM_TEXT = 'lorem ipsum dolor sit amet'; + var FILE_HTML = '
        ' + LOREM_IPSUM_TEXT + '
        '; + + var fp; + test.before(function() { + return fp = io.tmpFile().then(function(fp) { + fs.writeFileSync(fp, FILE_HTML); + return fp; + }); + }) + + var driver; + test.before(function() { + driver = env.builder().build(); + }); + + test.after(function() { + if (driver) { + driver.quit(); + } + }); + + test.ignore(env.browsers( + Browser.IPAD, + Browser.IPHONE, + // Uploads broken in PhantomJS 2.0. + // See https://github.com/ariya/phantomjs/issues/12506 + Browser.PHANTOM_JS, + Browser.SAFARI)). + it('can upload files', function() { + driver.setFileDetector(new remote.FileDetector); + + driver.get(Pages.uploadPage); + + var fp = driver.call(function() { + return io.tmpFile().then(function(fp) { + fs.writeFileSync(fp, FILE_HTML); + return fp; + }); + }); + + driver.findElement(By.id('upload')).sendKeys(fp); + driver.findElement(By.id('go')).submit(); + + // Uploading files across a network may take a while, even if they're small. + var label = driver.findElement(By.id('upload_label')); + driver.wait(until.elementIsNotVisible(label), + 10 * 1000, 'File took longer than 10 seconds to upload!'); + + driver.switchTo().frame('upload_target'); + assert(driver.findElement(By.css('body')).getText()) + .equalTo(LOREM_IPSUM_TEXT); + }); +}); diff --git a/test/window_test.js b/test/window_test.js index 2abd10e..959c9dc 100644 --- a/test/window_test.js +++ b/test/window_test.js @@ -1,29 +1,34 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. 'use strict'; -var assert = require('../testing/assert'), - test = require('../lib/test'), - Browser = test.Browser; +var Browser = require('..').Browser, + assert = require('../testing/assert'), + test = require('../lib/test'); test.suite(function(env) { var driver; - beforeEach(function() { - driver = env.driver; + + test.before(function() { driver = env.builder().build(); }); + test.after(function() { driver.quit(); }); + + test.beforeEach(function() { driver.switchTo().defaultContent(); }); @@ -49,13 +54,20 @@ test.suite(function(env) { driver.manage().window().setPosition(position.x + 10, position.y + 10); // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) - if (env.browser === Browser.PHANTOMJS) { + if (env.currentBrowser() === Browser.PHANTOM_JS) { driver.manage().window().getPosition().then(function(position) { assert(position.x).equalTo(0); assert(position.y).equalTo(0); }); } else { - driver.wait(forPositionToBe(position.x + 10, position.y + 10), 1000); + var dx = position.x + 10; + var dy = position.y + 10; + // On OSX, Safari position's the window relative to below the menubar + // at the top of the screen, which is 23 pixels tall. + if (env.currentBrowser() === Browser.SAFARI && + process.platform === 'darwin') { + dy += 23; + } } }); }); @@ -68,13 +80,21 @@ test.suite(function(env) { driver.manage().window().setPosition(position.x + 10, position.y + 10); // For phantomjs, setPosition is a no-op and the "window" stays at (0, 0) - if (env.browser === Browser.PHANTOMJS) { + if (env.currentBrowser() === Browser.PHANTOM_JS) { driver.manage().window().getPosition().then(function(position) { assert(position.x).equalTo(0); assert(position.y).equalTo(0); }); } else { - driver.wait(forPositionToBe(position.x + 10, position.y + 10), 1000); + var dx = position.x + 10; + var dy = position.y + 10; + // On OSX, Safari position's the window relative to below the menubar + // at the top of the screen, which is 23 pixels tall. + if (env.currentBrowser() === Browser.SAFARI && + process.platform === 'darwin') { + dy += 23; + } + driver.wait(forPositionToBe(dx, dy), 1000); } }); }); @@ -83,7 +103,7 @@ test.suite(function(env) { driver.manage().window().getSize().then(function(size) { driver.manage().window().setSize(size.width + dx, size.height + dy); driver.wait(forSizeToBe(size.width + dx, size.height + dy), 1000); - }) + }); } function forSizeToBe(w, h) { @@ -100,7 +120,8 @@ test.suite(function(env) { return position.x === x && // On OSX, the window height may be bumped down 22px for the top // status bar. - (position.y >= y && position.y <= (y + 22)); + // On Linux, Opera's window position will be off by 28px. + (position.y >= y && position.y <= (y + 28)); }); }; } diff --git a/testing/assert.js b/testing/assert.js index 80fcda3..a4d729b 100644 --- a/testing/assert.js +++ b/testing/assert.js @@ -1,35 +1,35 @@ -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Defines a library that simplifies writing assertions against * promised values. * - *
        - *
        - * NOTE: This module is considered experimental and is subject to - * change, or removal, at any time! - *
        - *
        + * >
        + * > __NOTE:__ This module is considered experimental and is subject to + * > change, or removal, at any time! + * >
        * * Sample usage: - *
        
        - * var driver = new webdriver.Builder().build();
        - * driver.get('http://www.google.com');
          *
        - * assert(driver.getTitle()).equalTo('Google');
        - * 
        + * var driver = new webdriver.Builder().build(); + * driver.get('http://www.google.com'); + * + * assert(driver.getTitle()).equalTo('Google'); */ var base = require('../_base'), @@ -39,5 +39,23 @@ var base = require('../_base'), // PUBLIC API -/** @type {webdriver.testing.assert.} */ -module.exports = assert; +/** + * Creates a new assertion. + * @param {*} value The value to perform an assertion on. + * @return {!webdriver.testing.Assertion} The new assertion. + */ +module.exports = function(value) { + return assert(value); +}; + + +/** + * Registers a new assertion to expose from the + * {@link webdriver.testing.Assertion} prototype. + * @param {string} name The assertion name. + * @param {(function(new: goog.labs.testing.Matcher, *)| + * {matches: function(*): boolean, + * describe: function(): string})} matcherTemplate Either the + * matcher constructor to use, or an object literal defining a matcher. + */ +module.exports.register = assert.register; diff --git a/testing/index.js b/testing/index.js index 12ac341..2a1c4cf 100644 --- a/testing/index.js +++ b/testing/index.js @@ -1,85 +1,73 @@ -// Copyright 2013 Selenium committers -// Copyright 2013 Software Freedom Conservancy +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. /** * @fileoverview Provides wrappers around the following global functions from - * Mocha's BDD interface: - *
          - *
        • after - *
        • afterEach - *
        • before - *
        • beforeEach - *
        • it - *
        • it.only - *
        • it.skip - *
        • xit - *
        + * [Mocha's BDD interface](https://github.com/mochajs/mocha): * - *

        The provided wrappers leverage the webdriver.promise.ControlFlow to - * simplify writing asynchronous tests: - *

        
        - * var webdriver = require('selenium-webdriver'),
        - *     remote = require('selenium-webdriver/remote'),
        - *     test = require('selenium-webdriver/testing');
        + * - after
        + * - afterEach
        + * - before
        + * - beforeEach
        + * - it
        + * - it.only
        + * - it.skip
        + * - xit
          *
        - * test.describe('Google Search', function() {
        - *   var driver, server;
        + * The provided wrappers leverage the {@link webdriver.promise.ControlFlow}
        + * to simplify writing asynchronous tests:
          *
        - *   test.before(function() {
        - *     server = new remote.SeleniumServer({
        - *       jar: 'path/to/selenium-server-standalone.jar'
        - *     });
        - *     server.start();
        + *     var By = require('selenium-webdriver').By,
        + *         until = require('selenium-webdriver').until,
        + *         firefox = require('selenium-webdriver/firefox'),
        + *         test = require('selenium-webdriver/testing');
          *
        - *     driver = new webdriver.Builder().
        - *         withCapabilities({'browserName': 'firefox'}).
        - *         usingServer(server.address()).
        - *         build();
        - *   });
        + *     test.describe('Google Search', function() {
        + *       var driver;
          *
        - *   test.after(function() {
        - *     driver.quit();
        - *     server.stop();
        - *   });
        + *       test.before(function() {
        + *         driver = new firefox.Driver();
        + *       });
          *
        - *   test.it('should append query to title', function() {
        - *     driver.get('http://www.google.com');
        - *     driver.findElement(webdriver.By.name('q')).sendKeys('webdriver');
        - *     driver.findElement(webdriver.By.name('btnG')).click();
        - *     driver.wait(function() {
        - *       return driver.getTitle().then(function(title) {
        - *         return 'webdriver - Google Search' === title;
        + *       test.after(function() {
        + *         driver.quit();
          *       });
        - *     }, 1000, 'Waiting for title to update');
        - *   });
        - * });
        - * 
        * - *

        You may conditionally suppress a test function using the exported + * test.it('should append query to title', function() { + * driver.get('http://www.google.com/ncr'); + * driver.findElement(By.name('q')).sendKeys('webdriver'); + * driver.findElement(By.name('btnG')).click(); + * driver.wait(until.titleIs('webdriver - Google Search'), 1000); + * }); + * }); + * + * You may conditionally suppress a test function using the exported * "ignore" function. If the provided predicate returns true, the attached * test case will be skipped: - *

        
        - *   test.ignore(maybe()).it('is flaky', function() {
        - *     if (Math.random() < 0.5) throw Error();
        - *   });
          *
        - *   function maybe() { return Math.random() < 0.5; }
        - * 
        + * test.ignore(maybe()).it('is flaky', function() { + * if (Math.random() < 0.5) throw Error(); + * }); + * + * function maybe() { return Math.random() < 0.5; } */ -var flow = require('..').promise.controlFlow(); +var promise = require('..').promise; +var flow = promise.controlFlow(); /** @@ -103,32 +91,67 @@ function seal(fn) { */ function wrapped(globalFn) { return function() { - switch (arguments.length) { - case 1: - globalFn(asyncTestFn(arguments[0])); - break; - - case 2: - globalFn(arguments[0], asyncTestFn(arguments[1])); - break; - - default: - throw Error('Invalid # arguments: ' + arguments.length); + if (arguments.length === 1) { + return globalFn(makeAsyncTestFn(arguments[0])); + } + else if (arguments.length === 2) { + return globalFn(arguments[0], makeAsyncTestFn(arguments[1])); + } + else { + throw Error('Invalid # arguments: ' + arguments.length); } }; +} - function asyncTestFn(fn) { - return function(done) { - this.timeout(0); - var timeout = this.timeout; - this.timeout = undefined; // Do not let tests change the timeout. - try { - flow.execute(fn.bind(this)).then(seal(done), done); - } finally { - this.timeout = timeout; - } +/** + * Make a wrapper to invoke caller's test function, fn. Run the test function + * within a ControlFlow. + * + * Should preserve the semantics of Mocha's Runnable.prototype.run (See + * https://github.com/mochajs/mocha/blob/master/lib/runnable.js#L192) + * + * @param {Function} fn + * @return {Function} + */ +function makeAsyncTestFn(fn) { + var async = fn.length > 0; // if test function expects a callback, its "async" + + var ret = function(done) { + var runnable = this.runnable(); + var mochaCallback = runnable.callback; + runnable.callback = function() { + flow.reset(); + return mochaCallback.apply(this, arguments); }; - } + + var testFn = fn.bind(this); + flow.execute(function controlFlowExecute() { + return new promise.Promise(function(fulfill, reject) { + if (async) { + // If testFn is async (it expects a done callback), resolve the promise of this + // test whenever that callback says to. Any promises returned from testFn are + // ignored. + testFn(function testFnDoneCallback(err) { + if (err) { + reject(err); + } else { + fulfill(); + } + }); + } else { + // Without a callback, testFn can return a promise, or it will + // be assumed to have completed synchronously + fulfill(testFn()); + } + }, flow); + }, runnable.fullTitle()).then(seal(done), done); + }; + + ret.toString = function() { + return fn.toString(); + }; + + return ret; } From be9445574eacf7a617b9d623a7237563365f1466 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Fri, 23 Oct 2015 20:15:36 +0530 Subject: [PATCH 2/8] Updated README for version 2.47.0 --- README.md | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index db55a3e..36a993b 100644 --- a/README.md +++ b/README.md @@ -14,30 +14,25 @@ Install via npm with The sample below and others are included in the `example` directory. You may also find the tests for browserstack-webdriver informative. - - var webdriver = require('browserstack-webdriver'); - - // Input capabilities - var capabilities = { + + var webdriver = require('browserstack-webdriver'); + // Input capabilities + var capabilities = { 'browser' : 'firefox', 'browserstack.user' : BROWSERSTACK_USERNAME, 'browserstack.key' : BROWSERSTACK_KEY - } - - var driver = new webdriver.Builder(). + } + var driver = new webdriver.Builder(). usingServer('http://hub.browserstack.com/wd/hub'). withCapabilities(capabilities). build(); - - driver.get('http://www.google.com/ncr'); - driver.findElement(webdriver.By.name('q')).sendKeys('BrowserStack'); - driver.findElement(webdriver.By.name('btnG')).click(); - - driver.getTitle().then(function(title) { + driver.get('http://www.google.com/ncr'); + driver.findElement(webdriver.By.name('q')).sendKeys('BrowserStack'); + driver.findElement(webdriver.By.name('btnG')).click(); + driver.getTitle().then(function(title) { console.log(title); - }); - - driver.quit(); + }); + driver.quit(); ## Documentation From 856aa1afaf19a0887e0a05fc46ec0b30ebbfd139 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Fri, 23 Oct 2015 21:13:53 +0530 Subject: [PATCH 3/8] Add keep-alive dependency --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 27c4b61..77bbe4c 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "rimraf": "^2.2.8", "tmp": "0.0.24", "ws": "^0.8.0", - "xml2js": "0.4.4" + "xml2js": "0.4.4", + "keep-alive-agent": "*" }, "devDependencies": { "express": "^4.11.2", From 7d6258ee8e74f4de8f43524b56456b9b4121fe7d Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Mon, 26 Oct 2015 16:42:50 +0530 Subject: [PATCH 4/8] Updated README --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 36a993b..780c38d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# selenium-webdriver +# browserstack-webdriver Selenium is a browser automation library. Most often used for testing web-applications, Selenium may be used for any task that requires automating @@ -12,8 +12,7 @@ Install via npm with ## Usage -The sample below and others are included in the `example` directory. You may -also find the tests for browserstack-webdriver informative. +Below is a sample test, which opens Google's homepage, searches for ‘browserstack’, and asks for the title of the search results page. var webdriver = require('browserstack-webdriver'); // Input capabilities From 9516095b7ee266946833f59e056552a3289f66c7 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Thu, 29 Oct 2015 15:30:32 +0530 Subject: [PATCH 5/8] Add keep-alive agent --- http/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/http/index.js b/http/index.js index a636f97..4a99a92 100644 --- a/http/index.js +++ b/http/index.js @@ -47,7 +47,7 @@ var HttpClient = function(serverUrl, opt_agent, opt_proxy) { } /** @private {http.Agent} */ - this.agent_ = opt_agent; + this.agent_ = opt_agent || agent; /** @private {string} */ this.proxy_ = opt_proxy; @@ -87,7 +87,6 @@ HttpClient.prototype.send = function(httpRequest, callback) { port: this.options_.port, path: path, headers: httpRequest.headers, - agent: agent }; if (this.agent_) { From 5a4c7f0ddec4825bacbb39115ab36662fb2ec681 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Tue, 1 Dec 2015 18:22:02 +0530 Subject: [PATCH 6/8] Adding support for "browser" capability --- builder.js | 2 +- lib/webdriver/capabilities.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/builder.js b/builder.js index 3a4cd46..788e6f4 100644 --- a/builder.js +++ b/builder.js @@ -401,7 +401,7 @@ Builder.prototype.build = function() { capabilities.set(Capability.PLATFORM, browser[2] || null); } - browser = capabilities.get(Capability.BROWSER_NAME); + browser = capabilities.get(Capability.BROWSER_NAME) || capabilities.get(Capability.BROWSER); if (typeof browser !== 'string') { throw TypeError( diff --git a/lib/webdriver/capabilities.js b/lib/webdriver/capabilities.js index b441633..11af3b0 100644 --- a/lib/webdriver/capabilities.js +++ b/lib/webdriver/capabilities.js @@ -86,6 +86,12 @@ webdriver.Capability = { */ BROWSER_NAME: 'browserName', + /* + * New Capability, which describes the browser name for supporting + * testing on BrowserStack + */ + BROWSER : 'browser', + /** * Defines how elements should be scrolled into the viewport for interaction. * This capability will be set to zero (0) if elements are aligned with the From 914dbcaab9dcbaa9ad415fc480918231938f01d6 Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Wed, 2 Dec 2015 12:40:11 +0530 Subject: [PATCH 7/8] Fix package.json and other issues --- builder.js | 2 +- lib/webdriver/capabilities.js | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/builder.js b/builder.js index 3a4cd46..788e6f4 100644 --- a/builder.js +++ b/builder.js @@ -401,7 +401,7 @@ Builder.prototype.build = function() { capabilities.set(Capability.PLATFORM, browser[2] || null); } - browser = capabilities.get(Capability.BROWSER_NAME); + browser = capabilities.get(Capability.BROWSER_NAME) || capabilities.get(Capability.BROWSER); if (typeof browser !== 'string') { throw TypeError( diff --git a/lib/webdriver/capabilities.js b/lib/webdriver/capabilities.js index b441633..101d54a 100644 --- a/lib/webdriver/capabilities.js +++ b/lib/webdriver/capabilities.js @@ -86,6 +86,12 @@ webdriver.Capability = { */ BROWSER_NAME: 'browserName', + /** + * Additional capability, which contains browser name, to support webdriver + * tests on BrowserStack + */ + BROWSER: 'browser', + /** * Defines how elements should be scrolled into the viewport for interaction. * This capability will be set to zero (0) if elements are aligned with the From d20832d592fd4d832fa20acd0422a740abd1894f Mon Sep 17 00:00:00 2001 From: Akhil Lb Date: Tue, 22 Dec 2015 12:56:32 +0530 Subject: [PATCH 8/8] Package.json updated --- package.json | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 45e9a29..d44a40c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "selenium-webdriver", + "name": "browserstack-webdriver", "version": "2.47.0", - "description": "The official WebDriver JavaScript bindings from the Selenium project", + "description": "BrowserStack WebDriver JavaScript bindings from the Selenium project", "license": "Apache-2.0", "keywords": [ "automation", @@ -10,14 +10,14 @@ "webdriver", "webdriverjs" ], - "homepage": "https://github.com/SeleniumHQ/selenium", + "homepage": "https://github.com/browserstack/selenium-webdriver-nodejs", "bugs": { - "url": "https://github.com/SeleniumHQ/selenium/issues" + "url": "https://github.com/browserstack/selenium-webdriver-nodejs/issues" }, "main": "./index", "repository": { "type": "git", - "url": "git+https://github.com/SeleniumHQ/selenium.git" + "url": "git+https://github.com/browserstack/selenium-webdriver-nodejs" }, "engines": { "node": ">= 0.12.x" @@ -38,26 +38,5 @@ }, "scripts": { "test": "mocha --harmony -t 600000 --recursive test" - }, - "_id": "selenium-webdriver@2.47.0", - "_shasum": "e29b52d2daa0f648c40f602f4d2a8e8bee351ea5", - "_from": "selenium-webdriver@2.47", - "_resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.47.0.tgz", - "_npmVersion": "2.14.2", - "_nodeVersion": "4.0.0", - "_npmUser": { - "name": "jmleyba", - "email": "jmleyba@gmail.com" - }, - "maintainers": [ - { - "name": "jmleyba", - "email": "jmleyba@gmail.com" - } - ], - "dist": { - "shasum": "e29b52d2daa0f648c40f602f4d2a8e8bee351ea5", - "tarball": "http://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-2.47.0.tgz" - }, - "directories": {} + } }