|  | 
|  | 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 | +package org.openqa.selenium.remote; | 
|  | 19 | + | 
|  | 20 | +import static org.openqa.selenium.remote.DriverCommand.GET; | 
|  | 21 | +import static org.openqa.selenium.remote.DriverCommand.PRINT_PAGE; | 
|  | 22 | + | 
|  | 23 | +import java.io.StringReader; | 
|  | 24 | +import java.util.ArrayList; | 
|  | 25 | +import java.util.HashMap; | 
|  | 26 | +import java.util.List; | 
|  | 27 | +import java.util.Map; | 
|  | 28 | +import java.util.concurrent.ConcurrentHashMap; | 
|  | 29 | +import java.util.concurrent.atomic.AtomicReference; | 
|  | 30 | +import java.util.function.BiFunction; | 
|  | 31 | +import org.openqa.selenium.UnsupportedCommandException; | 
|  | 32 | +import org.openqa.selenium.bidi.Network; | 
|  | 33 | +import org.openqa.selenium.bidi.Script; | 
|  | 34 | +import org.openqa.selenium.bidi.browsingcontext.BrowsingContext; | 
|  | 35 | +import org.openqa.selenium.bidi.browsingcontext.ReadinessState; | 
|  | 36 | +import org.openqa.selenium.json.Json; | 
|  | 37 | +import org.openqa.selenium.json.JsonInput; | 
|  | 38 | +import org.openqa.selenium.json.TypeToken; | 
|  | 39 | +import org.openqa.selenium.print.PageMargin; | 
|  | 40 | +import org.openqa.selenium.print.PageSize; | 
|  | 41 | +import org.openqa.selenium.print.PrintOptions; | 
|  | 42 | + | 
|  | 43 | +public class BiDiCommandMapper { | 
|  | 44 | + | 
|  | 45 | +  private static Json JSON = new Json(); | 
|  | 46 | + | 
|  | 47 | +  private final RemoteWebDriver driver; | 
|  | 48 | + | 
|  | 49 | +  private final AtomicReference<BrowsingContext> currentContext = new AtomicReference<>(); | 
|  | 50 | +  private final AtomicReference<Script> script = new AtomicReference<>(); | 
|  | 51 | +  private final AtomicReference<Network> network = new AtomicReference<>(); | 
|  | 52 | + | 
|  | 53 | +  private final Map<String, BiFunction<Command, RemoteWebDriver, Response>> commandHandlerMap = | 
|  | 54 | +      new HashMap<>(); | 
|  | 55 | + | 
|  | 56 | +  // Each browsing context has an associated id, maintains state | 
|  | 57 | +  // We will need to maintain a map of all the browsingContext to run further commands | 
|  | 58 | +  // Switching between tabs etc might be tricky | 
|  | 59 | +  private final Map<String, BrowsingContext> browsingContextMap = new ConcurrentHashMap<>(); | 
|  | 60 | + | 
|  | 61 | +  public BiDiCommandMapper(RemoteWebDriver driver) { | 
|  | 62 | +    this.driver = driver; | 
|  | 63 | +    init(this.driver); | 
|  | 64 | +  } | 
|  | 65 | + | 
|  | 66 | +  private void init(RemoteWebDriver driver) { | 
|  | 67 | +    script.set(new Script(driver)); | 
|  | 68 | +    network.set(new Network(driver)); | 
|  | 69 | + | 
|  | 70 | +    BrowsingContext parentContext = new BrowsingContext(driver, driver.getWindowHandle()); | 
|  | 71 | +    browsingContextMap.put(parentContext.getId(), parentContext); | 
|  | 72 | +    currentContext.set(parentContext); | 
|  | 73 | + | 
|  | 74 | +    commandHandlerMap.put( | 
|  | 75 | +        GET, | 
|  | 76 | +        (command, webDriver) -> { | 
|  | 77 | +          String pageLoadStrategy = | 
|  | 78 | +              (String) webDriver.getCapabilities().getCapability("pageLoadStrategy"); | 
|  | 79 | +          currentContext | 
|  | 80 | +              .get() | 
|  | 81 | +              .navigate( | 
|  | 82 | +                  (String) command.getParameters().get("url"), | 
|  | 83 | +                  ReadinessState.getReadinessState(pageLoadStrategy)); | 
|  | 84 | +          return new Response(webDriver.getSessionId()); | 
|  | 85 | +        }); | 
|  | 86 | + | 
|  | 87 | +    commandHandlerMap.put( | 
|  | 88 | +        PRINT_PAGE, | 
|  | 89 | +        (command, webDriver) -> { | 
|  | 90 | +          String result = currentContext.get().print(serializePrintOptions(command)); | 
|  | 91 | +          Response response = new Response(webDriver.getSessionId()); | 
|  | 92 | +          response.setValue(result); | 
|  | 93 | +          return response; | 
|  | 94 | +        }); | 
|  | 95 | +  } | 
|  | 96 | + | 
|  | 97 | +  public BiFunction<Command, RemoteWebDriver, Response> map(Command command) { | 
|  | 98 | +    // This is not optimal | 
|  | 99 | +    // But each time Classic calls switchTo, we need to identify the current browsing context to run | 
|  | 100 | +    // commands | 
|  | 101 | +    // Maybe we need a way where every time switchTo commands are called, only then we update the | 
|  | 102 | +    // current browsing context | 
|  | 103 | +    String currentWindowHandle = driver.getWindowHandle(); | 
|  | 104 | +    BrowsingContext browsingContext = | 
|  | 105 | +        browsingContextMap.computeIfAbsent( | 
|  | 106 | +            currentWindowHandle, windowHandle -> new BrowsingContext(driver, windowHandle)); | 
|  | 107 | + | 
|  | 108 | +    browsingContextMap.putIfAbsent(currentWindowHandle, browsingContext); | 
|  | 109 | +    currentContext.set(browsingContext); | 
|  | 110 | + | 
|  | 111 | +    if (commandHandlerMap.containsKey(command.getName())) { | 
|  | 112 | +      return commandHandlerMap.get(command.getName()); | 
|  | 113 | +    } else { | 
|  | 114 | +      throw new UnsupportedCommandException(); | 
|  | 115 | +    } | 
|  | 116 | +  } | 
|  | 117 | + | 
|  | 118 | +  private PrintOptions serializePrintOptions(Command command) { | 
|  | 119 | +    try (StringReader reader = new StringReader(JSON.toJson(command.getParameters())); | 
|  | 120 | +        JsonInput input = JSON.newInput(reader)) { | 
|  | 121 | +      PrintOptions printOptions = new PrintOptions(); | 
|  | 122 | + | 
|  | 123 | +      input.beginObject(); | 
|  | 124 | +      while (input.hasNext()) { | 
|  | 125 | +        switch (input.nextName()) { | 
|  | 126 | +          case "page": | 
|  | 127 | +            Map<String, Double> map = input.read(new TypeToken<Map<String, Double>>() {}.getType()); | 
|  | 128 | +            if (map.size() != 0) { | 
|  | 129 | +              printOptions.setPageSize(new PageSize(map.get("height"), map.get("width"))); | 
|  | 130 | +            } | 
|  | 131 | +            break; | 
|  | 132 | + | 
|  | 133 | +          case "orientation": | 
|  | 134 | +            String orientation = input.read(String.class); | 
|  | 135 | +            if (orientation.equals("portrait")) { | 
|  | 136 | +              printOptions.setOrientation(PrintOptions.Orientation.PORTRAIT); | 
|  | 137 | +            } else { | 
|  | 138 | +              printOptions.setOrientation(PrintOptions.Orientation.LANDSCAPE); | 
|  | 139 | +            } | 
|  | 140 | +            break; | 
|  | 141 | + | 
|  | 142 | +          case "scale": | 
|  | 143 | +            printOptions.setScale(input.read(Double.class)); | 
|  | 144 | +            break; | 
|  | 145 | + | 
|  | 146 | +          case "shrinkToFit": | 
|  | 147 | +            printOptions.setShrinkToFit(input.read(Boolean.class)); | 
|  | 148 | +            break; | 
|  | 149 | + | 
|  | 150 | +          case "background": | 
|  | 151 | +            printOptions.setBackground(input.read(Boolean.class)); | 
|  | 152 | +            break; | 
|  | 153 | + | 
|  | 154 | +          case "pageRanges": | 
|  | 155 | +            List<String> pageRanges = input.read(new TypeToken<ArrayList<String>>() {}.getType()); | 
|  | 156 | +            printOptions.setPageRanges(pageRanges); | 
|  | 157 | +            break; | 
|  | 158 | + | 
|  | 159 | +          case "margin": | 
|  | 160 | +            Map<String, Double> marginMap = | 
|  | 161 | +                input.read(new TypeToken<Map<String, Double>>() {}.getType()); | 
|  | 162 | +            if (marginMap.size() != 0) { | 
|  | 163 | +              printOptions.setPageMargin( | 
|  | 164 | +                  new PageMargin( | 
|  | 165 | +                      marginMap.get("top"), | 
|  | 166 | +                      marginMap.get("bottom"), | 
|  | 167 | +                      marginMap.get("left"), | 
|  | 168 | +                      marginMap.get("right"))); | 
|  | 169 | +            } | 
|  | 170 | +            break; | 
|  | 171 | + | 
|  | 172 | +          default: | 
|  | 173 | +            input.skipValue(); | 
|  | 174 | +            break; | 
|  | 175 | +        } | 
|  | 176 | +      } | 
|  | 177 | + | 
|  | 178 | +      input.endObject(); | 
|  | 179 | +      return printOptions; | 
|  | 180 | +    } | 
|  | 181 | +  } | 
|  | 182 | +} | 
0 commit comments