selenium icon indicating copy to clipboard operation
selenium copied to clipboard

[🚀 Feature]: Language bindings for Firefox Prefs JS API

Open MatzFan opened this issue 1 year ago • 6 comments

Feature and motivation

I would like to be able to get the value of a preference set in about:config dynamically. These are set when instantiating a driver via Options[:prefs] values). I'd also like to be able to dynamically set a preference.

The JS API for Firefox prefs seems straightforward. I do note that Firefox CDP is being deprecated IFO BiDi, could someone confirm whether this JS API is part of that scope or not?

Usage example

From your own Ruby tests I see here that you get a preference value like so, by passing a JS string to the Driver#execute_script method.

dir  = driver.execute_script("return Services.prefs.getStringPref('browser.download.dir')")

Something like this would be much better:

driver.pref['browser.download.dir']
# => ""

And the equivalent to set a pref value:

driver.pref['browser.download.dir'] = 'foo/bar'
driver.pref['browser.download.dir']
# => "/foo/bar"

MatzFan avatar Jul 16 '24 11:07 MatzFan

@MatzFan, thank you for creating this issue. We will troubleshoot it as soon as we can.


Info for maintainers

Triage this issue by using labels.

If information is missing, add a helpful comment and then I-issue-template label.

If the issue is a question, add the I-question label.

If the issue is valid but there is no time to troubleshoot it, consider adding the help wanted label.

If the issue requires changes or fixes from an external project (e.g., ChromeDriver, GeckoDriver, MSEdgeDriver, W3C), add the applicable G-* label, and it will provide the correct link and auto-close the issue.

After troubleshooting the issue, please add the R-awaiting answer label.

Thank you!

github-actions[bot] avatar Jul 16 '24 11:07 github-actions[bot]

So as a proof of concept I've created a Ruby module to do this. It can get and set prefs exactly as set out above. I've omitted the tests here for brevity, please let me know if you are interested in supporting this - or if I am reinventing a wheel!

module Selenium
  module WebDriver
    module Firefox
      # representation of a Firefox pref API: https://firefox-source-docs.mozilla.org/devtools/preferences.html
      class Preference
        PREF_TYPES = { 'PREF_BOOL' => 128, 'PREF_INT' => 64, 'PREF_INVALID' => 0, 'PREF_STRING' => 32 }.freeze
        GET_METHODS = { 0 => 'getStringPref', 32 => 'getStringPref', 64 => 'getIntPref', 128 => 'getBoolPref' }.freeze
        SET_METHODS = { 32 => 'setStringPref', 64 => 'setIntPref', 128 => 'setBoolPref' }.freeze

        def initialize(driver)
          @driver = driver
        end

        def [](str)
          execute_script_and_preserve_context str
        end

        def []=(str, val)
          execute_script_and_preserve_context str, val
        end

        private

        def execute_script_and_preserve_context(str, val = nil)
          @driver.in_chrome_context { @driver.execute_script(script_string(str, val)) }
        end

        def script_string(str, val)
          val ? setter_script(str, val) : getter_script(str)
        end

        def getter_script(str)
          type = pref_type(str)
          "return Services.prefs.#{GET_METHODS[type]}('#{str}'#{default(type)})"
        end

        def default(type)
          type.zero? ? ", ''" : '' # empty string default arg if pref not found, otherwise no default arg
        end

        def setter_script(str, val)
          "Services.prefs.#{set_method(str, val)}('#{str}', #{quote_str(val)})"
        end

        def set_method(str, val)
          type = pref_type(str)
          return SET_METHODS[type] unless type.zero?
          return 'setBoolPref' if val.instance_of?(TrueClass) || val.instance_of?(FalseClass)
          return 'setIntPref' if val.is_a? Integer

          'setStringPref' # everthing else gets set as a string
        end

        def pref_type(string)
          @driver.execute_script("return Services.prefs.getPrefType('#{string}')")
        end

        def quote_str(value)
          value.is_a?(String) ? "'#{value}'" : value
        end
      end

      # adds Driver#pref method and [] and []= methods on that object
      module FirefoxPrefs
        def pref
          in_chrome_context { Preference.new(self) }
        end

        def in_chrome_context
          old_context = context
          self.context = 'chrome'
          yield
        ensure
          self.context = old_context
        end
      end

      class Driver
        prepend FirefoxPrefs
      end
    end
  end
end

MatzFan avatar Jul 29 '24 16:07 MatzFan

FWIW here is an example of a Python project using Selenium which uses the Firefox Prefs JS API to set driver prefs.

MatzFan avatar Jul 31 '24 06:07 MatzFan

We are looking for projects to host on https://github.com/seleniumhq-community/, if you wish to work on this, it would be a great plugin that can be added as a dependency by any user. Would you be interested in doing that?

diemol avatar Jul 31 '24 20:07 diemol

@diemol if you are not interested in incorporating such functionality directly into Selenium (it adds a dependency on the Firefox Prefs JS API) I was planning to simply release my code as a Ruby gem. Are Selenium Community repos simply mirrors of contributors' projects? If so I see no reason why I wouldn't set up a mirror there - and also for my Selenium Tor Browser project too. Can you point me to the docs on Selenium Community?

MatzFan avatar Aug 01 '24 09:08 MatzFan

Selenium is meant to be a base library where users can build things on top. This use case is the perfect example of that.

The community org is meant to host projects driven and owned by the community, with the advantage of using the Selenium name to promote them. We don't have any formal docs because we are getting started. If you are interested, please join our Slack workspace, and we can chat about this.

diemol avatar Aug 01 '24 09:08 diemol

I will close this as the issue has not had any more activity.

diemol avatar Nov 05 '24 11:11 diemol

This issue has been automatically locked since there has not been any recent activity since it was closed. Please open a new issue for related bugs.

github-actions[bot] avatar Dec 05 '24 22:12 github-actions[bot]